diff --git a/.github/workflows/windows-vcpkg.yml b/.github/workflows/windows-vcpkg.yml index 3a9bc71d65..e54c221cdb 100644 --- a/.github/workflows/windows-vcpkg.yml +++ b/.github/workflows/windows-vcpkg.yml @@ -85,11 +85,11 @@ jobs: ortools-url: ${{env.ORTOOLS_URL}} ortools-dir: ${{env.ORTOOLS_DIR}} - - name: Setup Python 3.11 + - name: Setup Python 3.12 uses: actions/setup-python@v4 with: architecture: 'x64' - python-version: '3.11' + python-version: '3.12' - name: Install pip dependencies if necessary run: pip install -r src/tests/examples/requirements.txt diff --git a/docs/reference-guide/03-commands.md b/docs/reference-guide/03-commands.md index f5fb8cc779..a0842e2d84 100644 --- a/docs/reference-guide/03-commands.md +++ b/docs/reference-guide/03-commands.md @@ -178,7 +178,7 @@ this command allows to state, for each kind of time-series, whether it should be the available set (be it ready-made or Antares-generated) _**OR**_ should take a user-defined value (in the former case, the default "rand" value should be kept; in the latter, the value should be the reference number of the time-series to use). Multiple simulation profiles can be defined and archived. The default active profile gives the "rand" status for all time-series in all areas (full probabilistic simulation). - Regarding Hydro time-series, the scenario builder gives, in addition to the assignment of a specific number to use for the inflows time-series, the ability to define the initial reservoir level to use for each MC year. + Regarding Hydro time-series, the scenario builder gives, in addition to the assignment of a specific number to use for the inflows time-series, the ability to define the initial reservoir level to use for each MC year, also hydro max power scenario builder is available to support time-series for Maximum Generation and Maximum Pumping because the number of TS's for ROR, Hydro Storage and Minimum Generation can be different than the number of TS's for Maximum Generation and Maximum Pumping. - **MC Scenario playlist** For each Monte-Carlo year of the simulation defined in the "Simulation" active window, this command allows to state whether a MC year prepared for the simulation should be actually simulated or not. diff --git a/docs/reference-guide/04-active_windows.md b/docs/reference-guide/04-active_windows.md index 8aaa5d523c..32a2fdf2e1 100644 --- a/docs/reference-guide/04-active_windows.md +++ b/docs/reference-guide/04-active_windows.md @@ -97,10 +97,10 @@ These two parts are detailed hereafter. ### RIGHT PART: Time-series management -For the different kinds of time-series that Antares manages in a non-deterministic way (load, thermal generation, hydro power, wind power, solar power or renewable depending on the option chosen): +For the different kinds of time-series that Antares manages in a non-deterministic way (load, thermal generation, hydro power, hydro max power, wind power, solar power or renewable depending on the option chosen): 1. **Choice of the kind of time-series to use** -Either « ready-made » or «stochastic » (i.e. Antares-generated), defined by setting the value to either "on" or "off". Note that for Thermal time-series, the cluster-wise parameter may overrule this global parameter (see Thermal window description below). +Either « ready-made » or «stochastic » (i.e. Antares-generated), defined by setting the value to either "on" or "off". Exception is hydro max power that can only be « ready-made ». Note that for Thermal time-series, the cluster-wise parameter may overrule this global parameter (see Thermal window description below). 2. **For stochastic TS only**: - **Number** Number of TS to generate @@ -295,11 +295,11 @@ which explains the comparatively long length of this chapter. In the main Window, the user may pick any area appearing in the list and is then given access to different tabs: -- The "time-series" tab displays the "ready-made" time-series already available for simulation purposes. There are two categories of time-series (displayed in two different subtabs): the Run of River (ROR) time-series on the one hand and the Storage power (SP) time-series on the other hand. +- The "time-series" tab displays the "ready-made" time-series already available for simulation purposes. There are five categories of time-series (displayed in five different subtabs): the Run of River (ROR) time-series, the Storage power (SP) time-series, the Minimum Generation power, the Maximum Generation power and the Maximum Pumping Power. - ROR time-series are defined at the hourly scale; each of the 8760 values represents the ROR power expected at a given hour, expressed in round number and in MW. The SP time-series are defined at the daily scale; each of the 365 values represents an overall SP energy expected in the day, expressed in round number and in MWh. These natural inflows are considered to be storable into a reservoir for later use. + ROR time-series are defined at the hourly scale; each of the 8760 values represents the ROR power expected at a given hour, expressed in round number and in MW. The SP time-series are defined at the daily scale; each of the 365 values represents an overall SP energy expected in the day, expressed in round number and in MWh. These natural inflows are considered to be storable into a reservoir for later use. The Minimum Generation time-series are defined at the hourly scale; each of the 8760 values represents the Minimum Generation power expected at a given hour expressed in round number and in MW. The Maximum Generation time-series are defined at the hourly scale; each of the 8760 values represents the Maximum Generation power expected at a given hour expressed in round number and in MW. The Maximum Pumping time-series are defined at the hourly scale; each of the 8760 values represents the Maximum Pumping power expected at a given hour expressed in round number and in MW. - Both types of data may come from any origin outside Antares, or may have been formerly generated by the Antares time-series stochastic generator and stored as input data on the user's request. Different ways to update data are: + ROR time-series and SP time-series may come from any origin outside Antares, or may have been formerly generated by the Antares time-series stochastic generator and stored as input data on the user's request. Minimum Generation, Maximum Generation and Maximum Pumping may come from any origin outside Antares, but they can not be generated by the Antares time-series stochastic generator. Different ways to update data are: - direct typing - copy/paste a selected field to/from the clipboard @@ -313,7 +313,8 @@ In the main Window, the user may pick any area appearing in the list and is then - _Note that:_ - - _For a given area, the number of ROR time-series and SP times-series **must** be identical_ + - _For a given area, the number of ROR time-series, SP times-series and Minimum Generation **must** be identical_ + - _For a given area, the number of Maximum Generation and Maximum Pumping **must** be identical_ - _If the "intra-modal correlated draws" option was not selected in the_ **simulation** _window, MC adequacy or economy simulations can take place even if the number of hydro time-series is not the same in all areas (e.g. 2 , 5 , 1 , 45 ,...)_ @@ -376,10 +377,10 @@ the weekly optimal hydro-thermal unit-commitment and dispatch process. Standard credits (Bottom part) The bottom part displays two daily time-series (365 values) defined for energy generation/storage -(hydro turbines or hydro pumps). In each case, the first array defines the maximum power (generated or absorbed), -and the second defines the maximum daily energy (either generated or stored). +(hydro turbines or hydro pumps). Both arrays represents the maximum daily energy (either generated or stored). -For the sake of clarity, maximum daily energies are expressed as a number of hours at maximum power. +For the sake of clarity, maximum daily energies are expressed as a number of hours at maximum power and these values +are used along with the Maximum Generation and Maximum Pumping TS's to calculate daily mean energy. Credit modulation (Upper part) diff --git a/docs/reference-guide/13-file-format.md b/docs/reference-guide/13-file-format.md index 116ebfde11..26c5cb3401 100644 --- a/docs/reference-guide/13-file-format.md +++ b/docs/reference-guide/13-file-format.md @@ -1,5 +1,13 @@ # Study format changes This is a list of all recent changes that came with new Antares Simulator features. The main goal of this document is to lower the costs of changing existing interfaces, both GUI and scripts. +## v8.8.0 +For each area, new files are added **input/hydro/series/<area>/maxHourlyGenPower.txt** and **input/hydro/series/<area>/maxHourlyPumpPower.txt**. These files have one or more columns, and 8760 rows. The number of columns in these two files must be the same, if there is more than one column in each file, but if there is just one column for example in maxHourlyGenPower.txt file, maxHourlyPumpPower.txt file can have more than one column and vice versa. For old studies, file **input/hydro/common/capacity/maxpower_<area>** will be deleted after upgrading the study and corresponding data from that file will be copied to already mentioned new files, even if study is upgraded or not. In that case maxHourlyGenPower.txt and maxHourlyPumpPower.txt files will have just one column and 8760 rows. + +Also for each area, new files are added **input/hydro/common/capacity/maxDailyGenEnergy_<area>** and **input/hydro/common/capacity/maxDailyPumpEnergy_<area>**. These files have just one column and 365 rows. For old studies, file **input/hydro/common/capacity/maxpower_<area>** will be deleted after upgrading the study and corresponding data from that file will be copied to already mentioned new files, even if study is upgraded or not. +### Input +Under `Configure/MC Scenario Builder` new section added `Hydro Max Power` +In the existing file **settings/scenariobuilder.dat**, under **<ruleset>** section following properties added: +* **hgp,<area>,<year> = <hgp-value>** ## v8.7.0 ### Input #### Scenarized RHS for binding constraints diff --git a/src/libs/antares/CMakeLists.txt b/src/libs/antares/CMakeLists.txt index 6f7ce1063e..28a8916523 100644 --- a/src/libs/antares/CMakeLists.txt +++ b/src/libs/antares/CMakeLists.txt @@ -9,7 +9,6 @@ add_subdirectory(object) add_subdirectory(array) add_subdirectory(correlation) - add_subdirectory(logs) add_subdirectory(jit) add_subdirectory(inifile) diff --git a/src/libs/antares/constants.h b/src/libs/antares/constants.h index f82f0372de..61372b62bc 100644 --- a/src/libs/antares/constants.h +++ b/src/libs/antares/constants.h @@ -42,6 +42,8 @@ /*! Hours per year */ #define HOURS_PER_YEAR 8760 +#define HOURS_PER_DAY 24U + namespace Antares::Constants { extern const std::array daysPerMonth; diff --git a/src/libs/antares/study/CMakeLists.txt b/src/libs/antares/study/CMakeLists.txt index 700b6aff2e..be95d2762b 100644 --- a/src/libs/antares/study/CMakeLists.txt +++ b/src/libs/antares/study/CMakeLists.txt @@ -23,6 +23,8 @@ set(SRC_STUDY_SCENARIO_BUILDER scenario-builder/ThermalTSNumberData.h scenario-builder/HydroTSNumberData.h scenario-builder/HydroTSNumberData.cpp + scenario-builder/HydroMaxPowerTSNumberData.h + scenario-builder/HydroMaxPowerTSNumberData.cpp scenario-builder/SolarTSNumberData.cpp scenario-builder/solarTSNumberData.h scenario-builder/WindTSNumberData.h @@ -135,6 +137,10 @@ set(SRC_STUDY_PART_HYDRO parts/hydro/allocation.h parts/hydro/allocation.hxx parts/hydro/allocation.cpp + parts/hydro/hydromaxtimeseriesreader.h + parts/hydro/hydromaxtimeseriesreader.cpp + parts/hydro/pair-of-integers.h + parts/hydro/pair-of-integers.cpp ) source_group("study\\part\\hydro" FILES ${SRC_STUDY_PART_HYDRO}) diff --git a/src/libs/antares/study/area/area.cpp b/src/libs/antares/study/area/area.cpp index 030af80586..fa96644f71 100644 --- a/src/libs/antares/study/area/area.cpp +++ b/src/libs/antares/study/area/area.cpp @@ -280,6 +280,7 @@ void Area::resizeAllTimeseriesNumbers(uint n) solar.series->timeseriesNumbers.clear(); wind.series->timeseriesNumbers.clear(); hydro.series->timeseriesNumbers.clear(); + hydro.series->timeseriesNumbersHydroMaxPower.clear(); for (auto& namedLink : links) { AreaLink* link = namedLink.second; @@ -292,6 +293,7 @@ void Area::resizeAllTimeseriesNumbers(uint n) solar.series->timeseriesNumbers.resize(1, n); wind.series->timeseriesNumbers.resize(1, n); hydro.series->timeseriesNumbers.resize(1, n); + hydro.series->timeseriesNumbersHydroMaxPower.resize(1, n); for (auto& namedLink : links) { AreaLink* link = namedLink.second; diff --git a/src/libs/antares/study/area/area.h b/src/libs/antares/study/area/area.h index d49e3e0309..cc7cbae443 100644 --- a/src/libs/antares/study/area/area.h +++ b/src/libs/antares/study/area/area.h @@ -336,7 +336,6 @@ class Area final : private Yuni::NonCopyable void createMissingTimeSeries(); void createMissingPrepros(); - }; // class Area bool saveAreaOptimisationIniFile(const Area& area, const Yuni::Clob& buffer); diff --git a/src/libs/antares/study/area/list.cpp b/src/libs/antares/study/area/list.cpp index 0526108ee0..dd1519eea4 100644 --- a/src/libs/antares/study/area/list.cpp +++ b/src/libs/antares/study/area/list.cpp @@ -901,10 +901,49 @@ static bool AreaListLoadFromFolderSingleArea(Study& study, buffer.clear() << study.folderInput << SEP << "hydro" << SEP << "prepro"; ret = area.hydro.prepro->loadFromFolder(study, area.id, buffer.c_str()) && ret; } - if (area.hydro.series && (!options.loadOnlyNeeded || !area.hydro.prepro)) // Series + + if (auto* hydroSeries = area.hydro.series; hydroSeries) + { + if (!options.loadOnlyNeeded || !area.hydro.prepro) // Series + { + buffer.clear() << study.folderInput << SEP << "hydro" << SEP << "series"; + ret = hydroSeries->loadGenerationTS(area.id, buffer, study.header.version) && ret; + + hydroSeries->EqualizeGenerationTSsizes( + area, study.usedByTheSolver, study.gotFatalError); + } + + if (study.header.version < 870) + { + buffer.clear() << study.folderInput << SEP << "hydro"; + + HydroMaxTimeSeriesReader reader; + ret = reader(buffer, area, study.usedByTheSolver) && ret; + } + + if (study.header.version >= 870) + { + buffer.clear() << study.folderInput << SEP << "hydro" << SEP << "series"; + ret = hydroSeries->LoadMaxPower(area.id, buffer) && ret; + + if (study.usedByTheSolver) + { + hydroSeries->EqualizeMaxPowerTSsizes(area, study.gotFatalError); + } + else + hydroSeries->setHydroModulability(area); + } + + hydroSeries->resizeTSinDeratedMode( + study.parameters.derated, study.header.version, study.usedByTheSolver); + } + + buffer.clear() << study.folderInput << SEP << "hydro" << SEP << "common" << SEP + << "capacity" << SEP << "maxpower_" << area.id << ".txt"; + + if (bool exists = IO::File::Exists(buffer); study.header.version >= 870 && exists) { - buffer.clear() << study.folderInput << SEP << "hydro" << SEP << "series"; - ret = area.hydro.series->loadFromFolder(study, area.id, buffer) && ret; + IO::File::Delete(buffer); } ++options.progressTicks; @@ -1610,10 +1649,7 @@ void AreaList::removeLoadTimeseries() void AreaList::removeHydroTimeseries() { each([&](Data::Area& area) { - area.hydro.series->ror.reset(1, HOURS_PER_YEAR); - area.hydro.series->storage.reset(1, DAYS_PER_YEAR); - area.hydro.series->mingen.reset(1, HOURS_PER_YEAR); - area.hydro.series->count = 1; + area.hydro.series->reset(); }); } diff --git a/src/libs/antares/study/area/scratchpad.cpp b/src/libs/antares/study/area/scratchpad.cpp index 36aba7b633..4233293ad4 100644 --- a/src/libs/antares/study/area/scratchpad.cpp +++ b/src/libs/antares/study/area/scratchpad.cpp @@ -32,9 +32,7 @@ using namespace Yuni; -namespace Antares -{ -namespace Data +namespace Antares::Data { AreaScratchpad::TimeseriesData::TimeseriesData(Area& area) : load(area.load.series->timeSeries), solar(area.solar.series->timeSeries), wind(area.wind.series->timeSeries) @@ -56,12 +54,6 @@ AreaScratchpad::AreaScratchpad(const StudyRuntimeInfos& rinfos, Area& area) : ts originalMustrunSum[h] = std::numeric_limits::quiet_NaN(); } - for (uint d = 0; d != DAYS_PER_YEAR; ++d) - { - optimalMaxPower[d] = std::numeric_limits::quiet_NaN(); - pumpingMaxPower[d] = std::numeric_limits::quiet_NaN(); - } - // Fatal hors hydro { double sum; @@ -83,6 +75,18 @@ AreaScratchpad::AreaScratchpad(const StudyRuntimeInfos& rinfos, Area& area) : ts } } + // Hourly maximum generation/pumping power matrices and their number of TS's (width of matrices) + auto const& maxHourlyGenPower = area.hydro.series->maxHourlyGenPower; + auto const& maxHourlyPumpPower = area.hydro.series->maxHourlyPumpPower; + uint nbOfMaxPowerTimeSeries = area.hydro.series->maxPowerTScount(); + + // Setting width and height of daily mean maximum generation/pumping power matrices + meanMaxDailyGenPower.reset(nbOfMaxPowerTimeSeries, DAYS_PER_YEAR); + meanMaxDailyPumpPower.reset(nbOfMaxPowerTimeSeries, DAYS_PER_YEAR); + + // Instantiate daily mean maximum generation/pumping power matrices + CalculateMeanDailyMaxPowerMatrices(maxHourlyGenPower, maxHourlyPumpPower, nbOfMaxPowerTimeSeries); + // =============== // hydroHasMod // =============== @@ -93,20 +97,10 @@ AreaScratchpad::AreaScratchpad(const StudyRuntimeInfos& rinfos, Area& area) : ts // Useful whether we use a heuristic target or not bool hydroGenerationPermission = false; - // ... Getting hydro max power - auto const& maxPower = area.hydro.maxPower; - - // ... Hydro max generating power and energy - auto const& maxGenP = maxPower[Data::PartHydro::genMaxP]; - auto const& maxGenE = maxPower[Data::PartHydro::genMaxE]; + // ... Getting hydro max energy + auto const& maxDailyGenEnergy = area.hydro.maxDailyGenEnergy[0]; - double value = 0.; - for (uint d = 0; d < DAYS_PER_YEAR; ++d) - value += maxGenP[d] * maxGenE[d]; - - // If generating energy is nil over the whole year, hydroGenerationPermission is false, true - // otherwise. - hydroGenerationPermission = (value > 0.); + hydroGenerationPermission = CheckForPositiveEnergy(maxHourlyGenPower, maxDailyGenEnergy); // --------------------- // Hydro has inflows @@ -135,30 +129,64 @@ AreaScratchpad::AreaScratchpad(const StudyRuntimeInfos& rinfos, Area& area) : ts // -------------------------- hydroHasMod = hydroHasInflows || hydroGenerationPermission; - // =============== // Pumping // =============== - // ... Hydro max power - // ... Hydro max pumping power and energy - auto const& maxPumpingP = maxPower[Data::PartHydro::pumpMaxP]; - auto const& maxPumpingE = maxPower[Data::PartHydro::pumpMaxE]; + // Hydro max pumping energy + auto const& maxDailyPumpEnergy = area.hydro.maxDailyPumpEnergy[0]; + + // If pumping energy is nil over the whole year, pumpHasMod is false, true otherwise. + pumpHasMod = CheckForPositiveEnergy(maxHourlyPumpPower, maxDailyPumpEnergy); +} - // ... Pumping max power - for (uint d = 0; d != DAYS_PER_YEAR; ++d) - pumpingMaxPower[d] = maxPumpingP[d]; +AreaScratchpad::~AreaScratchpad() = default; - double valuePumping = 0.; - // ... Computing 'pumpHasMod' parameter - for (uint d = 0; d < DAYS_PER_YEAR; ++d) - valuePumping += maxPumpingP[d] * maxPumpingE[d]; +void AreaScratchpad::CalculateMeanDailyMaxPowerMatrices(const Matrix& hourlyMaxGenMatrix, + const Matrix& hourlyMaxPumpMatrix, + uint nbOfMaxPowerTimeSeries) +{ + for (uint nbOfTimeSeries = 0; nbOfTimeSeries < nbOfMaxPowerTimeSeries; ++nbOfTimeSeries) + { + auto& hourlyMaxGenColumn = hourlyMaxGenMatrix[nbOfTimeSeries]; + auto& hourlyMaxPumpColumn = hourlyMaxPumpMatrix[nbOfTimeSeries]; + auto& MeanMaxDailyGenPowerColumn = meanMaxDailyGenPower[nbOfTimeSeries]; + auto& MeanMaxDailyPumpPowerColumn = meanMaxDailyPumpPower[nbOfTimeSeries]; - // If pumping energy is nil over the whole year, pumpHasMod is false, true otherwise. - pumpHasMod = (valuePumping > 0.); + CalculateDailyMeanPower(hourlyMaxGenColumn, MeanMaxDailyGenPowerColumn); + CalculateDailyMeanPower(hourlyMaxPumpColumn, MeanMaxDailyPumpPowerColumn); + } } -AreaScratchpad::~AreaScratchpad() = default; +bool CheckForPositiveEnergy(const Matrix& power, + const Matrix::ColumnType& energy) +{ + for (uint tsNumber = 0; tsNumber < power.width; ++tsNumber) + { + double yearlyMaxGenEnergy = 0; + + for (uint day = 0; day < DAYS_PER_YEAR; ++day) + { + yearlyMaxGenEnergy += power[tsNumber][day] * energy[day]; + + if (yearlyMaxGenEnergy > 0.) + return true; + } + } + + return false; +} + +void CalculateDailyMeanPower(const Matrix::ColumnType& hourlyColumn, + Matrix::ColumnType& dailyColumn) +{ + for (uint day = 0; day < DAYS_PER_YEAR; ++day) + { + dailyColumn[day] = std::accumulate(hourlyColumn + day * HOURS_PER_DAY, + hourlyColumn + day * HOURS_PER_DAY + HOURS_PER_DAY, + 0) + / 24.; + } +} -} // namespace Data -} // namespace Antares +} // namespace Antares::Data diff --git a/src/libs/antares/study/area/scratchpad.h b/src/libs/antares/study/area/scratchpad.h index 01199a7cf8..f019ad7c75 100644 --- a/src/libs/antares/study/area/scratchpad.h +++ b/src/libs/antares/study/area/scratchpad.h @@ -34,10 +34,9 @@ #include #include #include +#include -namespace Antares -{ -namespace Data +namespace Antares::Data { /*! ** \brief Scratchpad for temporary data performed by the solver @@ -90,22 +89,44 @@ class AreaScratchpad final // This variable is initialized every MC-year double originalMustrunSum[HOURS_PER_YEAR]; - //! Optimal max power (OPP) - Hydro management - double optimalMaxPower[DAYS_PER_YEAR]; - - //! - double pumpingMaxPower[DAYS_PER_YEAR]; - - /*! + /*! ** \brief Dispatchable Generation Margin ** ** Those values, written by the output, must be calculated before ** running the hydro remix. */ double dispatchableGenerationMargin[168]; + + /*! + ** \brief Daily mean maximum power matrices + ** + ** These matrices will be calculated based on maximum + ** hourly generation/pumping matrices + */ + Matrix meanMaxDailyGenPower; + Matrix meanMaxDailyPumpPower; + +private: + /*! + ** \brief Caluclation of daily mean maximum power matrices + ** + ** Calculates daily mean maximum generation/pumping power + ** power matrices meanMaxDailyGenPower/meanMaxDailyPumpPower + */ + void CalculateMeanDailyMaxPowerMatrices(const Matrix& hourlyMaxGenMatrix, + const Matrix& hourlyMaxPumpMatrix, + uint nbOfMaxPowerTimeSeries); + }; // class AreaScratchpad -} // namespace Data -} // namespace Antares +// Calculates daily mean maximum generation/pumping power for one column/time-serie +void CalculateDailyMeanPower(const Matrix::ColumnType& hourlyColumn, + Matrix::ColumnType& dailyColumn); + +// Return true if maximum generated energy just in one day and for every TS is grated than 0 +bool CheckForPositiveEnergy(const Matrix& power, + const Matrix::ColumnType& energy); + +} // namespace Antares::Data #endif // __ANTARES_LIBS_STUDY_AREA_SCRATCHPAD_H__ diff --git a/src/libs/antares/study/area/store-timeseries-numbers.cpp b/src/libs/antares/study/area/store-timeseries-numbers.cpp index 4a2a1a0447..b453fb4fc2 100644 --- a/src/libs/antares/study/area/store-timeseries-numbers.cpp +++ b/src/libs/antares/study/area/store-timeseries-numbers.cpp @@ -48,7 +48,7 @@ struct TSNumbersPredicate }; } // anonymous namespace -static void genericStoreTimeseriesNumbers(Solver::IResultWriter::Ptr writer, +static void storeTSnumbers(Solver::IResultWriter::Ptr writer, const Matrix& timeseriesNumbers, const String& id, const String& directory) @@ -69,22 +69,28 @@ static void genericStoreTimeseriesNumbers(Solver::IResultWriter::Ptr writer, void storeTimeseriesNumbersForLoad(Solver::IResultWriter::Ptr writer, const Area& area) { - genericStoreTimeseriesNumbers(writer, area.load.series->timeseriesNumbers, area.id, "load"); + storeTSnumbers(writer, area.load.series->timeseriesNumbers, area.id, "load"); } void storeTimeseriesNumbersForSolar(Solver::IResultWriter::Ptr writer, const Area& area) { - genericStoreTimeseriesNumbers(writer, area.solar.series->timeseriesNumbers, area.id, "solar"); + storeTSnumbers(writer, area.solar.series->timeseriesNumbers, area.id, "solar"); } void storeTimeseriesNumbersForHydro(Solver::IResultWriter::Ptr writer, const Area& area) { - genericStoreTimeseriesNumbers(writer, area.hydro.series->timeseriesNumbers, area.id, "hydro"); + storeTSnumbers(writer, area.hydro.series->timeseriesNumbers, area.id, "hydro"); +} + + +void storeTimeseriesNumbersForHydroMaxPower(Solver::IResultWriter::Ptr writer, const Area& area) +{ + storeTSnumbers(writer, area.hydro.series->timeseriesNumbersHydroMaxPower, area.id, "max-power"); } void storeTimeseriesNumbersForWind(Solver::IResultWriter::Ptr writer, const Area& area) { - genericStoreTimeseriesNumbers(writer, area.wind.series->timeseriesNumbers, area.id, "wind"); + storeTSnumbers(writer, area.wind.series->timeseriesNumbers, area.id, "wind"); } void storeTimeseriesNumbersForThermal(Solver::IResultWriter::Ptr writer, const Area& area) diff --git a/src/libs/antares/study/area/store-timeseries-numbers.h b/src/libs/antares/study/area/store-timeseries-numbers.h index d3cb6bfe6f..c5a0d2cb7e 100644 --- a/src/libs/antares/study/area/store-timeseries-numbers.h +++ b/src/libs/antares/study/area/store-timeseries-numbers.h @@ -46,6 +46,7 @@ namespace Antares::Data void storeTimeseriesNumbersForThermal(Solver::IResultWriter::Ptr writer, const Area& area); void storeTimeseriesNumbersForRenewable(Solver::IResultWriter::Ptr writer, const Area& area); void storeTimeseriesNumbersForTransmissionCapacities(Solver::IResultWriter::Ptr writer, const Area& area); + void storeTimeseriesNumbersForHydroMaxPower(Solver::IResultWriter::Ptr writer, const Area& area); } //Antares::Data diff --git a/src/libs/antares/study/area/store-timeseries-numbers.hxx b/src/libs/antares/study/area/store-timeseries-numbers.hxx index 7def347947..ea183242f9 100644 --- a/src/libs/antares/study/area/store-timeseries-numbers.hxx +++ b/src/libs/antares/study/area/store-timeseries-numbers.hxx @@ -62,6 +62,9 @@ void singleAreaStoreTimeseriesNumbers(Solver::IResultWriter::Ptr writer, const A case timeSeriesTransmissionCapacities: storeTimeseriesNumbersForTransmissionCapacities(writer, area); break; + case timeSeriesHydroMaxPower: + storeTimeseriesNumbersForHydroMaxPower(writer, area); + break; case timeSeriesCount: default: break; diff --git a/src/libs/antares/study/cleaner/cleaner-v20.cpp b/src/libs/antares/study/cleaner/cleaner-v20.cpp index a0f4dea784..240d38aba3 100644 --- a/src/libs/antares/study/cleaner/cleaner-v20.cpp +++ b/src/libs/antares/study/cleaner/cleaner-v20.cpp @@ -87,7 +87,9 @@ static void listOfFilesAnDirectoriesToKeepForArea(PathList& e, PathList& p, cons e.add(buffer); buffer.clear() << "input/hydro/common/capacity/inflowPattern_" << id << ".txt"; e.add(buffer); - buffer.clear() << "input/hydro/common/capacity/maxpower_" << id << ".txt"; + buffer.clear() << "input/hydro/common/capacity/maxDailyGenEnergy_" << id << ".txt"; + e.add(buffer); + buffer.clear() << "input/hydro/common/capacity/maxDailyPumpEnergy_" << id << ".txt"; e.add(buffer); buffer.clear() << "input/hydro/common/capacity/reservoir_" << id << ".txt"; e.add(buffer); @@ -98,7 +100,11 @@ static void listOfFilesAnDirectoriesToKeepForArea(PathList& e, PathList& p, cons buffer.clear() << "input/hydro/series/" << id << "/mod.txt"; e.add(buffer); buffer.clear() << "input/hydro/series/" << id << "/mingen.txt"; - e.add(buffer); + e.add(buffer); + buffer.clear() << "input/hydro/series/" << id << "/maxHourlyGenPower.txt"; + e.add(buffer); + buffer.clear() << "input/hydro/series/" << id << "/maxHourlyPumpPower.txt"; + e.add(buffer); buffer.clear() << "input/hydro/allocation/" << id << ".ini"; p.add(buffer); buffer.clear() << "input/hydro/prepro/" << id; diff --git a/src/libs/antares/study/fwd.h b/src/libs/antares/study/fwd.h index 5c02e62655..9afca30e8a 100644 --- a/src/libs/antares/study/fwd.h +++ b/src/libs/antares/study/fwd.h @@ -214,16 +214,18 @@ enum TimeSeries : unsigned int //! TimeSeries : Wind timeSeriesWind = 4, //! TimeSeries : Thermal - timeSeriesThermal = 8, + timeSeriesThermal = 128, //! TimeSeries : Solar timeSeriesSolar = 16, //! TimeSeries : Renewable timeSeriesRenewable = 32, //! TimeSeries : Renewable timeSeriesTransmissionCapacities = 64, + //! TimeSeries : Hydro Max Power + timeSeriesHydroMaxPower = 256, //! The maximum number of time-series that we can encounter - timeSeriesCount = 7, + timeSeriesCount = 8, }; // enum TimeSeries @@ -255,7 +257,7 @@ struct TimeSeriesBitPatternIntoIndex<4> }; }; template<> -struct TimeSeriesBitPatternIntoIndex<8> +struct TimeSeriesBitPatternIntoIndex<128> { enum { @@ -306,7 +308,7 @@ struct TimeSeriesToCStr<4> } }; template<> -struct TimeSeriesToCStr<8> +struct TimeSeriesToCStr<128> { static const char* Value() { diff --git a/src/libs/antares/study/parameters.cpp b/src/libs/antares/study/parameters.cpp index 6126e94e7e..c255129b3a 100644 --- a/src/libs/antares/study/parameters.cpp +++ b/src/libs/antares/study/parameters.cpp @@ -74,6 +74,8 @@ static bool ConvertCStrToListTimeSeries(const String& value, uint& v) v |= timeSeriesRenewable; else if (word == "ntc") v |= timeSeriesTransmissionCapacities; + else if (word == "max-power") + v |= timeSeriesHydroMaxPower; return true; }); return true; @@ -392,6 +394,12 @@ static void ParametersSaveTimeSeries(IniFile::Section* s, const char* name, uint v += ", "; v += "ntc"; } + if (value & timeSeriesHydroMaxPower) + { + if (!v.empty()) + v += ", "; + v += "max-power"; + } s->add(name, v); } @@ -1073,6 +1081,8 @@ bool Parameters::loadFromINI(const IniFile& ini, uint version, const StudyLoadOp fixGenRefreshForNTC(); + fixGenRefreshForHydroMaxPower(); + // Specific action before launching a simulation if (options.usedByTheSolver) prepareForSimulation(options); @@ -1131,6 +1141,22 @@ void Parameters::fixGenRefreshForNTC() } } +void Parameters::fixGenRefreshForHydroMaxPower() +{ + if ((timeSeriesHydroMaxPower & timeSeriesToGenerate) != 0) + { + timeSeriesToGenerate &= ~timeSeriesHydroMaxPower; + logs.error() << "Time-series generation is not available for hydro max power. It " + "will be automatically disabled."; + } + if ((timeSeriesHydroMaxPower & timeSeriesToRefresh) != 0) + { + timeSeriesToRefresh &= ~timeSeriesHydroMaxPower; + logs.error() << "Time-series refresh is not available for hydro max power. It will " + "be automatically disabled."; + } +} + void Parameters::fixBadValues() { if (derated) @@ -1375,7 +1401,7 @@ void Parameters::prepareForSimulation(const StudyLoadOptions& options) if (interModal == timeSeriesLoad || interModal == timeSeriesSolar || interModal == timeSeriesWind || interModal == timeSeriesHydro - || interModal == timeSeriesThermal || interModal == timeSeriesRenewable) + || interModal == timeSeriesThermal || interModal == timeSeriesRenewable || interModal == timeSeriesHydroMaxPower) { // Only one timeseries in interModal correlation, which is the same than nothing interModal = 0; diff --git a/src/libs/antares/study/parameters.h b/src/libs/antares/study/parameters.h index 952d534764..0042f224a0 100644 --- a/src/libs/antares/study/parameters.h +++ b/src/libs/antares/study/parameters.h @@ -159,6 +159,12 @@ class Parameters final */ void fixGenRefreshForNTC(); + /*! + ** \brief Try to detect then fix TS generation/refresh parameters + * for Hydro Max Power + */ + void fixGenRefreshForHydroMaxPower(); + /*! ** \brief Get the amount of memory used by the general data */ diff --git a/src/libs/antares/study/parts/hydro/container.cpp b/src/libs/antares/study/parts/hydro/container.cpp index 288ec58a44..7bb56dc9fa 100644 --- a/src/libs/antares/study/parts/hydro/container.cpp +++ b/src/libs/antares/study/parts/hydro/container.cpp @@ -28,6 +28,7 @@ #include "../../study.h" #include "container.h" #include +#include "hydromaxtimeseriesreader.h" using namespace Antares; using namespace Yuni; @@ -91,9 +92,10 @@ void PartHydro::reset() reservoirLevel.fillColumn(average, 0.5); reservoirLevel.fillColumn(maximum, 1.); waterValues.reset(101, DAYS_PER_YEAR, true); - maxPower.reset(4, DAYS_PER_YEAR, true); - maxPower.fillColumn(genMaxE, 24.); - maxPower.fillColumn(pumpMaxE, 24.); + maxDailyGenEnergy.reset(1, DAYS_PER_YEAR, true); + maxDailyGenEnergy.fillColumn(0, 24.); + maxDailyPumpEnergy.reset(1, DAYS_PER_YEAR, true); + maxDailyPumpEnergy.fillColumn(0, 24.); creditModulation.reset(101, 2, true); creditModulation.fill(1); // reset of the hydro allocation - however we don't have any information @@ -114,146 +116,121 @@ bool PartHydro::LoadFromFolder(Study& study, const AnyString& folder) bool ret = true; // Initialize all alpha values to 0 - study.areas.each([&](Data::Area& area) { - area.hydro.interDailyBreakdown = 1.; - area.hydro.intraDailyModulation = 24.; - area.hydro.intermonthlyBreakdown = 1.; - area.hydro.reservoirManagement = false; - area.hydro.followLoadModulations = true; - area.hydro.useWaterValue = false; - area.hydro.hardBoundsOnRuleCurves = false; - area.hydro.useHeuristicTarget = true; - area.hydro.useLeeway = false; - area.hydro.powerToLevel = false; - area.hydro.leewayLowerBound = 1.; - area.hydro.leewayUpperBound = 1.; - area.hydro.initializeReservoirLevelDate = 0; - area.hydro.reservoirCapacity = 0.; - area.hydro.pumpingEfficiency = 1.; - - buffer.clear() << folder << SEP << "common" << SEP << "capacity" << SEP << "maxpower_" - << area.id << '.' << study.inputExtension; - - // GUI part patch : - // We need to know, when estimating the RAM required by the solver, if the current area - // is hydro modulable. Therefore, reading the area's daily max power at this stage is - // necessary. - - if (!study.usedByTheSolver) - { - bool enabledModeIsChanged = false; - if (JIT::enabled) - { - JIT::enabled = false; // Allowing to read the area's daily max power - enabledModeIsChanged = true; - } - - ret = area.hydro.maxPower.loadFromCSVFile( - buffer, 4, DAYS_PER_YEAR, Matrix<>::optFixedSize, &study.dataBuffer) + study.areas.each( + [&](Data::Area& area) + { + area.hydro.interDailyBreakdown = 1.; + area.hydro.intraDailyModulation = 24.; + area.hydro.intermonthlyBreakdown = 1.; + area.hydro.reservoirManagement = false; + area.hydro.followLoadModulations = true; + area.hydro.useWaterValue = false; + area.hydro.hardBoundsOnRuleCurves = false; + area.hydro.useHeuristicTarget = true; + area.hydro.useLeeway = false; + area.hydro.powerToLevel = false; + area.hydro.leewayLowerBound = 1.; + area.hydro.leewayUpperBound = 1.; + area.hydro.initializeReservoirLevelDate = 0; + area.hydro.reservoirCapacity = 0.; + area.hydro.pumpingEfficiency = 1.; + + if (study.header.version >= 870) + { + // GUI part patch : + // We need to know, when estimating the RAM required by the solver, if the current + // area is hydro modulable. Therefore, reading the area's daily max power at this + // stage is necessary. + + if (!study.usedByTheSolver) + { + bool enabledModeIsChanged = false; + if (JIT::enabled) + { + JIT::enabled = false; // Allowing to read the area's daily max power + enabledModeIsChanged = true; + } + + ret = area.hydro.LoadDailyMaxEnergy(folder, area.id) && ret; + + if (enabledModeIsChanged) + JIT::enabled = true; // Back to the previous loading mode. + } + else + { + ret = area.hydro.LoadDailyMaxEnergy(folder, area.id) && ret; + + // Check is moved here, because in case of old study + // maxDailyGenEnergy and maxDailyPumpEnergy are not yet initialized. + + ret = area.hydro.CheckDailyMaxEnergy(area.name) && ret; + } + } + + buffer.clear() << folder << SEP << "common" << SEP << "capacity" << SEP + << "creditmodulations_" << area.id << '.' << study.inputExtension; + ret = area.hydro.creditModulation.loadFromCSVFile( + buffer, 101, 2, Matrix<>::optFixedSize, &study.dataBuffer) && ret; - if (enabledModeIsChanged) - JIT::enabled = true; // Back to the previous loading mode. - } - else - { - ret = area.hydro.maxPower.loadFromCSVFile( - buffer, 4, DAYS_PER_YEAR, Matrix<>::optFixedSize, &study.dataBuffer) + buffer.clear() << folder << SEP << "common" << SEP << "capacity" << SEP << "reservoir_" + << area.id << '.' << study.inputExtension; + ret = area.hydro.reservoirLevel.loadFromCSVFile( + buffer, 3, DAYS_PER_YEAR, Matrix<>::optFixedSize, &study.dataBuffer) && ret; - } - - buffer.clear() << folder << SEP << "common" << SEP << "capacity" << SEP - << "creditmodulations_" << area.id << '.' << study.inputExtension; - ret = area.hydro.creditModulation.loadFromCSVFile( - buffer, 101, 2, Matrix<>::optFixedSize, &study.dataBuffer) - && ret; - buffer.clear() << folder << SEP << "common" << SEP << "capacity" << SEP << "reservoir_" - << area.id << '.' << study.inputExtension; - ret = area.hydro.reservoirLevel.loadFromCSVFile( - buffer, 3, DAYS_PER_YEAR, Matrix<>::optFixedSize, &study.dataBuffer) - && ret; - - - buffer.clear() << folder << SEP << "common" << SEP << "capacity" << SEP - << "waterValues_" << area.id << '.' << study.inputExtension; - ret = area.hydro.waterValues.loadFromCSVFile( - buffer, 101, DAYS_PER_YEAR, Matrix<>::optFixedSize, &study.dataBuffer) - && ret; + buffer.clear() << folder << SEP << "common" << SEP << "capacity" << SEP << "waterValues_" + << area.id << '.' << study.inputExtension; + ret = area.hydro.waterValues.loadFromCSVFile( + buffer, 101, DAYS_PER_YEAR, Matrix<>::optFixedSize, &study.dataBuffer) + && ret; - buffer.clear() << folder << SEP << "common" << SEP << "capacity" << SEP - << "inflowPattern_" << area.id << '.' << study.inputExtension; - ret = area.hydro.inflowPattern.loadFromCSVFile( - buffer, 1, DAYS_PER_YEAR, Matrix<>::optFixedSize, &study.dataBuffer) - && ret; + buffer.clear() << folder << SEP << "common" << SEP << "capacity" << SEP + << "inflowPattern_" << area.id << '.' << study.inputExtension; + ret = area.hydro.inflowPattern.loadFromCSVFile( + buffer, 1, DAYS_PER_YEAR, Matrix<>::optFixedSize, &study.dataBuffer) + && ret; - if (study.usedByTheSolver) - { - auto& col = area.hydro.inflowPattern[0]; - bool errorInflow = false; - for (int day = 0; day < DAYS_PER_YEAR; day++) - { - if (col[day] < 0 && !errorInflow) - { - logs.error() << area.name << ": invalid inflow value"; - errorInflow = true; - ret = false; - } - } - bool errorLevels = false; - auto& colMin = area.hydro.reservoirLevel[minimum]; - auto& colAvg = area.hydro.reservoirLevel[average]; - auto& colMax = area.hydro.reservoirLevel[maximum]; - for (int day = 0; day < DAYS_PER_YEAR; day++) - { - if (!errorLevels - && (colMin[day] < 0 || colAvg[day] < 0 || colMin[day] > colMax[day] - || colAvg[day] > 100 || colMax[day] > 100)) - { - logs.error() << area.name << ": invalid reservoir level value"; - errorLevels = true; - ret = false; - } - } - bool errorPowers = false; - for (int i = 0; i < 4; i++) - { - auto& col = area.hydro.maxPower[i]; - for (int day = 0; day < DAYS_PER_YEAR; day++) - { - if (!errorPowers && (col[day] < 0 || (i % 2 /*column hours*/ && col[day] > 24))) - { - logs.error() << area.name << ": invalid power or energy value"; - errorPowers = true; - ret = false; - } - } - } - for (int i = 0; i < 101; i++) - { - if ((area.hydro.creditModulation[i][0] < 0) - || (area.hydro.creditModulation[i][1] < 0)) - { - logs.error() << area.name << ": invalid credit modulation value"; - ret = false; - } - } - } - else - { - // Is area hydro modulable ? - auto& max = area.hydro.maxPower[area.hydro.genMaxP]; - - for (uint y = 0; y != area.hydro.maxPower.height; ++y) - { - if (max[y] > 0.) - { - area.hydro.hydroModulable = true; - break; - } - } - } - }); + if (study.usedByTheSolver) + { + auto& col = area.hydro.inflowPattern[0]; + bool errorInflow = false; + for (int day = 0; day < DAYS_PER_YEAR; day++) + { + if (col[day] < 0 && !errorInflow) + { + logs.error() << area.name << ": invalid inflow value"; + errorInflow = true; + ret = false; + } + } + bool errorLevels = false; + auto& colMin = area.hydro.reservoirLevel[minimum]; + auto& colAvg = area.hydro.reservoirLevel[average]; + auto& colMax = area.hydro.reservoirLevel[maximum]; + for (int day = 0; day < DAYS_PER_YEAR; day++) + { + if (!errorLevels + && (colMin[day] < 0 || colAvg[day] < 0 || colMin[day] > colMax[day] + || colAvg[day] > 100 || colMax[day] > 100)) + { + logs.error() << area.name << ": invalid reservoir level value"; + errorLevels = true; + ret = false; + } + } + + for (int i = 0; i < 101; i++) + { + if ((area.hydro.creditModulation[i][0] < 0) + || (area.hydro.creditModulation[i][1] < 0)) + { + logs.error() << area.name << ": invalid credit modulation value"; + ret = false; + } + } + } + }); IniFile ini; if (not ini.open(buffer.clear() << folder << SEP << "hydro.ini")) @@ -688,10 +665,15 @@ bool PartHydro::SaveToFolder(const AreaList& areas, const AnyString& folder) sUseLeeway->add(area.id, true); if (area.hydro.powerToLevel) sPowerToLevel->add(area.id, true); - // max power - buffer.clear() << folder << SEP << "common" << SEP << "capacity" << SEP << "maxpower_" + + // max hours gen + buffer.clear() << folder << SEP << "common" << SEP << "capacity" << SEP << "maxDailyGenEnergy_" + << area.id << ".txt"; + ret = area.hydro.maxDailyGenEnergy.saveToCSVFile(buffer, /*decimal*/ 2) && ret; + // max hours pump + buffer.clear() << folder << SEP << "common" << SEP << "capacity" << SEP << "maxDailyPumpEnergy_" << area.id << ".txt"; - ret = area.hydro.maxPower.saveToCSVFile(buffer, /*decimal*/ 2) && ret; + ret = area.hydro.maxDailyPumpEnergy.saveToCSVFile(buffer, /*decimal*/ 2) && ret; // credit modulations buffer.clear() << folder << SEP << "common" << SEP << "capacity" << SEP << "creditmodulations_" << area.id << ".txt"; @@ -717,11 +699,12 @@ bool PartHydro::SaveToFolder(const AreaList& areas, const AnyString& folder) bool PartHydro::forceReload(bool reload) const { bool ret = true; - ret = maxPower.forceReload(reload) && ret; ret = creditModulation.forceReload(reload) && ret; ret = inflowPattern.forceReload(reload) && ret; ret = reservoirLevel.forceReload(reload) && ret; ret = waterValues.forceReload(reload) && ret; + ret = maxDailyGenEnergy.forceReload(reload) && ret; + ret = maxDailyPumpEnergy.forceReload(reload) && ret; if (series) ret = series->forceReload(reload) && ret; @@ -733,11 +716,12 @@ bool PartHydro::forceReload(bool reload) const void PartHydro::markAsModified() const { - maxPower.markAsModified(); inflowPattern.markAsModified(); reservoirLevel.markAsModified(); waterValues.markAsModified(); creditModulation.markAsModified(); + maxDailyGenEnergy.markAsModified(); + maxDailyPumpEnergy.markAsModified(); if (series) series->markAsModified(); @@ -747,12 +731,6 @@ void PartHydro::markAsModified() const void PartHydro::copyFrom(const PartHydro& rhs) { - // max power - { - maxPower = rhs.maxPower; - maxPower.unloadFromMemory(); - rhs.maxPower.unloadFromMemory(); - } // credit modulations { creditModulation = rhs.creditModulation; @@ -795,6 +773,70 @@ void PartHydro::copyFrom(const PartHydro& rhs) leewayLowerBound = rhs.leewayLowerBound; pumpingEfficiency = rhs.pumpingEfficiency; } + + // max daily gen + { + maxDailyGenEnergy = rhs.maxDailyGenEnergy; + maxDailyGenEnergy.unloadFromMemory(); + rhs.maxDailyGenEnergy.unloadFromMemory(); + } + + // max daily pump + { + maxDailyPumpEnergy = rhs.maxDailyPumpEnergy; + maxDailyPumpEnergy.unloadFromMemory(); + rhs.maxDailyPumpEnergy.unloadFromMemory(); + } +} + +bool PartHydro::LoadDailyMaxEnergy(const AnyString& folder, const AnyString& areaid) +{ + YString filePath; + Matrix<>::BufferType fileContent; + bool ret = true; + + filePath.clear() << folder << SEP << "common" << SEP << "capacity" << SEP + << "maxDailyGenEnergy_" << areaid << ".txt"; + + ret = maxDailyGenEnergy.loadFromCSVFile( + filePath, 1, DAYS_PER_YEAR, Matrix<>::optFixedSize, &fileContent) + && ret; + + filePath.clear() << folder << SEP << "common" << SEP << "capacity" << SEP + << "maxDailyPumpEnergy_" << areaid << ".txt"; + + ret = maxDailyPumpEnergy.loadFromCSVFile( + filePath, 1, DAYS_PER_YEAR, Matrix<>::optFixedSize, &fileContent) + && ret; + + return ret; +} + +bool PartHydro::CheckDailyMaxEnergy(const AnyString& areaName) +{ + bool ret = true; + bool errorEnergy = false; + auto& colGen = maxDailyGenEnergy[0]; + auto& colPump = maxDailyPumpEnergy[0]; + + for (int day = 0; day < DAYS_PER_YEAR; day++) + { + if (!errorEnergy && (colGen[day] < 0 || (colGen[day] > 24))) + { + logs.error() << areaName << ": invalid maximum generation energy value"; + errorEnergy = true; + ret = false; + } + + if (!errorEnergy && (colPump[day] < 0 || (colPump[day] > 24))) + { + logs.error() << areaName << ": invalid maximum pumping energy value"; + errorEnergy = true; + ret = false; + } + } + + return ret; } void getWaterValue(const double& level /* format : in % of reservoir capacity */, diff --git a/src/libs/antares/study/parts/hydro/container.h b/src/libs/antares/study/parts/hydro/container.h index 84f9f4cc8f..26171655e3 100644 --- a/src/libs/antares/study/parts/hydro/container.h +++ b/src/libs/antares/study/parts/hydro/container.h @@ -50,18 +50,6 @@ class PartHydro maximum, }; - enum powerDailyE - { - //! Generated max power - genMaxP = 0, - //! Generated max energy - genMaxE, - //! Pumping max Power - pumpMaxP, - // Pumping max Energy - pumpMaxE, - }; - enum weeklyHydroMod { //! Weekly generating modulation @@ -111,6 +99,13 @@ class PartHydro */ void markAsModified() const; + /*! + ** \brief Load daily max energy + */ + bool LoadDailyMaxEnergy(const AnyString& folder, const AnyString& areaid); + + bool CheckDailyMaxEnergy(const AnyString& areaName); + public: //! Inter-daily breakdown (previously called Smoothing Factor or alpha) double interDailyBreakdown; @@ -146,9 +141,6 @@ class PartHydro double leewayUpperBound; //! Puming efficiency double pumpingEfficiency; - //! Daily max power ({generating max Power, generating max energy, pumping max power, pumping - //! max energy}x365) - Matrix maxPower; //! Credit Modulation (default 0, 101 * 2) Matrix creditModulation; @@ -172,8 +164,10 @@ class PartHydro //! Data for time-series DataSeriesHydro* series; -}; // class PartHydro + Matrix maxDailyGenEnergy; + Matrix maxDailyPumpEnergy; +}; // class PartHydro // Interpolates a water value from a table according to a level and a day. // As this function can be called a lot of times, we pass working variables and returned variables diff --git a/src/libs/antares/study/parts/hydro/hydromaxtimeseriesreader.cpp b/src/libs/antares/study/parts/hydro/hydromaxtimeseriesreader.cpp new file mode 100644 index 0000000000..ab2c60f6ae --- /dev/null +++ b/src/libs/antares/study/parts/hydro/hydromaxtimeseriesreader.cpp @@ -0,0 +1,136 @@ +/* +** Copyright 2007-2023 RTE +** Authors: Antares_Simulator Team +** +** This file is part of Antares_Simulator. +** +** Antares_Simulator is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** There are special exceptions to the terms and conditions of the +** license as they are applied to this software. View the full text of +** the exceptions in file COPYING.txt in the directory of this software +** distribution +** +** Antares_Simulator is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Antares_Simulator. If not, see . +** +** SPDX-License-Identifier: licenceRef-GPL3_WITH_RTE-Exceptions +*/ + +#include "../../study.h" +#include "hydromaxtimeseriesreader.h" +#include + +using namespace Yuni; + +#define SEP IO::Separator + +namespace Antares::Data +{ + +HydroMaxTimeSeriesReader::HydroMaxTimeSeriesReader() +{ + dailyMaxPumpAndGen.reset(4U, DAYS_PER_YEAR, true); +} + +bool HydroMaxTimeSeriesReader::loadDailyMaxPowersAndEnergies(const AnyString& folder, + const Area& area, + bool usedBySolver) +{ + YString filePath; + Matrix<>::BufferType fileContent; + bool ret = true; + + filePath.clear() << folder << SEP << "common" << SEP << "capacity" << SEP << "maxpower_" + << area.id << ".txt"; + + // It is necessary to load maxpower_ txt file, whether loading is called from old GUI + // or from solver. + + if (!usedBySolver) + { + bool enabledModeIsChanged = false; + + if (JIT::enabled) + { + JIT::enabled = false; // Allowing to read the area's daily max power and energy + enabledModeIsChanged = true; + } + + ret = dailyMaxPumpAndGen.loadFromCSVFile( + filePath, 4U, DAYS_PER_YEAR, Matrix<>::optFixedSize, &fileContent) + && ret; + + if (enabledModeIsChanged) + JIT::enabled = true; // Back to the previous loading mode. + } + else + { + ret = dailyMaxPumpAndGen.loadFromCSVFile( + filePath, 4U, DAYS_PER_YEAR, Matrix<>::optFixedSize, &fileContent) + && ret; + + bool errorPowers = false; + for (uint i = 0; i < 4U; ++i) + { + auto& col = dailyMaxPumpAndGen[i]; + for (uint day = 0; day < DAYS_PER_YEAR; ++day) + { + if (!errorPowers && (col[day] < 0. || (i % 2U /*column hours*/ && col[day] > 24.))) + { + logs.error() << area.name << ": invalid power or energy value"; + errorPowers = true; + ret = false; + } + } + } + } + return ret; +} + +void HydroMaxTimeSeriesReader::copyDailyMaxEnergy(Area& area) const +{ + copyDailyMaxGenerationEnergy(area); + copyDailyMaxPumpingEnergy(area); +} + +void HydroMaxTimeSeriesReader::copyDailyMaxGenerationEnergy(Area& area) const +{ + auto& maxDailyGenEnergy = area.hydro.maxDailyGenEnergy; + const auto& dailyMaxGenE = dailyMaxPumpAndGen[genMaxE]; + + maxDailyGenEnergy.reset(1U, DAYS_PER_YEAR, true); + + maxDailyGenEnergy.pasteToColumn(0, dailyMaxGenE); +} + +void HydroMaxTimeSeriesReader::copyDailyMaxPumpingEnergy(Area& area) const +{ + auto& maxDailyPumpEnergy = area.hydro.maxDailyPumpEnergy; + const auto& dailyMaxPumpE = dailyMaxPumpAndGen[pumpMaxE]; + + maxDailyPumpEnergy.reset(1U, DAYS_PER_YEAR, true); + + maxDailyPumpEnergy.pasteToColumn(0, dailyMaxPumpE); +} + +bool HydroMaxTimeSeriesReader::operator()(const AnyString& folder, Area& area, bool usedBySolver) +{ + bool ret = true; + + ret = loadDailyMaxPowersAndEnergies(folder, area, usedBySolver) && ret; + copyDailyMaxEnergy(area); + area.hydro.series->buildMaxPowerFromDailyTS(dailyMaxPumpAndGen[genMaxP], dailyMaxPumpAndGen[pumpMaxP]); + + return ret; +} + +} // namespace Antares::Data \ No newline at end of file diff --git a/src/libs/antares/study/parts/hydro/hydromaxtimeseriesreader.h b/src/libs/antares/study/parts/hydro/hydromaxtimeseriesreader.h new file mode 100644 index 0000000000..fefb4ef239 --- /dev/null +++ b/src/libs/antares/study/parts/hydro/hydromaxtimeseriesreader.h @@ -0,0 +1,83 @@ +/* +** Copyright 2007-2023 RTE +** Authors: Antares_Simulator Team +** +** This file is part of Antares_Simulator. +** +** Antares_Simulator is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** There are special exceptions to the terms and conditions of the +** license as they are applied to this software. View the full text of +** the exceptions in file COPYING.txt in the directory of this software +** distribution +** +** Antares_Simulator is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Antares_Simulator. If not, see . +** +** SPDX-License-Identifier: licenceRef-GPL3_WITH_RTE-Exceptions +*/ + +#ifndef __ANTARES_LIBS_STUDY_PARTS_HYDRO_MAX_TIME_SERIES_READER_H__ +#define __ANTARES_LIBS_STUDY_PARTS_HYDRO_MAX_TIME_SERIES_READER_H__ + +#include "../../../array/antares//array/matrix.h" + +namespace Antares::Data +{ +/*! +** This class provides support for old studies, reading from deprecated files, +** fils matrix dailyMaxPumpAndGen and transfers data to the corresponding data +** class members of class PartHydro. Just versions below 8.7 will use instance +** of this class to be compatible with current implementation. +*/ +class HydroMaxTimeSeriesReader +{ +public: + HydroMaxTimeSeriesReader(); + + bool operator()(const AnyString& folder, Area& area, bool usedBySolver); + + Matrix dailyMaxPumpAndGen; + + enum powerDailyE + { + //! Generated max power + genMaxP = 0, + //! Generated max energy + genMaxE, + //! Pumping max Power + pumpMaxP, + // Pumping max Energy + pumpMaxE, + }; + +private: + /** + * \brief Loading deprecated files + * This function provides reading from deprecated files which + * contains daily maximum generation/pumping power and energy data. + */ + bool loadDailyMaxPowersAndEnergies(const AnyString& folder, + const Area& area, + bool usedBySolver); + + /** + * \brief Copy energy functions + * These functions provides coping of energy data loaded + * from deprecated file. + */ + void copyDailyMaxEnergy(Area& area) const; + void copyDailyMaxGenerationEnergy(Area& area) const; + void copyDailyMaxPumpingEnergy(Area& area) const; +}; +} // namespace Antares::Data + +#endif /*__ANTARES_LIBS_STUDY_PARTS_HYDRO_MAX_TIME_SERIES_READER_H__*/ \ No newline at end of file diff --git a/src/libs/antares/study/parts/hydro/pair-of-integers.cpp b/src/libs/antares/study/parts/hydro/pair-of-integers.cpp new file mode 100644 index 0000000000..1332469837 --- /dev/null +++ b/src/libs/antares/study/parts/hydro/pair-of-integers.cpp @@ -0,0 +1,25 @@ +#include "pair-of-integers.h" +#include + + +PairOfIntegers::PairOfIntegers(unsigned int numberOfTS_1, unsigned int numberOfTS_2) + : numberOfTS_1_(numberOfTS_1), numberOfTS_2_(numberOfTS_2) +{ + numberOfTSsup_ = std::max(numberOfTS_1_, numberOfTS_2_); + numberOfTSinf_ = std::min(numberOfTS_1_, numberOfTS_2_); +} + +bool PairOfIntegers::bothZero() const +{ + return numberOfTSsup_ == 0; +} + +bool PairOfIntegers::same() const +{ + return (numberOfTS_1_ == numberOfTS_2_) ? true : false; +} + +bool PairOfIntegers::bothGreaterThanOne() const +{ + return (numberOfTSinf_ > 1); +} \ No newline at end of file diff --git a/src/libs/antares/study/parts/hydro/pair-of-integers.h b/src/libs/antares/study/parts/hydro/pair-of-integers.h new file mode 100644 index 0000000000..3c3ca0e839 --- /dev/null +++ b/src/libs/antares/study/parts/hydro/pair-of-integers.h @@ -0,0 +1,18 @@ +#pragma once + +class PairOfIntegers +{ +public: + PairOfIntegers(unsigned int numberOfTS_1, unsigned int numberOfTS_2); + + bool bothZero() const; + bool same() const; + bool bothGreaterThanOne() const; + unsigned int sup() const { return numberOfTSsup_; } + +private: + unsigned int numberOfTS_1_; + unsigned int numberOfTS_2_; + unsigned int numberOfTSsup_{ 0 }; + unsigned int numberOfTSinf_{ 0 }; +}; diff --git a/src/libs/antares/study/parts/hydro/series.cpp b/src/libs/antares/study/parts/hydro/series.cpp index 3cb60ba865..c94957f91d 100644 --- a/src/libs/antares/study/parts/hydro/series.cpp +++ b/src/libs/antares/study/parts/hydro/series.cpp @@ -32,6 +32,8 @@ #include #include #include "../../study.h" +#include "pair-of-integers.h" +#include using namespace Yuni; @@ -41,13 +43,78 @@ namespace Antares { namespace Data { -DataSeriesHydro::DataSeriesHydro() : count(0) +DataSeriesHydro::DataSeriesHydro() { // Pmin was introduced in v8.6 // The previous behavior was Pmin=0 - // For compatibility reasons with existing studies, mingen is set to one column of zeros - // by default + // For compatibility reasons with existing studies, mingen, maxHourlyGenPower and maxHourlyPumpPower are set to one + // column of zeros by default mingen.reset(1, HOURS_PER_YEAR); + maxHourlyGenPower.reset(1, HOURS_PER_YEAR); + maxHourlyPumpPower.reset(1, HOURS_PER_YEAR); +} + +unsigned int EqualizeTSsize(Matrix& TScollection1, + Matrix& TScollection2, + bool& fatalError, + const std::string& fatalErrorMsg, + Area& area, + unsigned int height1 = HOURS_PER_YEAR, + unsigned int height2 = HOURS_PER_YEAR) +{ + PairOfIntegers pairOfTSsizes(TScollection1.width, TScollection2.width); + + if (pairOfTSsizes.bothZero()) + { + TScollection1.reset(1, height1); + TScollection2.reset(1, height2); + return 1; + } + + if (pairOfTSsizes.same()) + return pairOfTSsizes.sup(); + + if (pairOfTSsizes.bothGreaterThanOne()) + { + logs.fatal() << fatalErrorMsg; + fatalError = true; + return 0; + } + + // At this point, one TS collection size is > 1 and the other is of size 1. + + // This following instruction to force reloading all area's TS when saving the study (GUI) + area.invalidateJIT = true; + + if (TScollection1.width == 1) + resizeMatrixNoDataLoss(TScollection1, pairOfTSsizes.sup()); + if (TScollection2.width == 1) + resizeMatrixNoDataLoss(TScollection2, pairOfTSsizes.sup()); + + return pairOfTSsizes.sup(); +} + +void DataSeriesHydro::copyGenerationTS(const DataSeriesHydro& source) +{ + ror = source.ror; + storage = source.storage; + mingen = source.mingen; + + generationTScount_ = source.generationTScount_; + + source.ror.unloadFromMemory(); + source.storage.unloadFromMemory(); + source.mingen.unloadFromMemory(); +} +void DataSeriesHydro::copyMaxPowerTS(const DataSeriesHydro& source) +{ + maxHourlyGenPower = source.maxHourlyGenPower; + maxHourlyPumpPower = source.maxHourlyPumpPower; + + maxPowerTScount_ = source.maxPowerTScount_; + + source.maxHourlyGenPower.unloadFromMemory(); + source.maxHourlyPumpPower.unloadFromMemory(); } bool DataSeriesHydro::saveToFolder(const AreaName& areaID, const AnyString& folder) const @@ -66,139 +133,117 @@ bool DataSeriesHydro::saveToFolder(const AreaName& areaID, const AnyString& fold ret = storage.saveToCSVFile(buffer, 0) && ret; buffer.clear() << folder << SEP << areaID << SEP << "mingen.txt"; ret = mingen.saveToCSVFile(buffer, 0) && ret; + buffer.clear() << folder << SEP << areaID << SEP << "maxHourlyGenPower.txt"; + ret = maxHourlyGenPower.saveToCSVFile(buffer, 0) && ret; + buffer.clear() << folder << SEP << areaID << SEP << "maxHourlyPumpPower.txt"; + ret = maxHourlyPumpPower.saveToCSVFile(buffer, 0) && ret; return ret; } return false; } -bool DataSeriesHydro::loadFromFolder(Study& study, const AreaName& areaID, const AnyString& folder) + +bool loadTSfromFile(Matrix& ts, + const AreaName& areaID, + const AnyString& folder, + const std::string& filename, + unsigned int height) { - bool ret = true; - auto& buffer = study.bufferLoadingTS; + YString filePath; + Matrix<>::BufferType fileContent; + filePath.clear() << folder << SEP << areaID << SEP << filename; + return ts.loadFromCSVFile(filePath, 1, height, &fileContent); +} - buffer.clear() << folder << SEP << areaID << SEP << "ror." << study.inputExtension; +bool DataSeriesHydro::loadGenerationTS(const AreaName& areaID, const AnyString& folder, unsigned int studyVersion) +{ + timeseriesNumbers.clear(); - ret = ror.loadFromCSVFile(buffer, 1, HOURS_PER_YEAR, &study.dataBuffer) && ret; + bool ret = loadTSfromFile(ror, areaID, folder, "ror.txt", HOURS_PER_YEAR); + ret = loadTSfromFile(storage, areaID, folder, "mod.txt", DAYS_PER_YEAR) && ret; + if (studyVersion >= 860) + ret = loadTSfromFile(mingen, areaID, folder, "mingen.txt", HOURS_PER_YEAR) && ret; + return ret; +} - buffer.clear() << folder << SEP << areaID << SEP << "mod." << study.inputExtension; - ret = storage.loadFromCSVFile(buffer, 1, DAYS_PER_YEAR, &study.dataBuffer) && ret; +void DataSeriesHydro::EqualizeGenerationTSsizes(Area& area, bool usedByTheSolver, bool& fatalError) +{ + if (!usedByTheSolver) // From GUI, no need to equalize TS collections sizes + return; - // The number of time-series - count = storage.width; + // Equalize ROR and INFLOWS time series sizes + // ------------------------------------------ + std::string fatalErrorMsg = "Hydro : area `" + area.id.to() + "` : "; + fatalErrorMsg += "ROR and INFLOWS must have the same number of time series."; - if (ror.width > count) - count = ror.width; + generationTScount_ = EqualizeTSsize(ror, storage, fatalError, fatalErrorMsg, area, HOURS_PER_YEAR, DAYS_PER_YEAR); - if (study.header.version >= 860) - { - buffer.clear() << folder << SEP << areaID << SEP << "mingen." << study.inputExtension; - ret = mingen.loadFromCSVFile(buffer, 1, HOURS_PER_YEAR, &study.dataBuffer) && ret; - } + logs.info() << " '" << area.id << "': ROR and INFLOWS time series were both set to : " << generationTScount_; - if (study.usedByTheSolver) - { - if (0 == count) - { - logs.error() << "Hydro: `" << areaID - << "`: empty matrix detected. Fixing it with default values"; - ror.reset(1, HOURS_PER_YEAR); - storage.reset(1, DAYS_PER_YEAR); - mingen.reset(1, HOURS_PER_YEAR); - } - else - { - if (count > 1 && storage.width != ror.width) - { - if (ror.width != 1 && storage.width != 1) - { - logs.fatal() << "Hydro: `" << areaID - << "`: The matrices ROR (run-of-the-river) and hydro-storage must " - "have the same number of time-series."; - study.gotFatalError = true; - } - else - { - if (ror.width == 1) - { - ror.resizeWithoutDataLost(count, ror.height); - for (uint x = 1; x < count; ++x) - ror.pasteToColumn(x, ror[0]); - } - else - { - if (storage.width == 1) - { - storage.resizeWithoutDataLost(count, storage.height); - for (uint x = 1; x < count; ++x) - storage.pasteToColumn(x, storage[0]); - } - } - Area* areaToInvalidate = study.areas.find(areaID); - if (areaToInvalidate) - { - areaToInvalidate->invalidateJIT = true; - logs.info() - << " '" << areaID << "': The hydro data have been normalized to " - << count << " timeseries"; - } - else - logs.error() - << "Impossible to find the area `" << areaID << "` to invalidate it"; - } - } - checkMinGenTsNumber(study, areaID); - } + // Equalize ROR and MINGEN time series sizes + // ----------------------------------------- + fatalErrorMsg = "Hydro : area `" + area.id.to() + "` : "; + fatalErrorMsg += "ROR and MINGEN must have the same number of time series."; - if (study.parameters.derated) - { - ror.averageTimeseries(); - storage.averageTimeseries(); - mingen.averageTimeseries(); - count = 1; - } - } + generationTScount_ = EqualizeTSsize(ror, mingen, fatalError, fatalErrorMsg, area); - timeseriesNumbers.clear(); + logs.info() << " '" << area.id << "': ROR and MINGEN time series were both set to : " << generationTScount_; +} + +bool DataSeriesHydro::LoadMaxPower(const AreaName& areaID, const AnyString& folder) +{ + bool ret = true; + YString filepath; + Matrix<>::BufferType fileContent; + + filepath.clear() << folder << SEP << areaID << SEP << "maxHourlyGenPower.txt"; + ret = maxHourlyGenPower.loadFromCSVFile(filepath, 1, HOURS_PER_YEAR, &fileContent) && ret; + + filepath.clear() << folder << SEP << areaID << SEP << "maxHourlyPumpPower.txt"; + ret = maxHourlyPumpPower.loadFromCSVFile(filepath, 1, HOURS_PER_YEAR, &fileContent) && ret; + + timeseriesNumbersHydroMaxPower.clear(); return ret; } -void DataSeriesHydro::checkMinGenTsNumber(Study& study, const AreaName& areaID) +void ConvertDailyTSintoHourlyTS(const Matrix::ColumnType& dailyColumn, + Matrix::ColumnType& hourlyColumn) { - if (mingen.width != storage.width) + uint hour = 0; + uint day = 0; + + while (hour < HOURS_PER_YEAR && day < DAYS_PER_YEAR) { - if (mingen.width > 1) + for (uint i = 0; i < HOURS_PER_DAY; ++i) { - logs.fatal() << "Hydro: `" << areaID - << "`: The matrices Minimum Generation must " - "has the same number of time-series as ROR and hydro-storage."; - study.gotFatalError = true; - } - else - { - mingen.resizeWithoutDataLost(count, mingen.height); - for (uint x = 1; x < count; ++x) - mingen.pasteToColumn(x, mingen[0]); - Area* areaToInvalidate = study.areas.find(areaID); - if (areaToInvalidate) - { - areaToInvalidate->invalidateJIT = true; - logs.info() << " '" << areaID - << "': The hydro minimum generation data have been normalized to " - << count << " timeseries"; - } - else - logs.error() << "Impossible to find the area `" << areaID << "` to invalidate it"; + hourlyColumn[hour] = dailyColumn[day]; + ++hour; } + ++day; } } +void DataSeriesHydro::buildMaxPowerFromDailyTS(const Matrix::ColumnType& DailyMaxGenPower, + const Matrix::ColumnType& DailyMaxPumpPower) +{ + maxPowerTScount_ = 1; + + maxHourlyGenPower.reset(maxPowerTScount_, HOURS_PER_YEAR); + maxHourlyPumpPower.reset(maxPowerTScount_, HOURS_PER_YEAR); + + ConvertDailyTSintoHourlyTS(DailyMaxGenPower, maxHourlyGenPower[0]); + ConvertDailyTSintoHourlyTS(DailyMaxPumpPower, maxHourlyPumpPower[0]); +} + bool DataSeriesHydro::forceReload(bool reload) const { bool ret = true; ret = ror.forceReload(reload) && ret; ret = storage.forceReload(reload) && ret; ret = mingen.forceReload(reload) && ret; + ret = maxHourlyGenPower.forceReload(reload) && ret; + ret = maxHourlyPumpPower.forceReload(reload) && ret; return ret; } @@ -207,6 +252,8 @@ void DataSeriesHydro::markAsModified() const ror.markAsModified(); storage.markAsModified(); mingen.markAsModified(); + maxHourlyGenPower.markAsModified(); + maxHourlyPumpPower.markAsModified(); } void DataSeriesHydro::reset() @@ -214,12 +261,98 @@ void DataSeriesHydro::reset() ror.reset(1, HOURS_PER_YEAR); storage.reset(1, DAYS_PER_YEAR); mingen.reset(1, HOURS_PER_YEAR); - count = 1; + generationTScount_ = 1; + + maxHourlyGenPower.reset(1, HOURS_PER_YEAR); + maxHourlyPumpPower.reset(1, HOURS_PER_YEAR); + maxPowerTScount_ = 1; +} + +void DataSeriesHydro::resizeRORandSTORAGE(unsigned int width) +{ + ror.resize(width, HOURS_PER_YEAR); + storage.resize(width, DAYS_PER_YEAR); + generationTScount_ = width; +} + +void DataSeriesHydro::resizeGenerationTS(unsigned int w, unsigned int h) +{ + ror.resize(w, h); + storage.resize(w, std::min((unsigned int)DAYS_PER_YEAR, h)); + mingen.resize(w, h); + generationTScount_ = w; +} + +void DataSeriesHydro::resizeMaxPowerTS(unsigned int w, unsigned int h) +{ + maxHourlyGenPower.reset(w, h); + maxHourlyPumpPower.reset(w, h); + maxPowerTScount_ = w; } uint64_t DataSeriesHydro::memoryUsage() const { - return sizeof(double) + ror.memoryUsage() + storage.memoryUsage() + mingen.memoryUsage(); + return sizeof(double) + ror.memoryUsage() + storage.memoryUsage() + mingen.memoryUsage() + + maxHourlyGenPower.memoryUsage() + maxHourlyPumpPower.memoryUsage(); +} + +void DataSeriesHydro::EqualizeMaxPowerTSsizes(Area& area, bool& fatalError) +{ + std::string fatalErrorMsg = "Hydro Max Power: " + area.id.to() + " : "; + fatalErrorMsg += "generation and pumping must have the same number of TS."; + + maxPowerTScount_ = EqualizeTSsize(maxHourlyGenPower, maxHourlyPumpPower, fatalError, fatalErrorMsg, area); + + logs.info() << " '" << area.id << "': The number of hydro max power (generation and pumping) " + << "TS were both set to : " << maxPowerTScount_; +} + +void DataSeriesHydro::setHydroModulability(Area& area) const +{ + if (MatrixTestForAtLeastOnePositiveValue(maxHourlyGenPower)) + { + area.hydro.hydroModulable = true; + } +} + +uint DataSeriesHydro::TScount() const +{ + return generationTScount_; +} + +uint DataSeriesHydro::maxPowerTScount() const +{ + return maxPowerTScount_; +} + +void DataSeriesHydro::resizeTSinDeratedMode(bool derated, + unsigned int studyVersion, + bool usedBySolver) +{ + if (!(derated && usedBySolver)) + return; + + ror.averageTimeseries(); + storage.averageTimeseries(); + if (studyVersion >= 860) + mingen.averageTimeseries(); + generationTScount_ = 1; + + if (studyVersion >= 870) + { + maxHourlyGenPower.averageTimeseries(); + maxHourlyPumpPower.averageTimeseries(); + maxPowerTScount_ = 1; + } +} + +// TODO : this function should not be here, as it applies to +// any time series, not just hydro TS. +void resizeMatrixNoDataLoss(Matrix& matrixToResize, uint width) +{ + matrixToResize.resizeWithoutDataLost(width, matrixToResize.height); + for (uint x = 1; x < width; ++x) + matrixToResize.pasteToColumn(x, matrixToResize[0]); } } // namespace Data diff --git a/src/libs/antares/study/parts/hydro/series.h b/src/libs/antares/study/parts/hydro/series.h index 5a50ce7d5c..04ffa79550 100644 --- a/src/libs/antares/study/parts/hydro/series.h +++ b/src/libs/antares/study/parts/hydro/series.h @@ -29,11 +29,14 @@ #include #include "../../fwd.h" +#include + namespace Antares { namespace Data { + /*! ** \brief Data series (Hydro) */ @@ -48,6 +51,9 @@ class DataSeriesHydro DataSeriesHydro(); //@} + void copyGenerationTS(const DataSeriesHydro& source); + void copyMaxPowerTS(const DataSeriesHydro& source); + //! \name Data //@{ /*! @@ -55,6 +61,10 @@ class DataSeriesHydro */ void reset(); + void resizeRORandSTORAGE(unsigned int width); + void resizeGenerationTS(unsigned int w, unsigned int h); + void resizeMaxPowerTS(unsigned int w, unsigned int h); + /*! ** \brief Load all data not already loaded ** @@ -65,16 +75,17 @@ class DataSeriesHydro void markAsModified() const; //@} - //! \name Save / Load - //@{ - /*! - ** \brief Load data series for hydro from a folder - ** - ** \param d The data series for hydro - ** \param folder The source folder - ** \return A non-zero value if the operation succeeded, 0 otherwise - */ - bool loadFromFolder(Study& s, const AreaName& areaID, const AnyString& folder); + void EqualizeGenerationTSsizes(Area& area, bool usedByTheSolver, bool& fatalError); + + // Loading hydro time series collection + // Returned boolean : reading from file failed + bool loadGenerationTS(const AreaName& areaID, const AnyString& folder, unsigned int studyVersion); + + // Loading hydro max generation and mqx pumping TS's + bool LoadMaxPower(const AreaName& areaID, const AnyString& folder); + + void buildMaxPowerFromDailyTS(const Matrix::ColumnType& DailyMaxGenPower, + const Matrix::ColumnType& DailyMaxPumpPower); /*! ** \brief Save data series for hydro into a folder (`input/hydro/series`) @@ -100,12 +111,6 @@ class DataSeriesHydro //@} - /*! - ** \brief Check TS number for Minimum Generation and logs error if necessary - */ - void checkMinGenTsNumber(Study& s, const AreaName& areaID); - -public: /*! ** \brief Run-of-the-river - ROR (MW) ** @@ -130,22 +135,52 @@ class DataSeriesHydro Matrix mingen; /*! - ** \brief The number of time-series + ** \brief Maximum Generation (MW) ** - ** This value must be the same as the width of the matrices `mod` and `fatal`. - ** It is only provided for convenience to avoid same strange and ambiguous code - ** (for example using `fatal.width` and `mod.width` in the same routine, it might - ** indicate that the two values are not strictly equal) + ** Merely a matrix of TimeSeriesCount * HOURS_PER_YEAR values */ - uint count; + Matrix maxHourlyGenPower; /*! - ** \brief Monte-Carlo + ** \brief Maximum Pumping (MW) + ** + ** Merely a matrix of TimeSeriesCount * HOURS_PER_YEAR values */ + Matrix maxHourlyPumpPower; + + // TS's number matrices for Generation and Maximum Power Matrix timeseriesNumbers; + Matrix timeseriesNumbersHydroMaxPower; + + // Equalizing max generation and max pumping numbers of TS's + void EqualizeMaxPowerTSsizes(Area& area, bool& fatalError); + + void setHydroModulability(Area& area) const; + + // Getters for generation (ror, storage and mingen) and + // max power (generation and pumping) number of TS + uint TScount() const; + uint maxPowerTScount() const; + + // Setting TS's when derated mode is on + void resizeTSinDeratedMode(bool derated, unsigned int studyVersion, bool useBySolver); + +private: + + // The number of time-series about generation (ror, inflows (=storage), mingen) + // They all should have the same number of columns (width), as they each year receives a common + // TS number for all three. + uint generationTScount_ = 0; + + // The number of time-series about max power (maxHourlyGenPower and maxHourlyPumpPower) + // They both should have the same number of columns (width), as they each year receives a common + // TS number for all three. + uint maxPowerTScount_ = 0; }; // class DataSeriesHydro +void resizeMatrixNoDataLoss(Matrix& matrixToResize, uint width); + } // namespace Data } // namespace Antares diff --git a/src/libs/antares/study/parts/parts.h b/src/libs/antares/study/parts/parts.h index d59933f179..01d654821f 100644 --- a/src/libs/antares/study/parts/parts.h +++ b/src/libs/antares/study/parts/parts.h @@ -40,6 +40,7 @@ #include "hydro/prepro.h" #include "hydro/series.h" #include "hydro/container.h" +#include "hydro/hydromaxtimeseriesreader.h" // Wind #include "wind/prepro.h" diff --git a/src/libs/antares/study/scenario-builder/HydroMaxPowerTSNumberData.cpp b/src/libs/antares/study/scenario-builder/HydroMaxPowerTSNumberData.cpp new file mode 100644 index 0000000000..3bfb7e50d5 --- /dev/null +++ b/src/libs/antares/study/scenario-builder/HydroMaxPowerTSNumberData.cpp @@ -0,0 +1,43 @@ + + +#include "HydroMaxPowerTSNumberData.h" +#include "applyToMatrix.hxx" + +// ================================ +// Hydro Max Power... +// ================================ + +namespace Antares::Data::ScenarioBuilder +{ +uint hydroMaxPowerTSNumberData::get_tsGenCount(const Study& study) const +{ + //This function must be overriden because it is inherited from abstract class + return 0; +} + +bool hydroMaxPowerTSNumberData::apply(Study& study) +{ + bool ret = true; + CString<512, false> logprefix; + // Errors + uint errors = 0; + + // The total number of areas; + const uint areaCount = study.areas.size(); + + const uint tsGenCountHydroMaxPower = get_tsGenCount(study); + + for (uint areaIndex = 0; areaIndex != areaCount; ++areaIndex) + { + // Alias to the current area + Area& area = *(study.areas.byIndex[areaIndex]); + // alias to the current column + assert(areaIndex < pTSNumberRules.width); + const MatrixType::ColumnType& col = pTSNumberRules[areaIndex]; + + logprefix.clear() << "Hydro Max Power: Area '" << area.name << "': "; + ret = ApplyToMatrixMaxPower(errors, logprefix, *area.hydro.series, col, tsGenCountHydroMaxPower) && ret; + } + return ret; +} +} \ No newline at end of file diff --git a/src/libs/antares/study/scenario-builder/HydroMaxPowerTSNumberData.h b/src/libs/antares/study/scenario-builder/HydroMaxPowerTSNumberData.h new file mode 100644 index 0000000000..0cdc741693 --- /dev/null +++ b/src/libs/antares/study/scenario-builder/HydroMaxPowerTSNumberData.h @@ -0,0 +1,25 @@ +#pragma once + +#include "TSnumberData.h" + +// ===================== +// Hydro Max Power... +// ===================== + +namespace Antares::Data::ScenarioBuilder +{ +class hydroMaxPowerTSNumberData : public TSNumberData +{ +public: + bool apply(Study& study) override; + CString<512, false> get_prefix() const override; + uint get_tsGenCount(const Study& study) const override; + + virtual ~hydroMaxPowerTSNumberData() = default; +}; + +inline CString<512, false> hydroMaxPowerTSNumberData::get_prefix() const +{ + return "hgp,"; +} +} \ No newline at end of file diff --git a/src/libs/antares/study/scenario-builder/TSnumberData.cpp b/src/libs/antares/study/scenario-builder/TSnumberData.cpp index 6f72b264b1..15554b93c1 100644 --- a/src/libs/antares/study/scenario-builder/TSnumberData.cpp +++ b/src/libs/antares/study/scenario-builder/TSnumberData.cpp @@ -77,4 +77,5 @@ void TSNumberData::set_value(uint x, uint y, uint value) { pTSNumberRules.entry[y][x] = value; } -} // namespace Antares + +} // namespace Antares \ No newline at end of file diff --git a/src/libs/antares/study/scenario-builder/applyToMatrix.hxx b/src/libs/antares/study/scenario-builder/applyToMatrix.hxx index c2990af51d..ac79bd9716 100644 --- a/src/libs/antares/study/scenario-builder/applyToMatrix.hxx +++ b/src/libs/antares/study/scenario-builder/applyToMatrix.hxx @@ -25,7 +25,7 @@ inline bool CheckValidity(uint value, uint tsGenMax) { // When the TS-Generators are not used - return (!tsGenMax) ? (value < data.count) : (value < tsGenMax); + return (!tsGenMax) ? (value < data.TScount()) : (value < tsGenMax); } template<> @@ -45,6 +45,13 @@ inline bool CheckValidity(uint value, const BindingConst return value < group.numberOfTimeseries(); } +template +static inline bool CheckValidityHydroMaxPower(uint value, const D& data, uint tsGenMax) +{ + // TS Generator never used + return (!tsGenMax) ? (value < data.maxPowerTScount()) : (value < tsGenMax); +} + template bool ApplyToMatrix(uint& errors, StringT& logprefix, @@ -89,4 +96,48 @@ bool ApplyToMatrix(uint& errors, return ret; } + +template +bool ApplyToMatrixMaxPower(uint& errors, + StringT& logprefix, + D& data, + const TSNumberData::MatrixType::ColumnType& years, uint tsGenMax) +{ + bool ret = true; + + // In this case, m.height represents the total number of years + const uint nbYears = data.timeseriesNumbersHydroMaxPower.height; + // The matrix m has only one column + assert(data.timeseriesNumbersHydroMaxPower.width == 1); + typename Matrix::ColumnType& target = data.timeseriesNumbersHydroMaxPower[0]; + + for (uint y = 0; y != nbYears; ++y) + { + if (years[y] != 0) + { + // The new TS number + uint tsNum = years[y] - 1; + + // When the TS-Generators are not used + if (!CheckValidityHydroMaxPower(tsNum, data, tsGenMax)) + { + if (errors <= maxErrors) + { + if (++errors == maxErrors) + logs.warning() << "scenario-builder: ... (skipped)"; + else + logs.warning() << "scenario-builder: " << logprefix + << "value out of bounds for the year " << (y + 1); + } + ret = false; + continue; + } + // Ok, assign. The value provided by the interface is user-friendly + // and starts from 1. + target[y] = tsNum; + } + } + + return ret; +} } diff --git a/src/libs/antares/study/scenario-builder/rules.cpp b/src/libs/antares/study/scenario-builder/rules.cpp index e814468a9a..191e92016b 100644 --- a/src/libs/antares/study/scenario-builder/rules.cpp +++ b/src/libs/antares/study/scenario-builder/rules.cpp @@ -55,6 +55,8 @@ void Rules::saveToINIFile(Yuni::IO::File::Stream& file) const solar.saveToINIFile(study_, file); // hydro hydro.saveToINIFile(study_, file); + // hydroMaxPower + hydroMaxPower.saveToINIFile(study_, file); // wind wind.saveToINIFile(study_, file); // Thermal clusters, renewable clusters, links NTS : each area @@ -79,6 +81,7 @@ bool Rules::reset() load.reset(study_); solar.reset(study_); hydro.reset(study_); + hydroMaxPower.reset(study_); wind.reset(study_); // Thermal @@ -243,6 +246,20 @@ bool Rules::readHydro(const AreaName::Vector& splitKey, String value, bool updat return true; } +bool Rules::readHydroMaxPower(const AreaName::Vector& splitKey, String value, bool updaterMode) +{ + const uint year = splitKey[2].to(); + const AreaName& areaname = splitKey[1]; + + const Data::Area* area = getArea(areaname, updaterMode); + if (!area) + return false; + + uint val = fromStringToTSnumber(value); + hydroMaxPower.setTSnumber(area->index, year, val); + return true; +} + bool Rules::readSolar(const AreaName::Vector& splitKey, String value, bool updaterMode) { const uint year = splitKey[2].to(); @@ -351,6 +368,8 @@ bool Rules::readLine(const AreaName::Vector& splitKey, String value, bool update return readWind(splitKey, value, updaterMode); else if (kind_of_scenario == "h") return readHydro(splitKey, value, updaterMode); + else if (kind_of_scenario == "hgp") + return readHydroMaxPower(splitKey, value, updaterMode); else if (kind_of_scenario == "s") return readSolar(splitKey, value, updaterMode); else if (kind_of_scenario == "hl") @@ -370,6 +389,7 @@ bool Rules::apply() returned_status = load.apply(study_) && returned_status; returned_status = solar.apply(study_) && returned_status; returned_status = hydro.apply(study_) && returned_status; + returned_status = hydroMaxPower.apply(study_) && returned_status; returned_status = wind.apply(study_) && returned_status; for (uint i = 0; i != pAreaCount; ++i) { diff --git a/src/libs/antares/study/scenario-builder/rules.h b/src/libs/antares/study/scenario-builder/rules.h index 60039c14be..ebfe5e534f 100644 --- a/src/libs/antares/study/scenario-builder/rules.h +++ b/src/libs/antares/study/scenario-builder/rules.h @@ -40,6 +40,7 @@ #include "HydroTSNumberData.h" #include "WindTSNumberData.h" #include "LoadTSNumberData.h" +#include "HydroMaxPowerTSNumberData.h" #include #include @@ -115,6 +116,8 @@ class Rules final : private Yuni::NonCopyable solarTSNumberData solar; //! Hydro hydroTSNumberData hydro; + //! Hydro Max Power + hydroMaxPowerTSNumberData hydroMaxPower; //! Wind windTSNumberData wind; @@ -138,6 +141,7 @@ class Rules final : private Yuni::NonCopyable bool readLoad(const AreaName::Vector& instrs, String value, bool updaterMode); bool readWind(const AreaName::Vector& instrs, String value, bool updaterMode); bool readHydro(const AreaName::Vector& instrs, String value, bool updaterMode); + bool readHydroMaxPower(const AreaName::Vector& splitKey, String value, bool updaterMode); bool readSolar(const AreaName::Vector& instrs, String value, bool updaterMode); bool readHydroLevels(const AreaName::Vector& instrs, String value, bool updaterMode); bool readLink(const AreaName::Vector& instrs, String value, bool updaterMode); diff --git a/src/solver/aleatoire/alea_tirage_au_sort_chroniques.cpp b/src/solver/aleatoire/alea_tirage_au_sort_chroniques.cpp index b39a83fd76..287631518e 100644 --- a/src/solver/aleatoire/alea_tirage_au_sort_chroniques.cpp +++ b/src/solver/aleatoire/alea_tirage_au_sort_chroniques.cpp @@ -63,8 +63,17 @@ void ApplyRandomTSnumbers(const Study& study, { const Data::DataSeriesHydro& data = *area.hydro.series; assert(year < data.timeseriesNumbers.height); + ptchro.Hydraulique - = (data.count != 1) ? (long)data.timeseriesNumbers[0][year] : 0; // zero-based + = (data.TScount() != 1) ? (long)data.timeseriesNumbers[0][year] : 0; // zero-based + } + // Hydro Max Power + { + const Data::DataSeriesHydro& data = *area.hydro.series; + assert(year < data.timeseriesNumbersHydroMaxPower.height); + ptchro.HydroMaxPower = (data.maxPowerTScount() != 1) + ? (data.timeseriesNumbersHydroMaxPower[0][year]) + : 0; // zero-based } // Wind { diff --git a/src/solver/hydro/management/daily.cpp b/src/solver/hydro/management/daily.cpp index 8c2a6ab245..8f5969169f 100644 --- a/src/solver/hydro/management/daily.cpp +++ b/src/solver/hydro/management/daily.cpp @@ -43,6 +43,7 @@ #include #include #include +#include using namespace Yuni; @@ -241,6 +242,8 @@ inline void HydroManagement::prepareDailyOptimalGenerations(Solver::Variable::St auto& scratchpad = area.scratchpad[numSpace]; + auto& meanMaxDailyGenPower = scratchpad.meanMaxDailyGenPower; + int initReservoirLvlMonth = area.hydro.initializeReservoirLevelDate; double reservoirCapacity = area.hydro.reservoirCapacity; @@ -251,10 +254,12 @@ inline void HydroManagement::prepareDailyOptimalGenerations(Solver::Variable::St uint dayYear = 0; - auto const& maxPower = area.hydro.maxPower; + auto const& maxDailyGenEnergy = area.hydro.maxDailyGenEnergy; + + uint tsIndexMaxPower = (NumeroChroniquesTireesParPays[numSpace][z]).HydroMaxPower; - auto const& maxP = maxPower[Data::PartHydro::genMaxP]; - auto const& maxE = maxPower[Data::PartHydro::genMaxE]; + auto const& maxP = meanMaxDailyGenPower[tsIndexMaxPower]; + auto const& maxE = maxDailyGenEnergy[0]; auto& ventilationResults = ventilationResults_[numSpace][z]; @@ -278,14 +283,13 @@ inline void HydroManagement::prepareDailyOptimalGenerations(Solver::Variable::St auto daysPerMonth = calendar_.months[month].days; assert(daysPerMonth <= maxOPP); assert(daysPerMonth <= maxDailyTargetGen); - assert(daysPerMonth + dayYear - 1 < maxPower.height); + assert(daysPerMonth + dayYear - 1 < meanMaxDailyGenPower.height); for (uint day = 0; day != daysPerMonth; ++day) { auto dYear = day + dayYear; assert(day < 32); assert(dYear < 366); - scratchpad.optimalMaxPower[dYear] = maxP[dYear]; if (debugData) debugData->OPP[dYear] = maxP[dYear] * maxE[dYear]; @@ -474,7 +478,7 @@ inline void HydroManagement::prepareDailyOptimalGenerations(Solver::Variable::St uint dayMonth = 0; for (uint day = firstDay; day != endDay; ++day) { - problem.TurbineMax[dayMonth] = maxP[day] * maxE[day] / reservoirCapacity; + problem.TurbineMax[dayMonth] = maxP[day] * maxE[day] / reservoirCapacity; problem.TurbineMin[dayMonth] = data.dailyMinGen[day] / reservoirCapacity; problem.TurbineCible[dayMonth] @@ -560,5 +564,4 @@ void HydroManagement::prepareDailyOptimalGenerations(Solver::Variable::State& st prepareDailyOptimalGenerations(state, area, y, numSpace); }); } - } // namespace Antares diff --git a/src/solver/hydro/management/management.cpp b/src/solver/hydro/management/management.cpp index 8e9aac07e3..b2a69f12b8 100644 --- a/src/solver/hydro/management/management.cpp +++ b/src/solver/hydro/management/management.cpp @@ -306,40 +306,38 @@ bool HydroManagement::checkWeeklyMinGeneration(uint tsIndex, Data::Area& area) c return true; } -bool HydroManagement::checkHourlyMinGeneration(uint tsIndex, Data::Area& area) const +bool HydroManagement::checkGenerationPowerConsistency(uint numSpace) const { - // Hourly minimum generation <= hourly inflows for each hour - auto& mingenmatrix = area.hydro.series->mingen; - auto const& srcmingen = mingenmatrix[tsIndex < mingenmatrix.width ? tsIndex : 0]; - auto const& maxPower = area.hydro.maxPower; - auto const& maxP = maxPower[Data::PartHydro::genMaxP]; + bool ret = true; - for (uint month = 0; month != 12; ++month) - { - uint realmonth = calendar_.months[month].realmonth; - uint simulationMonth = calendar_.mapping.months[realmonth]; - auto daysPerMonth = calendar_.months[simulationMonth].days; - uint firstDay = calendar_.months[simulationMonth].daysYear.first; - uint endDay = firstDay + daysPerMonth; + areas_.each( + [&numSpace, &ret](const Data::Area& area) + { + uint z = area.index; + auto tsIndex = (uint)NumeroChroniquesTireesParPays[numSpace][z].Hydraulique; + auto tsIndexMaxPower = NumeroChroniquesTireesParPays[numSpace][z].HydroMaxPower; - for (uint day = firstDay; day != endDay; ++day) - { - for (uint h = 0; h < 24; ++h) - { - if (srcmingen[day * 24 + h] > maxP[day]) - { - logs.error() - << "In area: " << area.name << " [hourly] minimum generation of " - << srcmingen[day * 24 + h] << " MW in timestep " << day * 24 + h + 1 - << " of TS-" << tsIndex + 1 - << " is incompatible with the maximum generation of " << maxP[day] - << " MW."; - return false; - } - } - } - } - return true; + auto const& srcmingen = area.hydro.series->mingen; + auto const& srcmaxgen = area.hydro.series->maxHourlyGenPower; + + for (uint h = 0; h < HOURS_PER_YEAR; ++h) + { + const auto& min = srcmingen[tsIndex < srcmingen.width ? tsIndex : 0][h]; + const auto& max = srcmaxgen[tsIndexMaxPower < srcmaxgen.width ? tsIndexMaxPower : 0][h]; + + if (max < min) + { + logs.error() << "In area: " << area.name << " [hourly] minimum generation of " + << min << " MW in timestep " << h + 1 << " of TS-" << tsIndex + 1 + << " is incompatible with the maximum generation of " << max + << " MW in timestep " << h + 1 << " of TS-" << tsIndexMaxPower + 1 << " MW."; + ret = false; + return; + } + } + }); + + return ret; } bool HydroManagement::checkMinGeneration(uint numSpace) @@ -355,9 +353,6 @@ bool HydroManagement::checkMinGeneration(uint numSpace) bool followLoadModulations = area.hydro.followLoadModulations; bool reservoirManagement = area.hydro.reservoirManagement; - if (!reservoirManagement) - ret = checkHourlyMinGeneration(tsIndex, area) && ret; - if (!useHeuristicTarget) return; @@ -371,6 +366,7 @@ bool HydroManagement::checkMinGeneration(uint numSpace) ret = checkYearlyMinGeneration(numSpace, tsIndex, area) && ret; else ret = checkMonthlyMinGeneration(numSpace, tsIndex, area) && ret; + }); return ret; } @@ -517,6 +513,12 @@ double HydroManagement::randomReservoirLevel(double min, double avg, double max) return x * max + (1. - x) * min; } +bool HydroManagement::checksOnGenerationPowerBounds(uint numSpace) +{ + return (checkMinGeneration(numSpace) && checkGenerationPowerConsistency(numSpace)) ? true + : false; +} + void HydroManagement::makeVentilation(double* randomReservoirLevel, Solver::Variable::State& state, uint y, @@ -526,7 +528,7 @@ void HydroManagement::makeVentilation(double* randomReservoirLevel, prepareInflowsScaling(numSpace); minGenerationScaling(numSpace); - if (!checkMinGeneration(numSpace)) + if (!checksOnGenerationPowerBounds(numSpace)) { throw FatalError("hydro management: invalid minimum generation"); } diff --git a/src/solver/hydro/management/management.h b/src/solver/hydro/management/management.h index 30dda45bf8..2ff0b75fdb 100644 --- a/src/solver/hydro/management/management.h +++ b/src/solver/hydro/management/management.h @@ -88,7 +88,7 @@ struct TmpDataByArea typedef struct { std::vector HydrauliqueModulableQuotidien; /* indice par jour */ - std::vector NiveauxReservoirsDebutJours; //Niveaux (quotidiens) du reservoir de début + std::vector NiveauxReservoirsDebutJours; //Niveaux (quotidiens) du reservoir de début //de jour (en cas de gestion des reservoirs). std::vector NiveauxReservoirsFinJours; //Niveaux (quotidiens) du reservoir de fin //de jour (en cas de gestion des reservoirs). @@ -131,9 +131,11 @@ class HydroManagement final //! check Weekly minimum generation is lower than available inflows bool checkWeeklyMinGeneration(uint tsIndex, Data::Area& area) const; //! check Hourly minimum generation is lower than available inflows - bool checkHourlyMinGeneration(uint tsIndex, Data::Area& area) const; + bool checkGenerationPowerConsistency(uint numSpace) const; //! check minimum generation is lower than available inflows bool checkMinGeneration(uint numSpace); + //! return false if checkGenerationPowerConsistency or checkMinGeneration returns false + bool checksOnGenerationPowerBounds(uint numSpace); //! Prepare the net demand for each area template void prepareNetDemand(uint numSpace); diff --git a/src/solver/simulation/sim_calcul_economique.cpp b/src/solver/simulation/sim_calcul_economique.cpp index 75bba4aa43..beb1696131 100644 --- a/src/solver/simulation/sim_calcul_economique.cpp +++ b/src/solver/simulation/sim_calcul_economique.cpp @@ -559,17 +559,27 @@ void SIM_RenseignementProblemeHebdo(const Study& study, ntc.ValeurDeLoopFlowOrigineVersExtremite[k] = lnk.parameters[fhlLoopFlow][hourInYear]; } } - preparerBindingConstraint(problem, numSpace, PasDeTempsDebut, study.bindingConstraints, weekFirstDay, hourInWeek); - - const uint dayInTheYear = study.calendar.hours[hourInYear].dayYear; + preparerBindingConstraint( + problem, numSpace, PasDeTempsDebut, study.bindingConstraints, weekFirstDay, hourInWeek); for (uint k = 0; k < nbPays; ++k) { auto& tsIndex = NumeroChroniquesTireesParPays[numSpace][k]; + uint tsIndexMaxPower = tsIndex.HydroMaxPower; auto& area = *(study.areas.byIndex[k]); auto& scratchpad = area.scratchpad[numSpace]; auto& ror = area.hydro.series->ror; + auto& maxHourlyGenPowerMatrix = area.hydro.series->maxHourlyGenPower; + auto const& maxHourlyGenPowerTS + = maxHourlyGenPowerMatrix[tsIndexMaxPower]; + auto const& ContrainteDePmaxHydrauliqueHoraire = maxHourlyGenPowerTS[PasDeTempsDebut + hourInWeek]; + + auto& maxHourlyPumpPowerMatrix = area.hydro.series->maxHourlyPumpPower; + auto const& maxHourlyPumpPowerTS + = maxHourlyPumpPowerMatrix[tsIndexMaxPower]; + auto const& ContrainteDePmaxPompageHoraire = maxHourlyPumpPowerTS[PasDeTempsDebut + hourInWeek]; + assert(&scratchpad); assert((uint)hourInYear < scratchpad.ts.load.height); assert((uint)tsIndex.Consommation < scratchpad.ts.load.width); @@ -615,14 +625,14 @@ void SIM_RenseignementProblemeHebdo(const Study& study, if (problem.CaracteristiquesHydrauliques[k].PresenceDHydrauliqueModulable > 0) { problem.CaracteristiquesHydrauliques[k].ContrainteDePmaxHydrauliqueHoraire[hourInWeek] - = scratchpad.optimalMaxPower[dayInTheYear] + = ContrainteDePmaxHydrauliqueHoraire * problem.CaracteristiquesHydrauliques[k].WeeklyGeneratingModulation; } if (problem.CaracteristiquesHydrauliques[k].PresenceDePompageModulable) { problem.CaracteristiquesHydrauliques[k].ContrainteDePmaxPompageHoraire[hourInWeek] - = scratchpad.pumpingMaxPower[dayInTheYear] + = ContrainteDePmaxPompageHoraire * problem.CaracteristiquesHydrauliques[k].WeeklyPumpingModulation; } @@ -640,6 +650,10 @@ void SIM_RenseignementProblemeHebdo(const Study& study, uint tsIndex = (NumeroChroniquesTireesParPays[numSpace][k]).Hydraulique; auto& inflowsmatrix = area.hydro.series->storage; auto const& srcinflows = inflowsmatrix[tsIndex < inflowsmatrix.width ? tsIndex : 0]; + uint tsIndexMaxPower = (NumeroChroniquesTireesParPays[numSpace][k]).HydroMaxPower; + auto& scratchpad = area.scratchpad[numSpace]; + auto& dailyMeanMaxGenPower = scratchpad.meanMaxDailyGenPower[tsIndexMaxPower]; + auto& dailyMeanMaxPumpPower = scratchpad.meanMaxDailyPumpPower[tsIndexMaxPower]; auto& mingenmatrix = area.hydro.series->mingen; auto const& srcmingen = mingenmatrix[tsIndex < mingenmatrix.width ? tsIndex : 0]; for (uint j = 0; j < problem.NombreDePasDeTemps; ++j) @@ -663,8 +677,7 @@ void SIM_RenseignementProblemeHebdo(const Study& study, = 0.; problem.CaracteristiquesHydrauliques[k] .MaxEnergieHydrauParIntervalleOptimise[j] - = area.hydro.maxPower[area.hydro.genMaxP][day] - * area.hydro.maxPower[area.hydro.genMaxE][day] + = dailyMeanMaxGenPower[day] * area.hydro.maxDailyGenEnergy[0][day] * problem.CaracteristiquesHydrauliques[k] .WeeklyGeneratingModulation; } @@ -714,8 +727,8 @@ void SIM_RenseignementProblemeHebdo(const Study& study, { uint day = study.calendar.hours[PasDeTempsDebut + j * 24].dayYear; - double DGC = area.hydro.maxPower[area.hydro.genMaxP][day] - * area.hydro.maxPower[area.hydro.genMaxE][day]; + double DGC + = dailyMeanMaxGenPower[day] * area.hydro.maxDailyGenEnergy[0][day]; DGU_tmp[j] = DNT[day] * LUB; DGL_tmp[j] = DNT[day] * LLB; @@ -823,8 +836,8 @@ void SIM_RenseignementProblemeHebdo(const Study& study, problem.CaracteristiquesHydrauliques[k] .MaxEnergiePompageParIntervalleOptimise[j] - = area.hydro.maxPower[area.hydro.pumpMaxP][day] - * area.hydro.maxPower[area.hydro.pumpMaxE][day] + = dailyMeanMaxPumpPower[day] + * area.hydro.maxDailyPumpEnergy[0][day] * problem.CaracteristiquesHydrauliques[k] .WeeklyPumpingModulation; } @@ -856,8 +869,8 @@ void SIM_RenseignementProblemeHebdo(const Study& study, { uint day = study.calendar.hours[PasDeTempsDebut + j * 24].dayYear; - double DPC = area.hydro.maxPower[area.hydro.pumpMaxP][day] - * area.hydro.maxPower[area.hydro.pumpMaxE][day]; + double DPC = dailyMeanMaxPumpPower[day] + * area.hydro.maxDailyPumpEnergy[0][day]; WPU += DPC; } @@ -867,8 +880,9 @@ void SIM_RenseignementProblemeHebdo(const Study& study, for (uint j = 0; j < 7; ++j) { uint day = study.calendar.hours[PasDeTempsDebut + j * 24].dayYear; - double DPC = area.hydro.maxPower[area.hydro.pumpMaxP][day] - * area.hydro.maxPower[area.hydro.pumpMaxE][day]; + + double DPC = dailyMeanMaxPumpPower[day] + * area.hydro.maxDailyPumpEnergy[0][day]; double rc = area.hydro.reservoirCapacity; if (not area.hydro.hardBoundsOnRuleCurves) diff --git a/src/solver/simulation/sim_structure_donnees.h b/src/solver/simulation/sim_structure_donnees.h index 5356732f63..725890b24a 100644 --- a/src/solver/simulation/sim_structure_donnees.h +++ b/src/solver/simulation/sim_structure_donnees.h @@ -34,6 +34,7 @@ typedef struct std::vector ThermiqueParPalier; std::vector RenouvelableParPalier; int Hydraulique; + unsigned int HydroMaxPower; int Eolien; int Consommation; int Solar; diff --git a/src/solver/simulation/timeseries-numbers.cpp b/src/solver/simulation/timeseries-numbers.cpp index fa3c64b0dd..17cf9055e5 100644 --- a/src/solver/simulation/timeseries-numbers.cpp +++ b/src/solver/simulation/timeseries-numbers.cpp @@ -49,7 +49,8 @@ const map ts_to_tsIndex = {{timeSeriesLoad, 0}, {timeSeriesThermal, 3}, {timeSeriesSolar, 4}, {timeSeriesRenewable, 5}, - {timeSeriesTransmissionCapacities, 6}}; + {timeSeriesTransmissionCapacities, 6}, + {timeSeriesHydroMaxPower, 7}}; const map ts_to_tsTitle = {{timeSeriesLoad, "load"}, @@ -58,7 +59,8 @@ const map ts_to_tsTitle {timeSeriesThermal, "thermal"}, {timeSeriesSolar, "solar"}, {timeSeriesRenewable, "renewable clusters"}, - {timeSeriesTransmissionCapacities, "transmission capacities"}}; + {timeSeriesTransmissionCapacities, "transmission capacities"}, + {timeSeriesHydroMaxPower, "max-power"}}; void addInterModalTimeSeriesToMessage(const array& isTSintermodal, std::string& interModalTsMsg) @@ -93,6 +95,7 @@ static bool GenerateDeratedMode(Study& study) area.solar.series->timeseriesNumbers.zero(); area.wind.series->timeseriesNumbers.zero(); area.hydro.series->timeseriesNumbers.zero(); + area.hydro.series->timeseriesNumbersHydroMaxPower.zero(); for (uint i = 0; i != area.thermal.clusterCount(); ++i) { @@ -148,7 +151,7 @@ class hydroAreaNumberOfTSretriever : public areaNumberOfTSretriever } std::vector getAreaTimeSeriesNumber(const Area& area) { - std::vector to_return = {area.hydro.series->count}; + std::vector to_return = {area.hydro.series->TScount()}; return to_return; } uint getGeneratedTimeSeriesNumber() @@ -157,6 +160,25 @@ class hydroAreaNumberOfTSretriever : public areaNumberOfTSretriever } }; +class hydroMaxPowerAreaNumberOfTSretriever : public areaNumberOfTSretriever +{ +public: + explicit hydroMaxPowerAreaNumberOfTSretriever(Study& study) : areaNumberOfTSretriever(study) + { + } + std::vector getAreaTimeSeriesNumber(const Area& area) override + { + std::vector to_return = {area.hydro.series->maxPowerTScount()}; + return to_return; + } + uint getGeneratedTimeSeriesNumber() override + { + return 1; + } + + virtual ~hydroMaxPowerAreaNumberOfTSretriever() = default; +}; + class windAreaNumberOfTSretriever : public areaNumberOfTSretriever { public: @@ -356,6 +378,8 @@ bool checkIntraModalConsistency(array& nbTimeseriesByMode = make_shared(study); ts_to_numberOfTSretrievers[timeSeriesTransmissionCapacities] = make_shared(study); + ts_to_numberOfTSretrievers[timeSeriesHydroMaxPower] + = make_shared(study); // Loop over TS kind and check intra-modal consistency mapTStoRetriever::iterator it = ts_to_numberOfTSretrievers.begin(); @@ -420,7 +444,15 @@ bool checkInterModalConsistencyForArea(Area& area, if (isTSintermodal[indexTS]) { uint nbTimeSeries - = isTSgenerated[indexTS] ? parameters.nbTimeSeriesHydro : area.hydro.series->count; + = isTSgenerated[indexTS] ? parameters.nbTimeSeriesHydro : area.hydro.series->TScount(); + listNumberTsOverArea.push_back(nbTimeSeries); + } + + // Hydro Max Power : Add hydro's max power number of TS in area ... + indexTS = ts_to_tsIndex.at(timeSeriesHydroMaxPower); + if (isTSintermodal[indexTS]) + { + uint nbTimeSeries = area.hydro.series->maxPowerTScount(); listNumberTsOverArea.push_back(nbTimeSeries); } @@ -533,6 +565,15 @@ void storeTSnumbersForIntraModal(const array& intramo if (isTSintramodal[indexTS]) area.hydro.series->timeseriesNumbers[0][year] = intramodal_draws[indexTS]; + // ------------- + // Hydro Max Power ... + // ------------- + assert(year < area.hydro.series->timeseriesNumbersHydroMaxPower.height); + indexTS = ts_to_tsIndex.at(timeSeriesHydroMaxPower); + + if (isTSintramodal[indexTS]) + area.hydro.series->timeseriesNumbersHydroMaxPower[0][year] = intramodal_draws[indexTS]; + // ------------- // Thermal ... // ------------- @@ -640,6 +681,21 @@ void drawAndStoreTSnumbersForNOTintraModal(const array& i = (uint32_t)(floor(study.runtime->random[seedTimeseriesNumbers].next() * nbTimeSeries)); } + // ------------- + // Hydro Max Power... + // ------------- + indexTS = ts_to_tsIndex.at(timeSeriesHydroMaxPower); + + if (!isTSintramodal[indexTS]) + { + uint nbTimeSeries = area.hydro.series->maxPowerTScount(); + if (nbTimeSeries != 1) + { + area.hydro.series->timeseriesNumbersHydroMaxPower[0][year] = static_cast( + (floor(study.runtime->random[seedTimeseriesNumbers].next() * nbTimeSeries))); + } + } + // ------------- // Thermal ... // ------------- @@ -749,6 +805,8 @@ Matrix* getFirstTSnumberInterModalMatrixFoundInArea( else if (isTSintermodal[ts_to_tsIndex.at(timeSeriesRenewable)] && area.renewable.clusterCount() > 0) tsNumbersMtx = &(area.renewable.clusters[0]->series->timeseriesNumbers); + else if (isTSintermodal[ts_to_tsIndex.at(timeSeriesHydroMaxPower)]) + tsNumbersMtx = &(area.hydro.series->timeseriesNumbersHydroMaxPower); } assert(tsNumbersMtx); @@ -781,6 +839,10 @@ void applyMatrixDrawsToInterModalModesInArea(Matrix* tsNumbersMtx, if (isTSintermodal[ts_to_tsIndex.at(timeSeriesHydro)]) area.hydro.series->timeseriesNumbers[0][year] = draw; + assert(year < area.hydro.series->timeseriesNumbersHydroMaxPower.height); + if (isTSintermodal[ts_to_tsIndex.at(timeSeriesHydroMaxPower)]) + area.hydro.series->timeseriesNumbersHydroMaxPower[0][year] = draw; + if (isTSintermodal[ts_to_tsIndex.at(timeSeriesThermal)]) { uint clusterCount = (uint)area.thermal.clusterCount(); @@ -832,7 +894,10 @@ static void fixTSNumbersWhenWidthIsOne(Study& study) area.wind.series->timeseriesNumbers, area.wind.series->timeSeries.width, years); // Hydro fixTSNumbersSingleAreaSingleMode( - area.hydro.series->timeseriesNumbers, area.hydro.series->count, years); + area.hydro.series->timeseriesNumbers, area.hydro.series->TScount(), years); + // Hydro Max Power + fixTSNumbersSingleAreaSingleMode( + area.hydro.series->timeseriesNumbersHydroMaxPower, area.hydro.series->maxPowerTScount(), years); // Thermal std::for_each(area.thermal.clusters.cbegin(), @@ -899,7 +964,8 @@ bool TimeSeriesNumbers::Generate(Study& study) && parameters.renewableGeneration.isAggregated(), (bool)(timeSeriesRenewable & parameters.intraModal) && parameters.renewableGeneration.isClusters(), - (bool)(timeSeriesTransmissionCapacities & parameters.intraModal)}; + (bool)(timeSeriesTransmissionCapacities & parameters.intraModal), + static_cast(timeSeriesHydroMaxPower & parameters.intraModal)}; array nbTimeseriesByMode; @@ -913,7 +979,8 @@ bool TimeSeriesNumbers::Generate(Study& study) (bool)(timeSeriesThermal & parameters.timeSeriesToRefresh), (bool)(timeSeriesSolar & parameters.timeSeriesToRefresh), false, // TS generation is always disabled for renewables - false}; // TS generation is always disabled for links transmission capacities + false, // TS generation is always disabled for links transmission capacities + false}; // TS generation is always disabled for hydro max power if (not checkIntraModalConsistency(nbTimeseriesByMode, isTSintramodal, isTSgenerated, study)) return false; @@ -944,7 +1011,8 @@ bool TimeSeriesNumbers::Generate(Study& study) && parameters.renewableGeneration.isAggregated(), (bool)(timeSeriesRenewable & parameters.interModal) && parameters.renewableGeneration.isClusters(), - false}; // links transmission capacities time series cannot be inter-modal + false, // links transmission capacities time series cannot be inter-modal + static_cast(timeSeriesHydroMaxPower & parameters.interModal)}; if (std::any_of(std::begin(isTSintermodal), std::end(isTSintermodal), [](bool x) { return x; })) { @@ -983,9 +1051,10 @@ void TimeSeriesNumbers::StoreTimeSeriesNumbersIntoOuput(Data::Study& study) study.storeTimeSeriesNumbers(); study.storeTimeSeriesNumbers(); study.storeTimeSeriesNumbers(); - + study.storeTimeSeriesNumbers(); Simulation::BindingConstraintsTimeSeriesNumbersWriter ts_writer(study.resultWriter); ts_writer.write(study.bindingConstraintsGroups); + } } diff --git a/src/solver/ts-generator/hydro.cpp b/src/solver/ts-generator/hydro.cpp index cb458bb4ad..1dfea0dfdc 100644 --- a/src/solver/ts-generator/hydro.cpp +++ b/src/solver/ts-generator/hydro.cpp @@ -50,11 +50,7 @@ namespace TSGenerator static void PreproHydroInitMatrices(Data::Study& study, uint tsCount) { study.areas.each([&](Data::Area& area) { - auto& hydroseries = *(area.hydro.series); - - hydroseries.ror.resize(tsCount, HOURS_PER_YEAR); - hydroseries.storage.resize(tsCount, DAYS_PER_YEAR); - hydroseries.count = tsCount; + area.hydro.series->resizeRORandSTORAGE(tsCount); }); } diff --git a/src/solver/variable/economy/max-mrg.cpp b/src/solver/variable/economy/max-mrg.cpp index 80f0bbcfb7..f522e52259 100644 --- a/src/solver/variable/economy/max-mrg.cpp +++ b/src/solver/variable/economy/max-mrg.cpp @@ -132,10 +132,10 @@ inline void PrepareMaxMRGFor(const State& state, double* opmrg, uint numSpace) double ecart = 1.; uint loop = 100; // arbitrary - maximum number of iterations - // ref to the study calendar - auto& calendar = state.study.calendar; // Pmax - const auto& P = area.hydro.maxPower[Data::PartHydro::genMaxP]; + uint tsIndex = (NumeroChroniquesTireesParPays[numSpace][index]).HydroMaxPower; + auto& maxHourlyGenPowerMatrix = area.hydro.series->maxHourlyGenPower; + const auto& P = maxHourlyGenPowerMatrix[tsIndex]; do { @@ -148,8 +148,7 @@ inline void PrepareMaxMRGFor(const State& state, double* opmrg, uint numSpace) assert(i < HOURS_PER_YEAR && "calendar overflow"); if (niveau > OI[i]) { - uint dayYear = calendar.hours[i + state.hourInTheYear].dayYear; - opmrg[i] = Math::Min(niveau, OI[i] + P[dayYear] - H[i]); + opmrg[i] = Math::Min(niveau, OI[i] + P[i + state.hourInTheYear] - H[i]); SM += opmrg[i] - OI[i]; } else diff --git a/src/tests/run-study-tests/test_unfeasible_problem.py b/src/tests/run-study-tests/test_unfeasible_problem.py index 7ef0212061..4047907b21 100644 --- a/src/tests/run-study-tests/test_unfeasible_problem.py +++ b/src/tests/run-study-tests/test_unfeasible_problem.py @@ -65,4 +65,3 @@ def test_unfeasible_problem_01__warning_dry(study_path, check_runner): checks_on_weeks=warnings_on_weeks, simulation=check_runner.get_simulation())) check_runner.run(checks) - diff --git a/src/tests/src/libs/antares/study/CMakeLists.txt b/src/tests/src/libs/antares/study/CMakeLists.txt index dcff0a42fd..9a0642229b 100644 --- a/src/tests/src/libs/antares/study/CMakeLists.txt +++ b/src/tests/src/libs/antares/study/CMakeLists.txt @@ -6,3 +6,4 @@ add_subdirectory(output-folder) add_subdirectory(short-term-storage-input) add_subdirectory(thermal-price-definition) add_subdirectory(constraint) +add_subdirectory(parts) diff --git a/src/tests/src/libs/antares/study/constraint/test_constraint.cpp b/src/tests/src/libs/antares/study/constraint/test_constraint.cpp index a671d7857c..91aedabf1b 100644 --- a/src/tests/src/libs/antares/study/constraint/test_constraint.cpp +++ b/src/tests/src/libs/antares/study/constraint/test_constraint.cpp @@ -13,7 +13,7 @@ #include "antares/study/area/area.h" #include #include -#include "utils.h" +#include using namespace Antares::Data; namespace fs = std::filesystem; diff --git a/src/tests/src/libs/antares/study/constraint/test_group.cpp b/src/tests/src/libs/antares/study/constraint/test_group.cpp index c53f3c855d..c7f4faef7d 100644 --- a/src/tests/src/libs/antares/study/constraint/test_group.cpp +++ b/src/tests/src/libs/antares/study/constraint/test_group.cpp @@ -9,7 +9,7 @@ #include #include #include "antares/study/study.h" -#include "utils.h" +#include using namespace Antares::Data; namespace fs = std::filesystem; diff --git a/src/tests/src/libs/antares/study/parts/CMakeLists.txt b/src/tests/src/libs/antares/study/parts/CMakeLists.txt new file mode 100644 index 0000000000..e7ba85aa8e --- /dev/null +++ b/src/tests/src/libs/antares/study/parts/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(hydro) diff --git a/src/tests/src/libs/antares/study/parts/hydro/CMakeLists.txt b/src/tests/src/libs/antares/study/parts/hydro/CMakeLists.txt new file mode 100644 index 0000000000..d113d66e8b --- /dev/null +++ b/src/tests/src/libs/antares/study/parts/hydro/CMakeLists.txt @@ -0,0 +1,66 @@ +# Useful variables definitions +set(src_libs_antares_study "${CMAKE_SOURCE_DIR}/libs/antares/study") +set(src_libs_antares "${CMAKE_SOURCE_DIR}/libs/antares") +set(src_test_utils "${CMAKE_SOURCE_DIR}/tests/src/utils") + + +# Hydro data reader +set(SRC_HYDRO_READER + test-hydroreader-class.cpp) + +add_executable(test-hydro-reader ${SRC_HYDRO_READER}) + +target_include_directories(test-hydro-reader + PRIVATE + "${src_libs_antares_study}" + "${src_libs_antares}/array/antares/array" + "${src_test_utils}" +) + +target_link_libraries(test-hydro-reader + PRIVATE + Boost::unit_test_framework + Antares::study + test_utils_unit +) + +# Linux +if(UNIX AND NOT APPLE) + target_link_libraries(test-hydro-reader PRIVATE stdc++fs) +endif() + +set_target_properties(test-hydro-reader PROPERTIES FOLDER Unit-tests/hydro) +add_test(NAME hydro-reader COMMAND test-hydro-reader) +set_property(TEST hydro-reader PROPERTY LABELS unit) + + +# Hydro series +set(SRC_HYDRO_SERIES + test-hydro-series.cpp) + +add_executable(test-hydro-series ${SRC_HYDRO_SERIES}) + +target_include_directories(test-hydro-series + PRIVATE + "${src_libs_antares_study}" + "${src_libs_antares}/array/antares/array" + "${src_libs_antares}/exception/antares/exception" + "${src_test_utils}" +) + +target_link_libraries(test-hydro-series + PRIVATE + Boost::unit_test_framework + Antares::study + Antares::exception + test_utils_unit +) + +# Linux +if(UNIX AND NOT APPLE) + target_link_libraries(test-hydro-series PRIVATE stdc++fs) +endif() + +set_target_properties(test-hydro-series PROPERTIES FOLDER Unit-tests/hydro) +add_test(NAME hydro-series COMMAND test-hydro-series) +set_property(TEST hydro-series PROPERTY LABELS unit) \ No newline at end of file diff --git a/src/tests/src/libs/antares/study/parts/hydro/test-hydro-series.cpp b/src/tests/src/libs/antares/study/parts/hydro/test-hydro-series.cpp new file mode 100644 index 0000000000..750cdf707d --- /dev/null +++ b/src/tests/src/libs/antares/study/parts/hydro/test-hydro-series.cpp @@ -0,0 +1,266 @@ +#define BOOST_TEST_MODULE test hydro series + +#define WIN32_LEAN_AND_MEAN + +#include +#include +#include +#include +#include + +#define SEP "/" + +using namespace Antares::Data; +namespace fs = std::filesystem; + +void fillTimeSeriesWithSpecialEnds(Matrix& timeSeries, double value) +{ + for (uint ts = 0; ts < timeSeries.width; ts++) + { + timeSeries[ts][0] = value + 1; + timeSeries[ts][timeSeries.height - 1] = value + 2; + } +} + +struct Fixture +{ + Fixture() + { + // Create studies + study = std::make_shared(true); + + // Add areas to studies + area_1 = study->areaAdd("Area1"); + study->areas.rebuildIndexes(); + + // Create necessary folders and files for these two areas + createFoldersAndFiles(); + + // Instantiating neccessary studies parameters + study->header.version = 870; + study->parameters.derated = false; + + // Setting necessary paths + pathToMaxHourlyGenPower_file.clear(); + pathToMaxHourlyGenPower_file = base_folder + SEP + series_folder + SEP + area_1->id.c_str() + + SEP + maxHourlyGenPower_file; + + pathToMaxHourlyPumpPower_file.clear(); + pathToMaxHourlyPumpPower_file = base_folder + SEP + series_folder + SEP + area_1->id.c_str() + + SEP + maxHourlyPumpPower_file; + + pathToSeriesFolder.clear(); + pathToSeriesFolder = base_folder + SEP + series_folder; + } + + void createFoldersAndFiles() + { + // series folder + stringT buffer; + createFolder(base_folder, series_folder); + + // area folder + stringT area1_folder = area_1->id.c_str(); + buffer.clear(); + buffer = base_folder + SEP + series_folder; + createFolder(buffer, area1_folder); + + // maxHourlyGenPower and maxHourlyPumpPower files + buffer.clear(); + buffer = base_folder + SEP + series_folder + SEP + area1_folder; + createFile(buffer, maxHourlyGenPower_file); + createFile(buffer, maxHourlyPumpPower_file); + } + + std::shared_ptr study; + Area* area_1; + stringT base_folder = fs::temp_directory_path().string(); + stringT series_folder = "series"; + stringT maxHourlyGenPower_file = "maxHourlyGenPower.txt"; + stringT maxHourlyPumpPower_file = "maxHourlyPumpPower.txt"; + stringT pathToMaxHourlyGenPower_file; + stringT pathToMaxHourlyPumpPower_file; + stringT pathToSeriesFolder; + + ~Fixture() + { + removeFolder(base_folder, series_folder); + } +}; + +BOOST_AUTO_TEST_SUITE(s) + +BOOST_FIXTURE_TEST_CASE(Testing_load_power_credits_matrices_equal_width, Fixture) +{ + bool ret = true; + bool fatalError = false; + + auto& maxHourlyGenPower = area_1->hydro.series->maxHourlyGenPower; + auto& maxHourlyPumpPower = area_1->hydro.series->maxHourlyPumpPower; + maxHourlyGenPower.reset(3, HOURS_PER_YEAR); + maxHourlyPumpPower.reset(3, HOURS_PER_YEAR); + + fillTimeSeriesWithSpecialEnds(maxHourlyGenPower, 400.); + fillTimeSeriesWithSpecialEnds(maxHourlyPumpPower, 200.); + + ret = maxHourlyGenPower.saveToCSVFile(pathToMaxHourlyGenPower_file, 0) && ret; + ret = maxHourlyPumpPower.saveToCSVFile(pathToMaxHourlyPumpPower_file, 0) && ret; + + maxHourlyGenPower.reset(3, HOURS_PER_YEAR); + maxHourlyPumpPower.reset(3, HOURS_PER_YEAR); + + ret = area_1->hydro.series->LoadMaxPower(area_1->id, pathToSeriesFolder) && ret; + BOOST_CHECK(ret); + + area_1->hydro.series->EqualizeMaxPowerTSsizes(*area_1, fatalError); + + BOOST_CHECK(!fatalError); + BOOST_CHECK_EQUAL(maxHourlyGenPower.width, 3); +} + +BOOST_FIXTURE_TEST_CASE(Testing_load_power_credits_both_matrix_equal_width_and_derated, Fixture) +{ + bool ret = true; + bool fatalError = false; + study->parameters.derated = true; + unsigned int studyVersion = 870; + bool usedBySolver = true; + + auto& maxHourlyGenPower = area_1->hydro.series->maxHourlyGenPower; + auto& maxHourlyPumpPower = area_1->hydro.series->maxHourlyPumpPower; + maxHourlyGenPower.reset(3, HOURS_PER_YEAR); + maxHourlyPumpPower.reset(3, HOURS_PER_YEAR); + + fillTimeSeriesWithSpecialEnds(maxHourlyGenPower, 400.); + fillTimeSeriesWithSpecialEnds(maxHourlyPumpPower, 200.); + + ret = maxHourlyGenPower.saveToCSVFile(pathToMaxHourlyGenPower_file, 0) && ret; + ret = maxHourlyPumpPower.saveToCSVFile(pathToMaxHourlyPumpPower_file, 0) && ret; + + maxHourlyGenPower.reset(3, HOURS_PER_YEAR); + maxHourlyPumpPower.reset(3, HOURS_PER_YEAR); + + ret = area_1->hydro.series->LoadMaxPower(area_1->id, pathToSeriesFolder) && ret; + BOOST_CHECK(ret); + + area_1->hydro.series->EqualizeMaxPowerTSsizes(*area_1, fatalError); + area_1->hydro.series->resizeTSinDeratedMode(study->parameters.derated, studyVersion, usedBySolver); + + BOOST_CHECK_EQUAL(maxHourlyGenPower.width, 1); + BOOST_CHECK_EQUAL(maxHourlyPumpPower.width, 1); + BOOST_CHECK(!fatalError); +} + +BOOST_FIXTURE_TEST_CASE(Testing_load_power_credits_matrices_different_width_case_2, Fixture) +{ + bool ret = true; + bool fatalError = false; + + auto& maxHourlyGenPower = area_1->hydro.series->maxHourlyGenPower; + auto& maxHourlyPumpPower = area_1->hydro.series->maxHourlyPumpPower; + maxHourlyGenPower.reset(3, HOURS_PER_YEAR); + maxHourlyPumpPower.reset(2, HOURS_PER_YEAR); + + fillTimeSeriesWithSpecialEnds(maxHourlyGenPower, 400.); + fillTimeSeriesWithSpecialEnds(maxHourlyPumpPower, 200.); + + ret = maxHourlyGenPower.saveToCSVFile(pathToMaxHourlyGenPower_file, 0) && ret; + ret = maxHourlyPumpPower.saveToCSVFile(pathToMaxHourlyPumpPower_file, 0) && ret; + + maxHourlyGenPower.reset(3, HOURS_PER_YEAR); + maxHourlyPumpPower.reset(2, HOURS_PER_YEAR); + + ret = area_1->hydro.series->LoadMaxPower(area_1->id, pathToSeriesFolder) && ret; + BOOST_CHECK(ret); + + area_1->hydro.series->EqualizeMaxPowerTSsizes(*area_1, fatalError); + BOOST_CHECK(fatalError); +} + +BOOST_FIXTURE_TEST_CASE(Testing_load_power_credits_different_width_case_1, Fixture) +{ + bool ret = true; + bool fatalError = false; + + auto& maxHourlyGenPower = area_1->hydro.series->maxHourlyGenPower; + auto& maxHourlyPumpPower = area_1->hydro.series->maxHourlyPumpPower; + maxHourlyGenPower.reset(1, HOURS_PER_YEAR); + maxHourlyPumpPower.reset(3, HOURS_PER_YEAR); + + fillTimeSeriesWithSpecialEnds(maxHourlyGenPower, 400.); + fillTimeSeriesWithSpecialEnds(maxHourlyPumpPower, 200.); + + ret = maxHourlyGenPower.saveToCSVFile(pathToMaxHourlyGenPower_file, 0) && ret; + ret = maxHourlyPumpPower.saveToCSVFile(pathToMaxHourlyPumpPower_file, 0) && ret; + + maxHourlyGenPower.reset(1, HOURS_PER_YEAR); + maxHourlyPumpPower.reset(3, HOURS_PER_YEAR); + + ret = area_1->hydro.series->LoadMaxPower(area_1->id, pathToSeriesFolder) && ret; + BOOST_CHECK(ret); + + area_1->hydro.series->EqualizeMaxPowerTSsizes(*area_1, fatalError); + + BOOST_CHECK(!fatalError); + BOOST_CHECK_EQUAL(maxHourlyGenPower.width, maxHourlyPumpPower.width); +} + +BOOST_FIXTURE_TEST_CASE(Testing_load_power_credits_different_width_case_2, Fixture) +{ + bool ret = true; + bool fatalError = false; + + auto& maxHourlyGenPower = area_1->hydro.series->maxHourlyGenPower; + auto& maxHourlyPumpPower = area_1->hydro.series->maxHourlyPumpPower; + maxHourlyGenPower.reset(4, HOURS_PER_YEAR); + maxHourlyPumpPower.reset(1, HOURS_PER_YEAR); + + fillTimeSeriesWithSpecialEnds(maxHourlyGenPower, 400.); + fillTimeSeriesWithSpecialEnds(maxHourlyPumpPower, 200.); + + ret = maxHourlyGenPower.saveToCSVFile(pathToMaxHourlyGenPower_file, 0) && ret; + ret = maxHourlyPumpPower.saveToCSVFile(pathToMaxHourlyPumpPower_file, 0) && ret; + + maxHourlyGenPower.reset(4, HOURS_PER_YEAR); + maxHourlyPumpPower.reset(1, HOURS_PER_YEAR); + + ret = area_1->hydro.series->LoadMaxPower(area_1->id, pathToSeriesFolder) && ret; + BOOST_CHECK(ret); + + area_1->hydro.series->EqualizeMaxPowerTSsizes(*area_1, fatalError); + + BOOST_CHECK(!fatalError); + BOOST_CHECK_EQUAL(maxHourlyGenPower.width, maxHourlyPumpPower.width); +} + +BOOST_FIXTURE_TEST_CASE(Testing_load_power_credits_both_zeros, Fixture) +{ + bool ret = true; + bool fatalError = false; + + auto& maxHourlyGenPower = area_1->hydro.series->maxHourlyGenPower; + auto& maxHourlyPumpPower = area_1->hydro.series->maxHourlyPumpPower; + maxHourlyGenPower.reset(4, HOURS_PER_YEAR); + maxHourlyPumpPower.reset(1, HOURS_PER_YEAR); + + fillTimeSeriesWithSpecialEnds(maxHourlyGenPower, 400.); + fillTimeSeriesWithSpecialEnds(maxHourlyPumpPower, 200.); + + ret = maxHourlyGenPower.saveToCSVFile(pathToMaxHourlyGenPower_file, 0) && ret; + ret = maxHourlyPumpPower.saveToCSVFile(pathToMaxHourlyPumpPower_file, 0) && ret; + + maxHourlyGenPower.reset(4, HOURS_PER_YEAR); + maxHourlyPumpPower.reset(1, HOURS_PER_YEAR); + + ret = area_1->hydro.series->LoadMaxPower(area_1->id, pathToSeriesFolder) && ret; + BOOST_CHECK(ret); + + maxHourlyGenPower.width = 0; + maxHourlyPumpPower.width = 0; + area_1->hydro.series->EqualizeMaxPowerTSsizes(*area_1, fatalError); + + BOOST_CHECK(!fatalError); + BOOST_CHECK_EQUAL(maxHourlyGenPower.width, maxHourlyPumpPower.width); +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/src/tests/src/libs/antares/study/parts/hydro/test-hydroreader-class.cpp b/src/tests/src/libs/antares/study/parts/hydro/test-hydroreader-class.cpp new file mode 100644 index 0000000000..c581528e8b --- /dev/null +++ b/src/tests/src/libs/antares/study/parts/hydro/test-hydroreader-class.cpp @@ -0,0 +1,184 @@ +#define BOOST_TEST_MODULE test data transfer + +#define WIN32_LEAN_AND_MEAN + +#include +#include +#include +#include + +#define SEP "/" + +using namespace Antares::Data; +namespace fs = std::filesystem; + +bool equalDailyMaxPowerAsHourlyTs(const Matrix::ColumnType& hourlyColumn, + const Matrix::ColumnType& dailyColumn) +{ + uint hour = 0; + uint day = 0; + + while (hour < HOURS_PER_YEAR && day < DAYS_PER_YEAR) + { + for (uint i = 0; i < HOURS_PER_DAY; ++i) + { + if (hourlyColumn[hour] != dailyColumn[day]) + return false; + ++hour; + } + ++day; + } + + return true; +} + +bool equalDailyMaxEnergyTs(const Matrix::ColumnType& col1, const Matrix::ColumnType& col2) +{ + for (uint h = 0; h < DAYS_PER_YEAR; ++h) + { + if (col1[h] != col2[h]) + return false; + } + + return true; +} + +void fillColumnWithSpecialEnds(Matrix::ColumnType& col, double value, uint heigth) +{ + col[0] = value + 1; + col[heigth - 1] = value + 2; +} + +struct Fixture +{ + Fixture() + { + study = std::make_shared(true); + reader = std::make_shared(); + // Add areas + area_1 = study->areaAdd("Area1"); + study->areas.rebuildIndexes(); + + // Create necessary folders and files for these two areas + createFoldersAndFiles(); + + auto& gen = reader->dailyMaxPumpAndGen[HydroMaxTimeSeriesReader::genMaxP]; + fillColumnWithSpecialEnds(gen, 300., DAYS_PER_YEAR); + + auto& pump = reader->dailyMaxPumpAndGen[HydroMaxTimeSeriesReader::pumpMaxP]; + fillColumnWithSpecialEnds(pump, 200., DAYS_PER_YEAR); + + auto& hoursGen = reader->dailyMaxPumpAndGen[HydroMaxTimeSeriesReader::genMaxE]; + fillColumnWithSpecialEnds(hoursGen, 20., DAYS_PER_YEAR); + + auto& hoursPump = reader->dailyMaxPumpAndGen[HydroMaxTimeSeriesReader::pumpMaxE]; + fillColumnWithSpecialEnds(hoursPump, 14., DAYS_PER_YEAR); + + stringT buffer; + buffer.clear(); + buffer = base_folder + SEP + hydro_folder + SEP + common_folder + SEP + capacity_folder + + SEP + maxpower + area_1->id.c_str() + ".txt"; + reader->dailyMaxPumpAndGen.saveToCSVFile(buffer, 2); + + // Reset columns + reader->dailyMaxPumpAndGen.fillColumn(HydroMaxTimeSeriesReader::genMaxP, 0.); + reader->dailyMaxPumpAndGen.fillColumn(HydroMaxTimeSeriesReader::pumpMaxP, 0.); + reader->dailyMaxPumpAndGen.fillColumn(HydroMaxTimeSeriesReader::genMaxE, 24.); + reader->dailyMaxPumpAndGen.fillColumn(HydroMaxTimeSeriesReader::pumpMaxE, 24.); + } + + void createFoldersAndFiles() + { + stringT buffer; + stringT area1_ID = area_1->id.c_str(); + stringT maxpowerArea1 = maxpower + area1_ID + ".txt"; + stringT maxDailyGenEnergy_Area1 = maxDailyGenEnergy_ + area1_ID + ".txt"; + stringT maxDailyPumpEnergy_Area1 = maxDailyPumpEnergy_ + area1_ID + ".txt"; + + buffer.clear(); + + // hydro folder + createFolder(base_folder, hydro_folder); + + // series folder + buffer = base_folder + SEP + hydro_folder; + createFolder(buffer, series_folder); + + // area1 folder + buffer.clear(); + buffer = base_folder + SEP + hydro_folder + SEP + series_folder; + createFolder(buffer, area1_ID); + buffer.clear(); + buffer = base_folder + SEP + hydro_folder + SEP + series_folder + SEP + area1_ID; + + // maxHourlyGenPower and maxHourlyPumpPower files + createFile(buffer, maxHourlyGenPower); + createFile(buffer, maxHourlyPumpPower); + + // common and capacity folders + buffer.clear(); + buffer = base_folder + SEP + hydro_folder; + createFolder(buffer, common_folder); + buffer.clear(); + buffer = base_folder + SEP + hydro_folder + SEP + common_folder; + createFolder(buffer, capacity_folder); + + // max daily energy and power file + buffer.clear(); + buffer = base_folder + SEP + hydro_folder + SEP + common_folder + SEP + capacity_folder; + createFile(buffer, maxpowerArea1); + + // max daily energy files + createFile(buffer, maxDailyGenEnergy_Area1); + createFile(buffer, maxDailyPumpEnergy_Area1); + } + + std::shared_ptr study; + std::shared_ptr reader; + Area* area_1; + stringT base_folder = fs::temp_directory_path().string(); + stringT hydro_folder = "hydro"; + stringT series_folder = "series"; + stringT common_folder = "common"; + stringT capacity_folder = "capacity"; + stringT maxDailyGenEnergy_ = "maxDailyGenEnergy_"; + stringT maxDailyPumpEnergy_ = "maxDailyPumpEnergy_"; + stringT maxpower = "maxpower_"; + stringT maxHourlyGenPower = "maxHourlyGenPower.txt"; + stringT maxHourlyPumpPower = "maxHourlyPumpPower.txt"; + + ~Fixture() + { + removeFolder(base_folder, hydro_folder); + } +}; + +BOOST_AUTO_TEST_SUITE(s) + +BOOST_FIXTURE_TEST_CASE(Testing_support_for_old_studies, Fixture) +{ + stringT buffer; + bool ret = true; + + auto& genP = area_1->hydro.series->maxHourlyGenPower[0]; + auto& pumpP = area_1->hydro.series->maxHourlyPumpPower[0]; + auto& genE = area_1->hydro.maxDailyGenEnergy[0]; + auto& pumpE = area_1->hydro.maxDailyPumpEnergy[0]; + + auto& genPReader = reader->dailyMaxPumpAndGen[HydroMaxTimeSeriesReader::genMaxP]; + auto& pumpPReader = reader->dailyMaxPumpAndGen[HydroMaxTimeSeriesReader::pumpMaxP]; + auto& genEReader = reader->dailyMaxPumpAndGen[HydroMaxTimeSeriesReader::genMaxE]; + auto& pumpEReader = reader->dailyMaxPumpAndGen[HydroMaxTimeSeriesReader::pumpMaxE]; + + buffer.clear(); + buffer = base_folder + SEP + hydro_folder; + ret = (*reader)(buffer, *area_1, study->usedByTheSolver) && ret; + + BOOST_CHECK(ret); + BOOST_CHECK(equalDailyMaxPowerAsHourlyTs(genP, genPReader)); + BOOST_CHECK(equalDailyMaxPowerAsHourlyTs(pumpP, pumpPReader)); + BOOST_CHECK(equalDailyMaxEnergyTs(genE, genEReader)); + BOOST_CHECK(equalDailyMaxEnergyTs(pumpE, pumpEReader)); +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/src/tests/src/libs/antares/study/scenario-builder/test-sc-builder-file-read-line.cpp b/src/tests/src/libs/antares/study/scenario-builder/test-sc-builder-file-read-line.cpp index 798b91ab2f..d098cf3ca0 100644 --- a/src/tests/src/libs/antares/study/scenario-builder/test-sc-builder-file-read-line.cpp +++ b/src/tests/src/libs/antares/study/scenario-builder/test-sc-builder-file-read-line.cpp @@ -83,9 +83,15 @@ struct Fixture // Hydro : set the nb of ready made TS nbReadyMadeTS = 12; - area_1->hydro.series->count = nbReadyMadeTS; - area_2->hydro.series->count = nbReadyMadeTS; - area_3->hydro.series->count = nbReadyMadeTS; + area_1->hydro.series->resizeGenerationTS(nbReadyMadeTS, 1); + area_2->hydro.series->resizeGenerationTS(nbReadyMadeTS, 1); + area_3->hydro.series->resizeGenerationTS(nbReadyMadeTS, 1); + + // Hydro Max Power: set the nb of ready made TS + nbReadyMadeTS = 15; + area_1->hydro.series->resizeMaxPowerTS(nbReadyMadeTS, 1); + area_2->hydro.series->resizeMaxPowerTS(nbReadyMadeTS, 1); + area_3->hydro.series->resizeMaxPowerTS(nbReadyMadeTS, 1); // Links link_12 = AreaAddLinkBetweenAreas(area_1, area_2, false); @@ -232,6 +238,22 @@ BOOST_FIXTURE_TEST_CASE(on_area2_and_on_year_15__solar_TS_number_3_is_chosen__re BOOST_CHECK_EQUAL(area_2->hydro.series->timeseriesNumbers[0][yearNumber.to()], tsNumber.to() - 1); } +// ================= +// Tests on Hydro Max Power +// ================= +BOOST_FIXTURE_TEST_CASE(on_area3_and_on_year_10__hydro_power_credits_TS_number_6_is_chosen__reading_OK, Fixture) +{ + AreaName yearNumber = "7"; + String tsNumber = "6"; + AreaName::Vector splitKey = { "hgp", "area 3", yearNumber }; + BOOST_CHECK(my_rule.readLine(splitKey, tsNumber, false)); + + BOOST_CHECK_EQUAL(my_rule.hydroMaxPower.get_value(yearNumber.to(), area_3->index), tsNumber.to()); + + BOOST_CHECK(my_rule.apply()); + BOOST_CHECK_EQUAL(area_3->hydro.series->timeseriesNumbersHydroMaxPower[0][yearNumber.to()], tsNumber.to() - 1); +} + // =========================== // Tests on Thermal clusters diff --git a/src/tests/src/libs/antares/study/scenario-builder/test-sc-builder-file-save.cpp b/src/tests/src/libs/antares/study/scenario-builder/test-sc-builder-file-save.cpp index 592ece1694..b2e26dd653 100644 --- a/src/tests/src/libs/antares/study/scenario-builder/test-sc-builder-file-save.cpp +++ b/src/tests/src/libs/antares/study/scenario-builder/test-sc-builder-file-save.cpp @@ -96,8 +96,7 @@ struct commonFixture study = std::make_shared(); // Set study parameters study->parameters.nbYears = 20; - study->parameters.timeSeriesToGenerate - = 0; // No generated time-series, only ready made time-series + study->parameters.timeSeriesToGenerate = 0; // No generated time-series, only ready made time-series // Add areas area_1 = study->areaAdd("Area 1"); @@ -125,9 +124,15 @@ struct commonFixture // Hydro : set the nb of ready made TS nbReadyMadeTS = 12; - area_1->hydro.series->count = nbReadyMadeTS; - area_2->hydro.series->count = nbReadyMadeTS; - area_3->hydro.series->count = nbReadyMadeTS; + area_1->hydro.series->resizeGenerationTS(nbReadyMadeTS, 1); + area_2->hydro.series->resizeGenerationTS(nbReadyMadeTS, 1); + area_3->hydro.series->resizeGenerationTS(nbReadyMadeTS, 1); + + // Hydro Max Power : set the nb of ready made TS + nbReadyMadeTS = 15; + area_1->hydro.series->resizeMaxPowerTS(nbReadyMadeTS, 1); + area_2->hydro.series->resizeMaxPowerTS(nbReadyMadeTS, 1); + area_3->hydro.series->resizeMaxPowerTS(nbReadyMadeTS, 1); // Links link_12 = AreaAddLinkBetweenAreas(area_1, area_2, false); @@ -350,6 +355,28 @@ BOOST_FIXTURE_TEST_CASE( BOOST_CHECK(files_identical(path_to_generated_file, referenceFile.path())); } +// ================= +// Tests on Hydro Max Power +// ================= +BOOST_FIXTURE_TEST_CASE( + HYDRO_POWER_CREDITS__TS_number_for_many_areas_and_years__generated_and_ref_sc_buider_files_are_identical, saveFixture) +{ + my_rule->hydroMaxPower.setTSnumber(area_2->index, 10, 7); + my_rule->hydroMaxPower.setTSnumber(area_3->index, 4, 11); + my_rule->hydroMaxPower.setTSnumber(area_1->index, 11, 3); + + saveScenarioBuilder(); + + // Build reference scenario builder file + referenceFile.append("[my rule name]"); + referenceFile.append("hgp,area 1,11 = 3"); + referenceFile.append("hgp,area 2,10 = 7"); + referenceFile.append("hgp,area 3,4 = 11"); + referenceFile.write(); + + BOOST_CHECK(files_identical(path_to_generated_file, referenceFile.path())); +} + // =========================== // Tests on Thermal clusters // =========================== diff --git a/src/tests/src/solver/simulation/test-store-timeseries-number.cpp b/src/tests/src/solver/simulation/test-store-timeseries-number.cpp index 940a8b28fa..889f390b47 100644 --- a/src/tests/src/solver/simulation/test-store-timeseries-number.cpp +++ b/src/tests/src/solver/simulation/test-store-timeseries-number.cpp @@ -11,7 +11,7 @@ #include #include "timeseries-numbers.h" #include "BindingConstraintsTimeSeriesNumbersWriter.h" -#include "utils.h" +#include #include #include #include diff --git a/src/tests/src/solver/simulation/test-time_series.cpp b/src/tests/src/solver/simulation/test-time_series.cpp index 00357c5299..897ab37ee1 100644 --- a/src/tests/src/solver/simulation/test-time_series.cpp +++ b/src/tests/src/solver/simulation/test-time_series.cpp @@ -9,7 +9,7 @@ #include #include #include -#include "utils.h" +#include using namespace Antares::Solver; using namespace Antares::Data; diff --git a/src/tests/src/utils/CMakeLists.txt b/src/tests/src/utils/CMakeLists.txt index bc7f9bc1d7..5e4781ad96 100644 --- a/src/tests/src/utils/CMakeLists.txt +++ b/src/tests/src/utils/CMakeLists.txt @@ -1,6 +1,6 @@ add_library(test_utils_unit - utils.cpp - utils.h + files-system.cpp + files-system.h ) target_include_directories( diff --git a/src/tests/src/utils/files-system.cpp b/src/tests/src/utils/files-system.cpp new file mode 100644 index 0000000000..32eebb9dcb --- /dev/null +++ b/src/tests/src/utils/files-system.cpp @@ -0,0 +1,73 @@ +#include "files-system.h" + +namespace fs = std::filesystem; + +fs::path generateAndCreateDirName(const std::string& dirName) +{ + fs::path working_dir = fs::temp_directory_path() / dirName; + fs::remove_all(working_dir); + fs::create_directories(working_dir); + return working_dir; +} + +void createFolder(const stringT& path, const stringT& folder_name) +{ + fs::path folder_path = fs::path(path.c_str()) / folder_name.c_str(); + + try + { + fs::create_directory(folder_path); + } + catch (const fs::filesystem_error& e) + { + std::cerr << "Exception creating folder '" + folder_name + "': " + e.what() + "\n"; + } +} + +void createFile(const stringT& folder_path, const stringT& file_name) +{ + // Construct the full path to the file + fs::path path = fs::path(folder_path.c_str()) / file_name.c_str(); + + // Create an output file stream + std::ofstream outputFile(path); + + try + { + if (outputFile.is_open()) + { + // File was successfully created and is open + outputFile << "This is a sample content." << std::endl; + outputFile.close(); + std::cout << "File " + file_name + " is created in " + folder_path + "\n"; + } + else + { + // Failed to create or open the file + std::error_code ec = std::make_error_code(std::errc::io_error); + throw fs::filesystem_error("Failed to create the file.", ec); + } + } + catch (const fs::filesystem_error& e) + { + std::cerr << "Error creating file: " << file_name << "/" << e.what() << "\n"; + } +} + +void removeFolder(stringT& path, stringT& folder_name) +{ + fs::path folder_path = fs::path(path.c_str()) / folder_name.c_str(); + if (fs::exists(folder_path)) + { + try + { + fs::remove_all(folder_path); + std::cout << "Folder " + folder_name + " at " + folder_path.string() + + " deleted.\n"; + } + catch (const fs::filesystem_error& e) + { + std::cerr << "Exception deleting folder '" + folder_name + "': " + e.what() + "\n"; + } + } +} \ No newline at end of file diff --git a/src/tests/src/utils/utils.h b/src/tests/src/utils/files-system.h similarity index 52% rename from src/tests/src/utils/utils.h rename to src/tests/src/utils/files-system.h index 9c7e70367f..ccc5289c62 100644 --- a/src/tests/src/utils/utils.h +++ b/src/tests/src/utils/files-system.h @@ -1,8 +1,17 @@ #pragma once #include +#include +#include +#include + +using stringT = std::string; // The following macro is used due to linking issues with #define CREATE_TMP_DIR_BASED_ON_TEST_NAME() generateAndCreateDirName(boost::unit_test::framework::current_test_case().p_name); std::filesystem::path generateAndCreateDirName(const std::string&); + +void createFolder(const stringT& path, const stringT& folder_name); +void createFile(const stringT& folder_path, const stringT& file_name); +void removeFolder(stringT& path, stringT& folder_name); diff --git a/src/tests/src/utils/utils.cpp b/src/tests/src/utils/utils.cpp deleted file mode 100644 index 4fd0fe735e..0000000000 --- a/src/tests/src/utils/utils.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "utils.h" - -namespace fs = std::filesystem; - -fs::path generateAndCreateDirName(const std::string& dirName) -{ - fs::path working_dir = fs::temp_directory_path() / dirName; - fs::remove_all(working_dir); - fs::create_directories(working_dir); - return working_dir; -} diff --git a/src/ui/action/handler/antares-study/area/create.cpp b/src/ui/action/handler/antares-study/area/create.cpp index 0348c2d253..781b3435e0 100644 --- a/src/ui/action/handler/antares-study/area/create.cpp +++ b/src/ui/action/handler/antares-study/area/create.cpp @@ -285,6 +285,9 @@ void Create::createActionsForAStandardAreaCopy(Context& ctx, bool copyPosition) *tsNode += prepro; *tsNode += new Action::AntaresStudy::Area::AllocationHydro(pOriginalAreaName); + //Hydro Max Power + *tsNode += new DataTimeseries(Data::timeSeriesHydroMaxPower, pOriginalAreaName); + // Thermal auto* area = ctx.extStudy->areas.findFromName(pOriginalAreaName); if (area) diff --git a/src/ui/action/handler/antares-study/area/timeseries.cpp b/src/ui/action/handler/antares-study/area/timeseries.cpp index 754d3ed737..a43bdc9577 100644 --- a/src/ui/action/handler/antares-study/area/timeseries.cpp +++ b/src/ui/action/handler/antares-study/area/timeseries.cpp @@ -57,6 +57,9 @@ DataTimeseries::DataTimeseries(Data::TimeSeries ts, const AnyString& areaname) : case Data::timeSeriesThermal: pInfos.caption << "Thermal : Timeseries"; break; + case Data::timeSeriesHydroMaxPower: + pInfos.caption << "Max-Power : Timeseries"; + break; default: break; } @@ -103,6 +106,9 @@ void DataTimeseries::registerViewsWL(Context& ctx) case Data::timeSeriesThermal: ctx.view["6:Thermal"]["1:TS"] = this; break; + case Data::timeSeriesHydroMaxPower: + ctx.view["7:Max-Power"]["1:TS"] = this; + break; default: break; } @@ -155,16 +161,12 @@ bool DataTimeseries::performWL(Context& ctx) } case Data::timeSeriesHydro: { - ctx.area->hydro.series->ror = source->hydro.series->ror; - ctx.area->hydro.series->storage = source->hydro.series->storage; - ctx.area->hydro.series->mingen = source->hydro.series->mingen; - - ctx.area->hydro.series->count = source->hydro.series->count; - - source->hydro.series->ror.unloadFromMemory(); - source->hydro.series->storage.unloadFromMemory(); - source->hydro.series->mingen.unloadFromMemory(); - + ctx.area->hydro.series->copyGenerationTS(*source->hydro.series); + break; + } + case Data::timeSeriesHydroMaxPower: + { + ctx.area->hydro.series->copyMaxPowerTS(*source->hydro.series); break; } case Data::timeSeriesThermal: diff --git a/src/ui/simulator/application/main/build/scenario-builder.cpp b/src/ui/simulator/application/main/build/scenario-builder.cpp index 645792f9ca..0618c51187 100644 --- a/src/ui/simulator/application/main/build/scenario-builder.cpp +++ b/src/ui/simulator/application/main/build/scenario-builder.cpp @@ -176,6 +176,21 @@ class hydroScBuilderPageMaker final : public simpleScBuilderPageMaker } }; +// Hydro Max Power ... +class hydroMaxPowerScBuilderPageMaker final : public simpleScBuilderPageMaker +{ + using simpleScBuilderPageMaker::simpleScBuilderPageMaker; + + Renderer::ScBuilderRendererBase* getRenderer() override + { + return new_check_allocation(); + } + Notebook::Page* addPageToNotebook() override + { + return notebook()->add(grid(), wxT("hydro-max-power"), wxT("Hydro-Max-Power")); + } +}; + // Wind ... class windScBuilderPageMaker final : public simpleScBuilderPageMaker { @@ -357,6 +372,10 @@ void ApplWnd::createNBScenarioBuilder() hydroScBuilderPageMaker hydroSBpageMaker(scenarioBuilderPanel, pScenarioBuilderNotebook); pageScBuilderHydro = hydroSBpageMaker.createPage(); + hydroMaxPowerScBuilderPageMaker hydroMaxPowerSBpageMaker(scenarioBuilderPanel, + pScenarioBuilderNotebook); + pageScBuilderHydroMaxPower = hydroMaxPowerSBpageMaker.createPage(); + windScBuilderPageMaker windSBpageMaker(scenarioBuilderPanel, pScenarioBuilderNotebook); pageScBuilderWind = windSBpageMaker.createPage(); diff --git a/src/ui/simulator/application/main/main.h b/src/ui/simulator/application/main/main.h index a870f91fb2..4d7caa3918 100644 --- a/src/ui/simulator/application/main/main.h +++ b/src/ui/simulator/application/main/main.h @@ -699,6 +699,7 @@ class ApplWnd final : public Component::Frame::WxLocalFrame, public Yuni::IEvent Component::Notebook::Page* pageScBuilderLoad; Component::Notebook::Page* pageScBuilderThermal; Component::Notebook::Page* pageScBuilderHydro; + Component::Notebook::Page* pageScBuilderHydroMaxPower; Component::Notebook::Page* pageScBuilderWind; Component::Notebook::Page* pageScBuilderSolar; Component::Notebook::Page* pageScBuilderNTC; diff --git a/src/ui/simulator/toolbox/components/datagrid/renderer/area/hydromonthlypower.cpp b/src/ui/simulator/toolbox/components/datagrid/renderer/area/hydromonthlypower.cpp index 9c0d2c0bfb..39aa340833 100644 --- a/src/ui/simulator/toolbox/components/datagrid/renderer/area/hydromonthlypower.cpp +++ b/src/ui/simulator/toolbox/components/datagrid/renderer/area/hydromonthlypower.cpp @@ -37,45 +37,45 @@ namespace Datagrid { namespace Renderer { -HydroMonthlyPower::HydroMonthlyPower(wxWindow* control, Toolbox::InputSelector::Area* notifier) : - MatrixAncestorType(control), Renderer::ARendererArea(control, notifier) +HydroMonthlyHours::HydroMonthlyHours(wxWindow* control, + Toolbox::InputSelector::Area* notifier, + HoursType type) : + MatrixAncestorType(control), Renderer::ARendererArea(control, notifier), hoursType(type) { } -HydroMonthlyPower::~HydroMonthlyPower() +HydroMonthlyHours::~HydroMonthlyHours() { destroyBoundEvents(); } -wxString HydroMonthlyPower::columnCaption(int colIndx) const +wxString HydroMonthlyHours::columnCaption(int colIndx) const { - switch (colIndx) + if (colIndx == 0 && hoursType == HoursType::Generation) { - case Data::PartHydro::genMaxP: - return wxT(" Generating Max Power \n (MW) "); - case Data::PartHydro::genMaxE: return wxT(" Generating Max Energy \n (Hours at Pmax) "); - case Data::PartHydro::pumpMaxP: - return wxT(" Pumping Max Power \n (MW) "); - case Data::PartHydro::pumpMaxE: - return wxT(" Pumping Max Energy \n (Hours at Pmax) "); - default: + } + else if (colIndx == 0 && hoursType == HoursType::Pumping) + { + return wxT(" Pumping Max Energy \n (Hours at Pmax) "); + } + else + { return wxEmptyString; } - return wxEmptyString; } -wxString HydroMonthlyPower::cellValue(int x, int y) const +wxString HydroMonthlyHours::cellValue(int x, int y) const { return MatrixAncestorType::cellValue(x, y); } -double HydroMonthlyPower::cellNumericValue(int x, int y) const +double HydroMonthlyHours::cellNumericValue(int x, int y) const { return MatrixAncestorType::cellNumericValue(x, y); } -bool HydroMonthlyPower::cellValue(int x, int y, const String& value) +bool HydroMonthlyHours::cellValue(int x, int y, const String& value) { double v; if (not value.to(v)) @@ -93,7 +93,7 @@ bool HydroMonthlyPower::cellValue(int x, int y, const String& value) return MatrixAncestorType::cellValue(x, y, String() << Math::Round(v, round)); } -void HydroMonthlyPower::internalAreaChanged(Antares::Data::Area* area) +void HydroMonthlyHours::internalAreaChanged(Antares::Data::Area* area) { // FIXME for some reasons, the variable study here is not properly initialized if (area && !study) @@ -101,67 +101,48 @@ void HydroMonthlyPower::internalAreaChanged(Antares::Data::Area* area) Data::PartHydro* pHydro = (area) ? &(area->hydro) : nullptr; Renderer::ARendererArea::internalAreaChanged(area); - if (pHydro) - MatrixAncestorType::matrix(&pHydro->maxPower); + if (pHydro && hoursType == HoursType::Generation) + MatrixAncestorType::matrix(&pHydro->maxDailyGenEnergy); + else if (pHydro && hoursType == HoursType::Pumping) + MatrixAncestorType::matrix(&pHydro->maxDailyPumpEnergy); else MatrixAncestorType::matrix(nullptr); } -IRenderer::CellStyle HydroMonthlyPower::cellStyle(int col, int row) const +IRenderer::CellStyle HydroMonthlyHours::cellStyle(int col, int row) const { - switch (col) - { - case 0: + if (double MaxE = MatrixAncestorType::cellNumericValue(0, row); + col == 0 && (MaxE < 0. || MaxE > 24.)) { - double genMaxP = MatrixAncestorType::cellNumericValue(0, row); - if (genMaxP < 0.) - return IRenderer::cellStyleError; - break; + return IRenderer::cellStyleError; } - case 1: - { - double genMaxE = MatrixAncestorType::cellNumericValue(1, row); - if (genMaxE < 0. || genMaxE > 24.) - return IRenderer::cellStyleError; - break; - } - case 2: - { - double PumpMaxP = MatrixAncestorType::cellNumericValue(2, row); - if (PumpMaxP < 0.) - return IRenderer::cellStyleError; - break; - } - case 3: + else { - double PumpMaxE = MatrixAncestorType::cellNumericValue(3, row); - if (PumpMaxE < 0. || PumpMaxE > 24.) - return IRenderer::cellStyleError; - break; - } + return IRenderer::cellStyleWithNumericCheck(col, row); } - return IRenderer::cellStyleWithNumericCheck(col, row); } -wxString HydroMonthlyPower::rowCaption(int row) const +wxString HydroMonthlyHours::rowCaption(int row) const { if (!study || row >= study->calendar.maxDaysInYear) return wxEmptyString; return wxStringFromUTF8(study->calendar.text.daysYear[row]); } -void HydroMonthlyPower::onStudyClosed() +void HydroMonthlyHours::onStudyClosed() { MatrixAncestorType::onStudyClosed(); Renderer::ARendererArea::onStudyClosed(); } -void HydroMonthlyPower::onStudyLoaded() +void HydroMonthlyHours::onStudyLoaded() { MatrixAncestorType::onStudyLoaded(); Renderer::ARendererArea::onStudyLoaded(); } +// Pump + } // namespace Renderer } // namespace Datagrid } // namespace Component diff --git a/src/ui/simulator/toolbox/components/datagrid/renderer/area/hydromonthlypower.h b/src/ui/simulator/toolbox/components/datagrid/renderer/area/hydromonthlypower.h index 9acd8213f2..a57b84b769 100644 --- a/src/ui/simulator/toolbox/components/datagrid/renderer/area/hydromonthlypower.h +++ b/src/ui/simulator/toolbox/components/datagrid/renderer/area/hydromonthlypower.h @@ -40,11 +40,17 @@ namespace Datagrid { namespace Renderer { -class HydroMonthlyPower final : public Renderer::Matrix, + +class HydroMonthlyHours final : public Renderer::Matrix, public Renderer::ARendererArea { public: using MatrixAncestorType = Renderer::Matrix; + enum class HoursType + { + Generation = 0, + Pumping + }; public: //! \name Constructor & Destructor @@ -52,20 +58,22 @@ class HydroMonthlyPower final : public Renderer::Matrix, /*! ** \brief Constructor */ - HydroMonthlyPower(wxWindow* control, Toolbox::InputSelector::Area* notifier); + HydroMonthlyHours(wxWindow* control, Toolbox::InputSelector::Area* notifier, HoursType type); //! Destructor - virtual ~HydroMonthlyPower(); + virtual ~HydroMonthlyHours(); //@} virtual int width() const { - return 4; + return 1; } virtual int height() const { return DAYS_PER_YEAR; } + HoursType hoursType; + virtual wxString columnCaption(int colIndx) const; virtual wxString rowCaption(int rowIndx) const; @@ -108,7 +116,7 @@ class HydroMonthlyPower final : public Renderer::Matrix, //! Event: the study has been loaded virtual void onStudyLoaded() override; -}; // class HydroMonthlyPower +}; // class HydroMonthlyHoursGen } // namespace Renderer } // namespace Datagrid diff --git a/src/ui/simulator/toolbox/components/datagrid/renderer/area/timeseries.h b/src/ui/simulator/toolbox/components/datagrid/renderer/area/timeseries.h index b9ed6f3576..60b83477c0 100644 --- a/src/ui/simulator/toolbox/components/datagrid/renderer/area/timeseries.h +++ b/src/ui/simulator/toolbox/components/datagrid/renderer/area/timeseries.h @@ -332,6 +332,70 @@ class TimeSeriesHydroMinGen final : public ATimeSeries } }; +class TimeSeriesHydroMaxHourlyGenPower final : public ATimeSeries +{ +public: + using AncestorType = Renderer::Matrix; + + TimeSeriesHydroMaxHourlyGenPower(wxWindow* control, Toolbox::InputSelector::Area* notifier) : + ATimeSeries(control, notifier) + { + } + ~TimeSeriesHydroMaxHourlyGenPower() override + { + destroyBoundEvents(); + } + + Date::Precision precision() override + { + return Date::hourly; + } + + uint maxHeightResize() const override + { + return HOURS_PER_YEAR; + } + +private: + void internalAreaChanged(Antares::Data::Area* area) override + { + matrix((area && CurrentStudyIsValid()) ? &(area->hydro.series->maxHourlyGenPower) : NULL); + Renderer::ARendererArea::internalAreaChanged(area); + } +}; + +class TimeSeriesHydroMaxHourlyPumpPower final : public ATimeSeries +{ +public: + using AncestorType = Renderer::Matrix; + + TimeSeriesHydroMaxHourlyPumpPower(wxWindow* control, Toolbox::InputSelector::Area* notifier) : + ATimeSeries(control, notifier) + { + } + ~TimeSeriesHydroMaxHourlyPumpPower() override + { + destroyBoundEvents(); + } + + Date::Precision precision() override + { + return Date::hourly; + } + + uint maxHeightResize() const override + { + return HOURS_PER_YEAR; + } + +private: + void internalAreaChanged(Antares::Data::Area* area) override + { + matrix((area && CurrentStudyIsValid()) ? &(area->hydro.series->maxHourlyPumpPower) : NULL); + Renderer::ARendererArea::internalAreaChanged(area); + } +}; + // ========================= // Clusters ... // ========================= diff --git a/src/ui/simulator/toolbox/components/datagrid/renderer/column.cpp b/src/ui/simulator/toolbox/components/datagrid/renderer/column.cpp index 064551856f..1363e9b39b 100644 --- a/src/ui/simulator/toolbox/components/datagrid/renderer/column.cpp +++ b/src/ui/simulator/toolbox/components/datagrid/renderer/column.cpp @@ -163,6 +163,26 @@ ColumnNTC::ColumnNTC() : Column(timeSeriesTransmissionCapacities, " Links NTC new_check_allocation(wxT("-"))}; } +// ------------------------------- +// Column Hydro Max Power +// ------------------------------- +ColumnHydroMaxPower::ColumnHydroMaxPower() : Column(timeSeriesHydroMaxPower, " Hydro-Max-Power ") +{ + cells_ = {new_check_allocation(), + new_check_allocation(wxT("On")), + new_check_allocation(wxT("-")), + new_check_allocation(wxT("-")), + new_check_allocation(wxT("-")), + new_check_allocation(wxT("-")), + new_check_allocation(wxT("-")), + new_check_allocation(wxT("-")), + new_check_allocation(wxT("-")), + new_check_allocation(wxT("-")), + new_check_allocation(), + new_check_allocation(tsKind_), + new_check_allocation(tsKind_)}; +} + } // namespace Renderer } // namespace Datagrid } // namespace Component diff --git a/src/ui/simulator/toolbox/components/datagrid/renderer/column.h b/src/ui/simulator/toolbox/components/datagrid/renderer/column.h index e18a2d7893..71b31aa0ec 100644 --- a/src/ui/simulator/toolbox/components/datagrid/renderer/column.h +++ b/src/ui/simulator/toolbox/components/datagrid/renderer/column.h @@ -98,6 +98,16 @@ class ColumnNTC final : public Column ~ColumnNTC() override = default; }; +// ------------------------------- +// Column for Hydro Max Power +// ------------------------------- +class ColumnHydroMaxPower final : public Column +{ +public: + ColumnHydroMaxPower(); + ~ColumnHydroMaxPower() override = default; +}; + } // namespace Renderer } // namespace Datagrid } // namespace Component diff --git a/src/ui/simulator/toolbox/components/datagrid/renderer/scenario-builder-hydro-renderer.cpp b/src/ui/simulator/toolbox/components/datagrid/renderer/scenario-builder-hydro-renderer.cpp index b0576280d7..752ef14eec 100644 --- a/src/ui/simulator/toolbox/components/datagrid/renderer/scenario-builder-hydro-renderer.cpp +++ b/src/ui/simulator/toolbox/components/datagrid/renderer/scenario-builder-hydro-renderer.cpp @@ -69,6 +69,36 @@ double hydroScBuilderRenderer::cellNumericValue(int x, int y) const return 0.; } +bool hydroMaxPowerScBuilderRenderer::cellValue(int x, int y, const Yuni::String& value) +{ + if (!(!study) && !(!pRules) && (uint)x < study->parameters.nbYears) + { + if ((uint)y < study->areas.size()) + { + assert((uint)y < pRules->hydroMaxPower.width()); + assert((uint)x < pRules->hydroMaxPower.height()); + uint val = fromStringToTSnumber(value); + pRules->hydroMaxPower.set_value(x, y, val); + return true; + } + } + return false; +} + +double hydroMaxPowerScBuilderRenderer::cellNumericValue(int x, int y) const +{ + if (!(!study) && !(!pRules) && (uint)x < study->parameters.nbYears) + { + if ((uint)y < study->areas.size()) + { + assert((uint)y < pRules->hydroMaxPower.width()); + assert((uint)x < pRules->hydroMaxPower.height()); + return pRules->hydroMaxPower.get_value(x, y); + } + } + return 0.; +} + } // namespace Renderer } // namespace Datagrid } // namespace Component diff --git a/src/ui/simulator/toolbox/components/datagrid/renderer/scenario-builder-hydro-renderer.h b/src/ui/simulator/toolbox/components/datagrid/renderer/scenario-builder-hydro-renderer.h index d0b8480f28..80ba31ebe6 100644 --- a/src/ui/simulator/toolbox/components/datagrid/renderer/scenario-builder-hydro-renderer.h +++ b/src/ui/simulator/toolbox/components/datagrid/renderer/scenario-builder-hydro-renderer.h @@ -46,6 +46,15 @@ class hydroScBuilderRenderer : public ScBuilderRendererAreasAsRows double cellNumericValue(int x, int y) const; }; // class hydroScBuilderRenderer +class hydroMaxPowerScBuilderRenderer : public ScBuilderRendererAreasAsRows +{ +public: + hydroMaxPowerScBuilderRenderer() = default; + + bool cellValue(int x, int y, const Yuni::String& value); + double cellNumericValue(int x, int y) const; +}; // class hydroMaxPowerScBuilderRenderer + } // namespace Renderer } // namespace Datagrid } // namespace Component diff --git a/src/ui/simulator/toolbox/components/datagrid/renderer/ts-management.cpp b/src/ui/simulator/toolbox/components/datagrid/renderer/ts-management.cpp index d4a25498ca..69e212256b 100644 --- a/src/ui/simulator/toolbox/components/datagrid/renderer/ts-management.cpp +++ b/src/ui/simulator/toolbox/components/datagrid/renderer/ts-management.cpp @@ -46,6 +46,7 @@ TSmanagement::TSmanagement() : pControl(nullptr) columns_.push_back(new classicColumn(timeSeriesLoad, " Load ")); columns_.push_back(new thermalColumn()); columns_.push_back(new classicColumn(timeSeriesHydro, " Hydro ")); + columns_.push_back(new ColumnHydroMaxPower()); } void TSmanagement::checkLineNumberInColumns() diff --git a/src/ui/simulator/windows/hydro/dailypower.cpp b/src/ui/simulator/windows/hydro/dailypower.cpp index 8a99499e05..7b87c56c99 100644 --- a/src/ui/simulator/windows/hydro/dailypower.cpp +++ b/src/ui/simulator/windows/hydro/dailypower.cpp @@ -96,16 +96,30 @@ void Dailypower::createComponents() wxBoxSizer* ssGridsLow = new wxBoxSizer(wxHORIZONTAL); - ssGridsLow->Add( - new Component::Datagrid::Component( - pSupport, - new Component::Datagrid::Renderer::HydroMonthlyPower(this, pInputAreaSelector), - wxT("Standard Credits (calendar)")), - 3, - wxALL | wxEXPAND, - 5); + ssGridsLow->Add(new Component::Datagrid::Component( + pSupport, + new Component::Datagrid::Renderer::HydroMonthlyHours( + this, + pInputAreaSelector, + Component::Datagrid::Renderer::HydroMonthlyHours::HoursType::Generation), + wxT("Standard Credits Calendar (Maximum Generation)")), + 3, + wxALL | wxEXPAND, + 5); + sizer->Add(ssGridsLow, 4, wxALL | wxEXPAND | wxFIXED_MINSIZE); + ssGridsLow->Add(new Component::Datagrid::Component( + pSupport, + new Component::Datagrid::Renderer::HydroMonthlyHours( + this, + pInputAreaSelector, + Component::Datagrid::Renderer::HydroMonthlyHours::HoursType::Pumping), + wxT("Standard Credits Calendar (Maximum Pumping)")), + 3, + wxALL | wxEXPAND, + 5); + sizer->Layout(); } diff --git a/src/ui/simulator/windows/hydro/series.cpp b/src/ui/simulator/windows/hydro/series.cpp index 36be9a658c..310fc98d9a 100644 --- a/src/ui/simulator/windows/hydro/series.cpp +++ b/src/ui/simulator/windows/hydro/series.cpp @@ -57,6 +57,14 @@ Series::Series(wxWindow* parent, Toolbox::InputSelector::Area* notifier) : com->renderer(new Component::Datagrid::Renderer::TimeSeriesHydroMinGen(com, notifier)); pPageFatal = notebook->add(com, wxT("Minimum Generation")); + com = new Component::Datagrid::Component(notebook); + com->renderer(new Component::Datagrid::Renderer::TimeSeriesHydroMaxHourlyGenPower(com, notifier)); + pPageFatal = notebook->add(com, wxT("Maximum Generation")); + + com = new Component::Datagrid::Component(notebook); + com->renderer(new Component::Datagrid::Renderer::TimeSeriesHydroMaxHourlyPumpPower(com, notifier)); + pPageFatal = notebook->add(com, wxT("Maximum Pumping")); + // Connection to the notifier if (pNotifier) pNotifier->onAreaChanged.connect(this, &Series::onAreaChanged);