diff --git a/docs/reference-guide/03-commands.md b/docs/reference-guide/03-commands.md index 5c45d7256a..9b42c4d7fd 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 9f54726f36..40abdc3574 100644 --- a/docs/reference-guide/04-active_windows.md +++ b/docs/reference-guide/04-active_windows.md @@ -99,10 +99,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 @@ -334,11 +334,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 @@ -352,7 +352,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 ,...)_ @@ -415,10 +416,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 74ae71e2b3..598c68793f 100644 --- a/docs/reference-guide/13-file-format.md +++ b/docs/reference-guide/13-file-format.md @@ -1,5 +1,16 @@ # 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. +## v9.1.0 +### (Input) Hydro Maximum Generation/Pumping Power +* 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. Starting from v9.0, file **input/hydro/common/capacity/maxpower_<area>** is no longer used. +### How to upgrade capacity time-series ? +Source file = maxpower_<area>, destination files are maxHourlyGenPower.txt and maxHourlyPumpPower.txt. To upgrade time-series, you need to +1. For generation, multiply 1st and 2nd rows. For pumping, multiply 3rd and 4rd rows. +2. Duplicate values 24 times : source has 365 rows, destination has 365 * 24 = 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 cleaning a study. +* Under `Configure/MC Scenario Builder` new section is added `Hydro Max Power` +* In the existing file **settings/scenariobuilder.dat**, under **<ruleset>** section following properties added: **hgp,<area>,<year> = <hgp-value>** + ## v9.0.0 ### Input ### Study version @@ -13,6 +24,7 @@ version = 9.0 ``` Compatibility is kept with versions up to 8.8.0. Starting from version 9.0.0, the new format must be used. + ## v8.8.0 ### Input #### Short-term storage diff --git a/src/libs/antares/series/series.cpp b/src/libs/antares/series/series.cpp index c12cd880ef..19fdab4cc5 100644 --- a/src/libs/antares/series/series.cpp +++ b/src/libs/antares/series/series.cpp @@ -135,5 +135,4 @@ uint64_t TimeSeries::memoryUsage() const { return timeSeries.memoryUsage(); } - } // namespace Antares::Data diff --git a/src/libs/antares/study/CMakeLists.txt b/src/libs/antares/study/CMakeLists.txt index 2f61b665d2..c240990cd6 100644 --- a/src/libs/antares/study/CMakeLists.txt +++ b/src/libs/antares/study/CMakeLists.txt @@ -23,6 +23,8 @@ set(SRC_STUDY_SCENARIO_BUILDER include/antares/study/scenario-builder/ThermalTSNumberData.h include/antares/study/scenario-builder/HydroTSNumberData.h scenario-builder/HydroTSNumberData.cpp + include/antares/study/scenario-builder/HydroMaxPowerTSNumberData.h + scenario-builder/HydroMaxPowerTSNumberData.cpp scenario-builder/SolarTSNumberData.cpp include/antares/study/scenario-builder/solarTSNumberData.h include/antares/study/scenario-builder/WindTSNumberData.h @@ -127,6 +129,8 @@ set(SRC_STUDY_PART_HYDRO include/antares/study/parts/hydro/allocation.h include/antares/study/parts/hydro/allocation.hxx parts/hydro/allocation.cpp + include/antares/study/parts/hydro/hydromaxtimeseriesreader.h + parts/hydro/hydromaxtimeseriesreader.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 a8deec80a2..038d120f60 100644 --- a/src/libs/antares/study/area/area.cpp +++ b/src/libs/antares/study/area/area.cpp @@ -259,6 +259,7 @@ void Area::resizeAllTimeseriesNumbers(uint nbYears) solar.series.timeseriesNumbers.reset(1, nbYears); wind.series.timeseriesNumbers.reset(1, nbYears); hydro.series->timeseriesNumbers.reset(1, nbYears); + hydro.series->timeseriesNumbersHydroMaxPower.reset(1, nbYears); for (auto& namedLink : links) { AreaLink* link = namedLink.second; diff --git a/src/libs/antares/study/area/list.cpp b/src/libs/antares/study/area/list.cpp index 8b25d0fe60..57b2a2dcb2 100644 --- a/src/libs/antares/study/area/list.cpp +++ b/src/libs/antares/study/area/list.cpp @@ -874,11 +874,38 @@ 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 + + auto* hydroSeries = area.hydro.series; + if (!options.loadOnlyNeeded || !area.hydro.prepro) // Series { buffer.clear() << study.folderInput << SEP << "hydro" << SEP << "series"; - ret = area.hydro.series->loadFromFolder(study, area.id, buffer) && ret; + ret = hydroSeries->loadGenerationTS(area.id, buffer, study.header.version) && ret; + + hydroSeries->EqualizeGenerationTSsizes(area, study.usedByTheSolver); } + + if (study.header.version < StudyVersion(9,1)) + { + buffer.clear() << study.folderInput << SEP << "hydro"; + + HydroMaxTimeSeriesReader reader(area.hydro, area.id.to(), area.name.to()); + ret = reader.read(buffer, study.usedByTheSolver) && ret; + } + else + { + buffer.clear() << study.folderInput << SEP << "hydro" << SEP << "series"; + ret = hydroSeries->LoadMaxPower(area.id, buffer) && ret; + + if (study.usedByTheSolver) + { + hydroSeries->EqualizeMaxPowerTSsizes(area); + } + else + hydroSeries->setHydroModulability(area); + } + + hydroSeries->resizeTSinDeratedMode( + study.parameters.derated, study.header.version, study.usedByTheSolver); } // Wind @@ -1522,9 +1549,7 @@ void AreaList::removeLoadTimeseries() void AreaList::removeHydroTimeseries() { - each([](Data::Area& area) { - area.hydro.series->reset(); - }); + each([](Data::Area& area) { area.hydro.series->reset(); }); } void AreaList::removeSolarTimeseries() diff --git a/src/libs/antares/study/area/scratchpad.cpp b/src/libs/antares/study/area/scratchpad.cpp index 8ce9f988fd..58f45d32ce 100644 --- a/src/libs/antares/study/area/scratchpad.cpp +++ b/src/libs/antares/study/area/scratchpad.cpp @@ -29,7 +29,36 @@ using namespace Yuni; namespace Antares::Data { -AreaScratchpad::AreaScratchpad(const StudyRuntimeInfos& rinfos, Area& area) +bool doWeHaveOnePositiveMaxDailyEnergy(const Matrix& dailyPower, + const Matrix::ColumnType& nbHoursAtPmaxPerDay) +{ + for (uint tsNumber = 0; tsNumber < dailyPower.width; ++tsNumber) + { + for (uint day = 0; day < DAYS_PER_YEAR; ++day) + { + if (dailyPower[tsNumber][day] * nbHoursAtPmaxPerDay[day] > 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.; + } +} + +AreaScratchpad::AreaScratchpad(const StudyRuntimeInfos& rinfos, Area& area) : + meanMaxDailyGenPower(area.hydro.series->timeseriesNumbersHydroMaxPower), + meanMaxDailyPumpPower(area.hydro.series->timeseriesNumbersHydroMaxPower) { // alias to the simulation mode auto mode = rinfos.mode; @@ -44,12 +73,6 @@ AreaScratchpad::AreaScratchpad(const StudyRuntimeInfos& rinfos, Area& area) 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; @@ -71,6 +94,37 @@ AreaScratchpad::AreaScratchpad(const StudyRuntimeInfos& rinfos, Area& area) } } + //******************************************************************************* + // TODO : about computing hydro max power daily mean from hourly max power TS. + //******************************************************************************* + // - This computation is done here, but we don't want it here. + // We want Scratchpad to shrink and even disappear. + // So a possible solution to move this computation to some place else is to host + // these means TS in the hydro part of areas, and compute them right after + // their the hourly TS (max power). + // Note that scratchpad instances are duplicated for multi-threading purpose, + // and that moving these TS elsewhere could create concurrency issues. + // But these daily TS, once computed, are then only read (in daily.cpp + // and when building the weekly optimization problem). + // Thus we don't have to fear such issues. + // - Besides, there is a performance problem here : for a given area, we compute + // the max power daily means for each call to scratchpad constructor, that is + // the same computation for each thread. + // This is another reason to move the computation from here. + //******************************************************************************* + + // Hourly maximum generation/pumping power matrices and their number of TS's (width of matrices) + auto const& maxHourlyGenPower = area.hydro.series->maxHourlyGenPower.timeSeries; + auto const& maxHourlyPumpPower = area.hydro.series->maxHourlyPumpPower.timeSeries; + uint nbOfMaxPowerTimeSeries = area.hydro.series->maxPowerTScount(); + + // Setting width and height of daily mean maximum generation/pumping power matrices + meanMaxDailyGenPower.timeSeries.reset(nbOfMaxPowerTimeSeries, DAYS_PER_YEAR); + meanMaxDailyPumpPower.timeSeries.reset(nbOfMaxPowerTimeSeries, DAYS_PER_YEAR); + + // Instantiate daily mean maximum generation/pumping power matrices + CalculateMeanDailyMaxPowerMatrices(maxHourlyGenPower, maxHourlyPumpPower, nbOfMaxPowerTimeSeries); + // =============== // hydroHasMod // =============== @@ -81,20 +135,10 @@ AreaScratchpad::AreaScratchpad(const StudyRuntimeInfos& rinfos, Area& area) // Useful whether we use a heuristic target or not bool hydroGenerationPermission = false; - // ... Getting hydro max power - auto const& maxPower = area.hydro.maxPower; + // ... Getting hydro max energy + auto const& dailyNbHoursAtGenPmax = area.hydro.dailyNbHoursAtGenPmax[0]; - // ... Hydro max generating power and energy - auto const& maxGenP = maxPower[Data::PartHydro::genMaxP]; - auto const& maxGenE = maxPower[Data::PartHydro::genMaxE]; - - 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 = doWeHaveOnePositiveMaxDailyEnergy(meanMaxDailyGenPower.timeSeries, dailyNbHoursAtGenPmax); // --------------------- // Hydro has inflows @@ -123,30 +167,31 @@ AreaScratchpad::AreaScratchpad(const StudyRuntimeInfos& rinfos, Area& area) // -------------------------- 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& dailyNbHoursAtPumpPmax = area.hydro.dailyNbHoursAtPumpPmax[0]; - // ... Pumping max power - for (uint d = 0; d != DAYS_PER_YEAR; ++d) - pumpingMaxPower[d] = maxPumpingP[d]; - - double valuePumping = 0.; - // ... Computing 'pumpHasMod' parameter - for (uint d = 0; d < DAYS_PER_YEAR; ++d) - valuePumping += maxPumpingP[d] * maxPumpingE[d]; - - // If pumping energy is nil over the whole year, pumpHasMod is false, true otherwise. - pumpHasMod = (valuePumping > 0.); + // If pumping energy is nil over the whole year, pumpHasMod is false, true otherwise. + pumpHasMod = doWeHaveOnePositiveMaxDailyEnergy(meanMaxDailyPumpPower.timeSeries, dailyNbHoursAtPumpPmax); } -AreaScratchpad::~AreaScratchpad() = default; +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.timeSeries[nbOfTimeSeries]; + auto& MeanMaxDailyPumpPowerColumn = meanMaxDailyPumpPower.timeSeries[nbOfTimeSeries]; + CalculateDailyMeanPower(hourlyMaxGenColumn, MeanMaxDailyGenPowerColumn); + CalculateDailyMeanPower(hourlyMaxPumpColumn, MeanMaxDailyPumpPowerColumn); + } +} } // namespace Antares::Data diff --git a/src/libs/antares/study/area/store-timeseries-numbers.cpp b/src/libs/antares/study/area/store-timeseries-numbers.cpp index e35ca9b184..a97812507e 100644 --- a/src/libs/antares/study/area/store-timeseries-numbers.cpp +++ b/src/libs/antares/study/area/store-timeseries-numbers.cpp @@ -42,10 +42,10 @@ struct TSNumbersPredicate }; } // anonymous namespace -static void genericStoreTimeseriesNumbers(Solver::IResultWriter& writer, - const Matrix& timeseriesNumbers, - const String& id, - const String& directory) +static void storeTSnumbers(Solver::IResultWriter& writer, + const Matrix& timeseriesNumbers, + const String& id, + const String& directory) { TSNumbersPredicate predicate; Clob path; @@ -63,22 +63,28 @@ static void genericStoreTimeseriesNumbers(Solver::IResultWriter& writer, void storeTimeseriesNumbersForLoad(Solver::IResultWriter& 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& 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& 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& writer, const Area& area) +{ + storeTSnumbers(writer, area.hydro.series->timeseriesNumbersHydroMaxPower, area.id, "hgp"); } void storeTimeseriesNumbersForWind(Solver::IResultWriter& 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& writer, const Area& area) diff --git a/src/libs/antares/study/cleaner/cleaner-v20.cpp b/src/libs/antares/study/cleaner/cleaner-v20.cpp index e089cce58f..904aca7ef1 100644 --- a/src/libs/antares/study/cleaner/cleaner-v20.cpp +++ b/src/libs/antares/study/cleaner/cleaner-v20.cpp @@ -79,7 +79,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); @@ -90,7 +92,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/include/antares/study/area/area.h b/src/libs/antares/study/include/antares/study/area/area.h index 1db011762f..b6baf3f4d3 100644 --- a/src/libs/antares/study/include/antares/study/area/area.h +++ b/src/libs/antares/study/include/antares/study/area/area.h @@ -331,7 +331,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/include/antares/study/area/scratchpad.h b/src/libs/antares/study/include/antares/study/area/scratchpad.h index 9ee8609e75..7dde2a03ee 100644 --- a/src/libs/antares/study/include/antares/study/area/scratchpad.h +++ b/src/libs/antares/study/include/antares/study/area/scratchpad.h @@ -28,10 +28,10 @@ #include #include #include +#include +#include -namespace Antares -{ -namespace Data +namespace Antares::Data { /*! ** \brief Scratchpad for temporary data performed by the solver @@ -49,7 +49,7 @@ class AreaScratchpad final */ AreaScratchpad(const StudyRuntimeInfos& rinfos, Area& area); //! Destructor - ~AreaScratchpad(); + ~AreaScratchpad() = default; //@} //! Sum of all fatal hors hydro @@ -69,22 +69,36 @@ 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 + */ + TimeSeries meanMaxDailyGenPower; + TimeSeries 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 +} // namespace Antares::Data #endif // __ANTARES_LIBS_STUDY_AREA_SCRATCHPAD_H__ diff --git a/src/libs/antares/study/include/antares/study/area/store-timeseries-numbers.h b/src/libs/antares/study/include/antares/study/area/store-timeseries-numbers.h index 27e505ee65..35a0510e1d 100644 --- a/src/libs/antares/study/include/antares/study/area/store-timeseries-numbers.h +++ b/src/libs/antares/study/include/antares/study/area/store-timeseries-numbers.h @@ -40,6 +40,7 @@ namespace Antares::Data void storeTimeseriesNumbersForThermal(Solver::IResultWriter& writer, const Area& area); void storeTimeseriesNumbersForRenewable(Solver::IResultWriter& writer, const Area& area); void storeTimeseriesNumbersForTransmissionCapacities(Solver::IResultWriter& writer, const Area& area); + void storeTimeseriesNumbersForHydroMaxPower(Solver::IResultWriter& writer, const Area& area); } //Antares::Data diff --git a/src/libs/antares/study/include/antares/study/area/store-timeseries-numbers.hxx b/src/libs/antares/study/include/antares/study/area/store-timeseries-numbers.hxx index b75f86116a..6f48041342 100644 --- a/src/libs/antares/study/include/antares/study/area/store-timeseries-numbers.hxx +++ b/src/libs/antares/study/include/antares/study/area/store-timeseries-numbers.hxx @@ -56,7 +56,9 @@ void singleAreaStoreTimeseriesNumbers(Solver::IResultWriter& writer, const Area& case timeSeriesTransmissionCapacities: storeTimeseriesNumbersForTransmissionCapacities(writer, area); break; - case timeSeriesCount: + case timeSeriesHydroMaxPower: + storeTimeseriesNumbersForHydroMaxPower(writer, area); + break; default: break; } diff --git a/src/libs/antares/study/include/antares/study/fwd.h b/src/libs/antares/study/include/antares/study/fwd.h index 13feafb41d..c5e5933691 100644 --- a/src/libs/antares/study/include/antares/study/fwd.h +++ b/src/libs/antares/study/include/antares/study/fwd.h @@ -198,6 +198,8 @@ std::string styleToString(const StyleType& style); ** ** These values are mainly used for mask bits */ +static const unsigned int timeSeriesCount = 8; + enum TimeSeriesType : unsigned int { //! TimeSeries : Load @@ -214,11 +216,14 @@ enum TimeSeriesType : unsigned int timeSeriesRenewable = 32, //! TimeSeries : Renewable timeSeriesTransmissionCapacities = 64, + //! TimeSeries : Hydro Max Power + timeSeriesHydroMaxPower = 128, + // *********************************************************************** + // Please update the constant timeSeriesCount if you add / remove an item + // *********************************************************************** +}; // enum TimeSeries - //! The maximum number of time-series that we can encounter - timeSeriesCount = 7, -}; // enum TimeSeries template struct TimeSeriesBitPatternIntoIndex; diff --git a/src/libs/antares/study/include/antares/study/parameters.h b/src/libs/antares/study/include/antares/study/parameters.h index c1525ddabc..ca7a774245 100644 --- a/src/libs/antares/study/include/antares/study/parameters.h +++ b/src/libs/antares/study/include/antares/study/parameters.h @@ -154,6 +154,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/include/antares/study/parts/hydro/container.h b/src/libs/antares/study/include/antares/study/parts/hydro/container.h index 0b2dca536f..3556da7222 100644 --- a/src/libs/antares/study/include/antares/study/parts/hydro/container.h +++ b/src/libs/antares/study/include/antares/study/parts/hydro/container.h @@ -44,18 +44,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 @@ -105,6 +93,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; @@ -140,9 +135,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; @@ -163,12 +155,16 @@ class PartHydro //! Data for the pre-processor PreproHydro* prepro; + //! Data for time-series DataSeriesHydro* series; + // TODO : following time series could be hosted by the series data member above (of type DataSeriesHydro), + // which contains other time. + Matrix dailyNbHoursAtGenPmax; + Matrix dailyNbHoursAtPumpPmax; }; // 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 // as arguments, so that we don't have to create them locally (as in a classical function) each diff --git a/src/libs/antares/study/include/antares/study/parts/hydro/hydromaxtimeseriesreader.h b/src/libs/antares/study/include/antares/study/parts/hydro/hydromaxtimeseriesreader.h new file mode 100644 index 0000000000..f8295cb873 --- /dev/null +++ b/src/libs/antares/study/include/antares/study/parts/hydro/hydromaxtimeseriesreader.h @@ -0,0 +1,84 @@ +/* +** 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__ + +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(PartHydro& hydro, std::string areaID, std::string areaName); + + bool read(const AnyString& folder, bool usedBySolver); + + enum powerDailyE + { + //! Generated max power + genMaxP = 0, + //! Generated max energy + genMaxE, + //! Pumping max Power + pumpMaxP, + // Pumping max Energy + pumpMaxE, + }; + +private: + Matrix dailyMaxPumpAndGen; + + PartHydro& hydro_; + std::string areaID_; + std::string areaName_; + + /** + * \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, + bool usedBySolver); + + /** + * \brief Copy energy functions + * These functions provides coping of energy data loaded + * from deprecated file. + */ + void copyDailyMaxEnergy() const; + void copyDailyMaxGenerationEnergy() const; + void copyDailyMaxPumpingEnergy() 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/include/antares/study/parts/hydro/series.h b/src/libs/antares/study/include/antares/study/parts/hydro/series.h index 3428a33203..6339321b69 100644 --- a/src/libs/antares/study/include/antares/study/parts/hydro/series.h +++ b/src/libs/antares/study/include/antares/study/parts/hydro/series.h @@ -23,12 +23,15 @@ #include #include +#include #include "../../fwd.h" + namespace Antares { namespace Data { + /*! ** \brief Data series (Hydro) */ @@ -44,6 +47,7 @@ class DataSeriesHydro //@} void copyGenerationTS(const DataSeriesHydro& source); + void copyMaxPowerTS(const DataSeriesHydro& source); //! \name Data //@{ @@ -52,8 +56,9 @@ class DataSeriesHydro */ void reset(); - void resize_ROR_STORAGE_MINGEN_whenGeneratedTS(unsigned int width); - void resizeGenerationTS(unsigned int w, unsigned int h); + void resizeGenerationTS(uint nbSeries); + void resizeMaxPowerTS(uint nbSeries); + /*! ** \brief Load all data not already loaded @@ -65,16 +70,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); + + // Loading hydro time series collection + // Returned boolean : reading from file failed + bool loadGenerationTS(const AreaName& areaID, const AnyString& folder, StudyVersion version); + + // Loading hydro max generation and mqx pumping TS's + bool LoadMaxPower(const AreaName& areaID, const AnyString& folder); + + void buildHourlyMaxPowerFromDailyTS(const Matrix::ColumnType& DailyMaxGenPower, + const Matrix::ColumnType& DailyMaxPumpPower); /*! ** \brief Save data series for hydro into a folder (`input/hydro/series`) @@ -100,12 +106,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) ** @@ -129,26 +129,53 @@ class DataSeriesHydro */ TimeSeries mingen; - unsigned int TScount() const { return count; }; - /*! - ** \brief Monte-Carlo + ** \brief Maximum Generation (MW) + ** + ** Merely a matrix of TimeSeriesCount * HOURS_PER_YEAR values */ - Matrix timeseriesNumbers; + + TimeSeries maxHourlyGenPower; /*! - ** \brief The number of time-series + ** \brief Maximum Pumping (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 */ + TimeSeries 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); + + 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; + void setMaxPowerTScount(uint count) { maxPowerTScount_ = count;} + + // Setting TS's when derated mode is on + void resizeTSinDeratedMode(bool derated, StudyVersion version, bool useBySolver); + private: - uint count = 0; + + // 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; -}; // class DataSeriesHydro + // 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 } // namespace Data } // namespace Antares diff --git a/src/libs/antares/study/include/antares/study/parts/parts.h b/src/libs/antares/study/include/antares/study/parts/parts.h index 46d49c5d5b..7f02083486 100644 --- a/src/libs/antares/study/include/antares/study/parts/parts.h +++ b/src/libs/antares/study/include/antares/study/parts/parts.h @@ -32,6 +32,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/include/antares/study/scenario-builder/HydroMaxPowerTSNumberData.h b/src/libs/antares/study/include/antares/study/scenario-builder/HydroMaxPowerTSNumberData.h new file mode 100644 index 0000000000..0cdc741693 --- /dev/null +++ b/src/libs/antares/study/include/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/include/antares/study/scenario-builder/applyToMatrix.hxx b/src/libs/antares/study/include/antares/study/scenario-builder/applyToMatrix.hxx index e6817d2083..6bb8b1ac61 100644 --- a/src/libs/antares/study/include/antares/study/scenario-builder/applyToMatrix.hxx +++ b/src/libs/antares/study/include/antares/study/scenario-builder/applyToMatrix.hxx @@ -67,6 +67,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, @@ -111,4 +118,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/include/antares/study/scenario-builder/rules.h b/src/libs/antares/study/include/antares/study/scenario-builder/rules.h index 56d9a3a442..8205d7a8b3 100644 --- a/src/libs/antares/study/include/antares/study/scenario-builder/rules.h +++ b/src/libs/antares/study/include/antares/study/scenario-builder/rules.h @@ -34,6 +34,7 @@ #include "HydroTSNumberData.h" #include "WindTSNumberData.h" #include "LoadTSNumberData.h" +#include "HydroMaxPowerTSNumberData.h" #include #include @@ -109,6 +110,8 @@ class Rules final : private Yuni::NonCopyable solarTSNumberData solar; //! Hydro hydroTSNumberData hydro; + //! Hydro Max Power + hydroMaxPowerTSNumberData hydroMaxPower; //! Wind windTSNumberData wind; @@ -132,6 +135,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/libs/antares/study/include/antares/study/study.hxx b/src/libs/antares/study/include/antares/study/study.hxx index 0d71014e2e..9f0053d0a4 100644 --- a/src/libs/antares/study/include/antares/study/study.hxx +++ b/src/libs/antares/study/include/antares/study/study.hxx @@ -56,7 +56,7 @@ inline void Study::destroyTSGeneratorData() case TimeSeriesType::timeSeriesThermal: destroyAllThermalTSGeneratorData(); break; - case TimeSeriesType::timeSeriesCount: + default: break; } } diff --git a/src/libs/antares/study/parameters.cpp b/src/libs/antares/study/parameters.cpp index d6a36b59b2..1a5fa3450b 100644 --- a/src/libs/antares/study/parameters.cpp +++ b/src/libs/antares/study/parameters.cpp @@ -68,6 +68,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; @@ -384,6 +386,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); } @@ -1056,6 +1064,8 @@ bool Parameters::loadFromINI(const IniFile& ini, StudyVersion& version, const St fixGenRefreshForNTC(); + fixGenRefreshForHydroMaxPower(); + // Specific action before launching a simulation if (options.usedByTheSolver) prepareForSimulation(options); @@ -1114,6 +1124,22 @@ void Parameters::fixGenRefreshForNTC() } } +void Parameters::fixGenRefreshForHydroMaxPower() +{ + if ((timeSeriesHydroMaxPower & timeSeriesToGenerate) != 0) + { + timeSeriesToGenerate &= ~timeSeriesHydroMaxPower; + logs.warning() << "Time-series generation is not available for hydro max power. It " + "will be automatically disabled."; + } + if ((timeSeriesHydroMaxPower & timeSeriesToRefresh) != 0) + { + timeSeriesToRefresh &= ~timeSeriesHydroMaxPower; + logs.warning() << "Time-series refresh is not available for hydro max power. It will " + "be automatically disabled."; + } +} + void Parameters::fixBadValues() { if (derated) @@ -1352,7 +1378,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/parts/hydro/container.cpp b/src/libs/antares/study/parts/hydro/container.cpp index d5826d39a1..85b8acd4ad 100644 --- a/src/libs/antares/study/parts/hydro/container.cpp +++ b/src/libs/antares/study/parts/hydro/container.cpp @@ -22,6 +22,7 @@ #include "antares/study/study.h" #include "antares/study/parts/hydro/container.h" #include +#include "antares/study/parts/hydro/hydromaxtimeseriesreader.h" using namespace Antares; using namespace Yuni; @@ -83,9 +84,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.); + dailyNbHoursAtGenPmax.reset(1, DAYS_PER_YEAR, true); + dailyNbHoursAtGenPmax.fillColumn(0, 24.); + dailyNbHoursAtPumpPmax.reset(1, DAYS_PER_YEAR, true); + dailyNbHoursAtPumpPmax.fillColumn(0, 24.); creditModulation.reset(101, 2, true); creditModulation.fill(1); // reset of the hydro allocation - however we don't have any information @@ -106,146 +108,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 >= StudyVersion(9, 1)) + { + // 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 + // dailyNbHoursAtGenPmax and dailyNbHoursAtPumpPmax 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 (unsigned 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 (unsigned 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 (unsigned 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 (unsigned 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")) @@ -680,10 +657,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.dailyNbHoursAtGenPmax.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.dailyNbHoursAtPumpPmax.saveToCSVFile(buffer, /*decimal*/ 2) && ret; // credit modulations buffer.clear() << folder << SEP << "common" << SEP << "capacity" << SEP << "creditmodulations_" << area.id << ".txt"; @@ -709,11 +691,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 = dailyNbHoursAtGenPmax.forceReload(reload) && ret; + ret = dailyNbHoursAtPumpPmax.forceReload(reload) && ret; if (series) ret = series->forceReload(reload) && ret; @@ -725,11 +708,12 @@ bool PartHydro::forceReload(bool reload) const void PartHydro::markAsModified() const { - maxPower.markAsModified(); inflowPattern.markAsModified(); reservoirLevel.markAsModified(); waterValues.markAsModified(); creditModulation.markAsModified(); + dailyNbHoursAtGenPmax.markAsModified(); + dailyNbHoursAtPumpPmax.markAsModified(); if (series) series->markAsModified(); @@ -739,12 +723,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; @@ -787,6 +765,70 @@ void PartHydro::copyFrom(const PartHydro& rhs) leewayLowerBound = rhs.leewayLowerBound; pumpingEfficiency = rhs.pumpingEfficiency; } + + // max daily gen + { + dailyNbHoursAtGenPmax = rhs.dailyNbHoursAtGenPmax; + dailyNbHoursAtGenPmax.unloadFromMemory(); + rhs.dailyNbHoursAtGenPmax.unloadFromMemory(); + } + + // max daily pump + { + dailyNbHoursAtPumpPmax = rhs.dailyNbHoursAtPumpPmax; + dailyNbHoursAtPumpPmax.unloadFromMemory(); + rhs.dailyNbHoursAtPumpPmax.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 = dailyNbHoursAtGenPmax.loadFromCSVFile( + filePath, 1, DAYS_PER_YEAR, Matrix<>::optFixedSize, &fileContent) + && ret; + + filePath.clear() << folder << SEP << "common" << SEP << "capacity" << SEP + << "maxDailyPumpEnergy_" << areaid << ".txt"; + + ret = dailyNbHoursAtPumpPmax.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 = dailyNbHoursAtGenPmax[0]; + auto& colPump = dailyNbHoursAtPumpPmax[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/hydromaxtimeseriesreader.cpp b/src/libs/antares/study/parts/hydro/hydromaxtimeseriesreader.cpp new file mode 100644 index 0000000000..fb4909a4a8 --- /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 "antares/study/study.h" +#include "antares/study/parts/hydro/hydromaxtimeseriesreader.h" +#include + +using namespace Yuni; + +#define SEP IO::Separator + +namespace Antares::Data +{ + +HydroMaxTimeSeriesReader::HydroMaxTimeSeriesReader(PartHydro& hydro, + std::string areaID, + std::string areaName) : + hydro_(hydro), areaID_(areaID), areaName_(areaName) +{ + dailyMaxPumpAndGen.reset(4U, DAYS_PER_YEAR, true); +} + +bool HydroMaxTimeSeriesReader::loadDailyMaxPowersAndEnergies(const AnyString& folder, + bool usedBySolver) +{ + YString filePath; + Matrix<>::BufferType fileContent; + bool ret = true; + + filePath.clear() << folder << SEP << "common" << SEP << "capacity" << SEP << "maxpower_" + << areaID_ << ".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() << areaName_ << ": invalid power or energy value"; + errorPowers = true; + ret = false; + } + } + } + } + return ret; +} + +void HydroMaxTimeSeriesReader::copyDailyMaxEnergy() const +{ + copyDailyMaxGenerationEnergy(); + copyDailyMaxPumpingEnergy(); +} + +void HydroMaxTimeSeriesReader::copyDailyMaxGenerationEnergy() const +{ + auto& dailyNbHoursAtGenPmax = hydro_.dailyNbHoursAtGenPmax; + const auto& dailyMaxGenE = dailyMaxPumpAndGen[genMaxE]; + + dailyNbHoursAtGenPmax.reset(1U, DAYS_PER_YEAR, true); + + dailyNbHoursAtGenPmax.pasteToColumn(0, dailyMaxGenE); +} + +void HydroMaxTimeSeriesReader::copyDailyMaxPumpingEnergy() const +{ + auto& dailyNbHoursAtPumpPmax = hydro_.dailyNbHoursAtPumpPmax; + const auto& dailyMaxPumpE = dailyMaxPumpAndGen[pumpMaxE]; + + dailyNbHoursAtPumpPmax.reset(1U, DAYS_PER_YEAR, true); + + dailyNbHoursAtPumpPmax.pasteToColumn(0, dailyMaxPumpE); +} + +bool HydroMaxTimeSeriesReader::read(const AnyString& folder, bool usedBySolver) +{ + bool ret = loadDailyMaxPowersAndEnergies(folder, usedBySolver); + copyDailyMaxEnergy(); + hydro_.series->buildHourlyMaxPowerFromDailyTS(dailyMaxPumpAndGen[genMaxP], dailyMaxPumpAndGen[pumpMaxP]); + + return ret; +} + +} // namespace Antares::Data \ No newline at end of file diff --git a/src/libs/antares/study/parts/hydro/series.cpp b/src/libs/antares/study/parts/hydro/series.cpp index b896984a5c..7ba67ad1b7 100644 --- a/src/libs/antares/study/parts/hydro/series.cpp +++ b/src/libs/antares/study/parts/hydro/series.cpp @@ -21,12 +21,12 @@ #include #include -#include #include "antares/study/parts/hydro/series.h" #include #include #include #include "antares/study/study.h" +#include using namespace Yuni; @@ -34,16 +34,98 @@ using namespace Yuni; namespace Antares::Data { + +static void resizeTSNoDataLoss(TimeSeries& TSToResize, uint width) +{ + auto& ts = TSToResize.timeSeries; + ts.resizeWithoutDataLost(width, ts.height); + for (uint x = 1; x < width; ++x) + ts.pasteToColumn(x, ts[0]); +} + +static uint EqualizeTSsize(TimeSeries& TScollection1, + TimeSeries& TScollection2, + const std::string& fatalErrorMsg, + Area& area, + unsigned int height1 = HOURS_PER_YEAR, + unsigned int height2 = HOURS_PER_YEAR) +{ + const auto ts1Width = TScollection1.timeSeries.width; + const auto ts2Width = TScollection2.timeSeries.width; + const auto maxWidth = std::max(ts1Width, ts2Width); + + if (ts1Width == 0 && ts2Width == 0) + { + TScollection1.reset(1, height1); + TScollection2.reset(1, height2); + return 1; + } + + if (ts1Width == ts2Width) + return maxWidth; + + if (ts1Width > 1 && ts2Width > 1) + { + logs.fatal() << fatalErrorMsg; + 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 (ts1Width == 1) + resizeTSNoDataLoss(TScollection1, maxWidth); + if (ts2Width == 1) + resizeTSNoDataLoss(TScollection2, maxWidth); + + return maxWidth; +} + +static bool loadTSfromFile(Matrix& ts, + const AreaName& areaID, + const AnyString& folder, + const std::string& filename, + unsigned int height) +{ + YString filePath; + Matrix<>::BufferType fileContent; + filePath.clear() << folder << SEP << areaID << SEP << filename; + return ts.loadFromCSVFile(filePath, 1, height, &fileContent); +} + +static void ConvertDailyTSintoHourlyTS(const Matrix::ColumnType& dailyColumn, + Matrix::ColumnType& hourlyColumn) +{ + uint hour = 0; + uint day = 0; + + while (hour < HOURS_PER_YEAR && day < DAYS_PER_YEAR) + { + for (uint i = 0; i < HOURS_PER_DAY; ++i) + { + hourlyColumn[hour] = dailyColumn[day]; + ++hour; + } + ++day; + } +} + DataSeriesHydro::DataSeriesHydro() : ror(timeseriesNumbers), storage(timeseriesNumbers), - mingen(timeseriesNumbers) + mingen(timeseriesNumbers), + maxHourlyGenPower(timeseriesNumbersHydroMaxPower), + maxHourlyPumpPower(timeseriesNumbersHydroMaxPower) { // 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(); + maxHourlyGenPower.reset(); + maxHourlyPumpPower.reset(); } void DataSeriesHydro::copyGenerationTS(const DataSeriesHydro& source) @@ -52,220 +134,215 @@ void DataSeriesHydro::copyGenerationTS(const DataSeriesHydro& source) storage.timeSeries = source.storage.timeSeries; mingen.timeSeries = source.mingen.timeSeries; - count = source.count; + generationTScount_ = source.generationTScount_; source.ror.unloadFromMemory(); source.storage.unloadFromMemory(); source.mingen.unloadFromMemory(); } -bool DataSeriesHydro::saveToFolder(const AreaName& areaID, const AnyString& folder) const +void DataSeriesHydro::copyMaxPowerTS(const DataSeriesHydro& source) { - String buffer; - buffer.clear() << folder << SEP << areaID; - /* Make sure the folder is created */ - if (IO::Directory::Create(buffer)) - { - bool ret = true; + maxHourlyGenPower.timeSeries = source.maxHourlyGenPower.timeSeries; + maxHourlyPumpPower.timeSeries = source.maxHourlyPumpPower.timeSeries; - // Saving data - buffer.clear() << folder << SEP << areaID << SEP << "ror.txt"; - ret = ror.timeSeries.saveToCSVFile(buffer, 0) && ret; - buffer.clear() << folder << SEP << areaID << SEP << "mod.txt"; - ret = storage.timeSeries.saveToCSVFile(buffer, 0) && ret; - buffer.clear() << folder << SEP << areaID << SEP << "mingen.txt"; - ret = mingen.timeSeries.saveToCSVFile(buffer, 0) && ret; - return ret; - } - return false; + maxPowerTScount_ = source.maxPowerTScount_; + + source.maxHourlyGenPower.unloadFromMemory(); + source.maxHourlyPumpPower.unloadFromMemory(); } -bool DataSeriesHydro::loadFromFolder(Study& study, const AreaName& areaID, const AnyString& folder) +void DataSeriesHydro::reset() { - bool ret = true; - auto& buffer = study.bufferLoadingTS; + resizeGenerationTS(1); + resizeMaxPowerTS(1); +} - buffer.clear() << folder << SEP << areaID << SEP << "ror." << study.inputExtension; +void DataSeriesHydro::resizeGenerationTS(uint nbSeries) +{ + storage.reset(nbSeries, DAYS_PER_YEAR); + ror.reset(nbSeries, HOURS_PER_YEAR); + mingen.reset(nbSeries, HOURS_PER_YEAR); - ret = ror.timeSeries.loadFromCSVFile(buffer, 1, HOURS_PER_YEAR, &study.dataBuffer) && ret; + generationTScount_ = nbSeries; +} - buffer.clear() << folder << SEP << areaID << SEP << "mod." << study.inputExtension; - ret = storage.timeSeries.loadFromCSVFile(buffer, 1, DAYS_PER_YEAR, &study.dataBuffer) && ret; +void DataSeriesHydro::resizeMaxPowerTS(uint nbSeries) +{ + maxHourlyGenPower.reset(nbSeries, HOURS_PER_YEAR); + maxHourlyPumpPower.reset(nbSeries, HOURS_PER_YEAR); - // The number of time-series - count = storage.timeSeries.width; + maxPowerTScount_ = nbSeries; +} - if (ror.timeSeries.width > count) - count = ror.timeSeries.width; +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; +} - if (study.header.version >= StudyVersion(8, 6)) - { - buffer.clear() << folder << SEP << areaID << SEP << "mingen." << study.inputExtension; - ret = mingen.timeSeries.loadFromCSVFile(buffer, 1, HOURS_PER_YEAR, &study.dataBuffer) && ret; - } +void DataSeriesHydro::markAsModified() const +{ + ror.markAsModified(); + storage.markAsModified(); + mingen.markAsModified(); + maxHourlyGenPower.markAsModified(); + maxHourlyPumpPower.markAsModified(); +} - if (!study.usedByTheSolver) - { - timeseriesNumbers.clear(); - return ret; - } +void DataSeriesHydro::EqualizeGenerationTSsizes(Area& area, bool usedByTheSolver) +{ + if (!usedByTheSolver) // From GUI, no need to equalize TS collections sizes + return; - if (count == 0) - { - logs.error() << "Hydro: `" << areaID - << "`: empty matrix detected. Fixing it with default values"; - ror.reset(); - storage.reset(1, DAYS_PER_YEAR); - mingen.reset(); - } - else - { - if (count > 1 && storage.timeSeries.width != ror.timeSeries.width) - { - if (ror.timeSeries.width != 1 && storage.timeSeries.width != 1) - { - logs.fatal() << "Hydro: `" << areaID - << "`: The matrices ROR (run-of-the-river) and hydro-storage must " - "have the same number of time-series."; - throw Antares::Error::ReadingStudy(); - } - else - { - if (ror.timeSeries.width == 1) - { - ror.timeSeries.resizeWithoutDataLost(count, ror.timeSeries.height); - for (uint x = 1; x < count; ++x) - ror.timeSeries.pasteToColumn(x, ror[0]); - } - else - { - if (storage.timeSeries.width == 1) - { - storage.timeSeries.resizeWithoutDataLost(count, storage.timeSeries.height); - for (uint x = 1; x < count; ++x) - storage.timeSeries.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 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 (study.parameters.derated) - { - ror.averageTimeseries(); - storage.averageTimeseries(); - mingen.averageTimeseries(); - count = 1; - } + generationTScount_ = EqualizeTSsize(ror, storage, fatalErrorMsg, area, HOURS_PER_YEAR, DAYS_PER_YEAR); - timeseriesNumbers.clear(); + logs.info() << " '" << area.id << "': ROR and INFLOWS time series were both set to : " << generationTScount_; - return ret; + // 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."; + + generationTScount_ = EqualizeTSsize(ror, mingen, fatalErrorMsg, area); + + logs.info() << " '" << area.id << "': ROR and MINGEN time series were both set to : " << generationTScount_; } -void DataSeriesHydro::checkMinGenTsNumber(Study& study, const AreaName& areaID) +bool DataSeriesHydro::loadGenerationTS(const AreaName& areaID, + const AnyString& folder, + StudyVersion studyVersion) { - if (mingen.timeSeries.width != storage.timeSeries.width) - { - if (mingen.timeSeries.width > 1) - { - logs.fatal() << "Hydro: `" << areaID - << "`: The matrices Minimum Generation must " - "has the same number of time-series as ROR and hydro-storage."; - throw Antares::Error::ReadingStudy(); - } - else - { - mingen.timeSeries.resizeWithoutDataLost(count, mingen.timeSeries.height); - for (uint x = 1; x < count; ++x) - mingen.timeSeries.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"; - } - } + timeseriesNumbers.clear(); + + bool ret = loadTSfromFile(ror.timeSeries, areaID, folder, "ror.txt", HOURS_PER_YEAR); + ret = loadTSfromFile(storage.timeSeries, areaID, folder, "mod.txt", DAYS_PER_YEAR) && ret; + if (studyVersion >= StudyVersion(8, 6)) + ret = loadTSfromFile(mingen.timeSeries, areaID, folder, "mingen.txt", HOURS_PER_YEAR) && ret; + + return ret; } -bool DataSeriesHydro::forceReload(bool reload) const +bool DataSeriesHydro::LoadMaxPower(const AreaName& areaID, const AnyString& folder) { bool ret = true; - ret = ror.forceReload(reload) && ret; - ret = storage.forceReload(reload) && ret; - ret = mingen.forceReload(reload) && ret; + YString filepath; + Matrix<>::BufferType fileContent; + + filepath.clear() << folder << SEP << areaID << SEP << "maxHourlyGenPower.txt"; + ret = maxHourlyGenPower.timeSeries.loadFromCSVFile(filepath, 1, HOURS_PER_YEAR, &fileContent) && ret; + + filepath.clear() << folder << SEP << areaID << SEP << "maxHourlyPumpPower.txt"; + ret = maxHourlyPumpPower.timeSeries.loadFromCSVFile(filepath, 1, HOURS_PER_YEAR, &fileContent) && ret; + + timeseriesNumbersHydroMaxPower.clear(); + return ret; } -void DataSeriesHydro::markAsModified() const +void DataSeriesHydro::buildHourlyMaxPowerFromDailyTS(const Matrix::ColumnType& DailyMaxGenPower, + const Matrix::ColumnType& DailyMaxPumpPower) { - ror.markAsModified(); - storage.markAsModified(); - mingen.markAsModified(); + maxPowerTScount_ = 1; + + maxHourlyGenPower.reset(maxPowerTScount_, HOURS_PER_YEAR); + maxHourlyPumpPower.reset(maxPowerTScount_, HOURS_PER_YEAR); + + ConvertDailyTSintoHourlyTS(DailyMaxGenPower, maxHourlyGenPower.timeSeries[0]); + ConvertDailyTSintoHourlyTS(DailyMaxPumpPower, maxHourlyPumpPower.timeSeries[0]); } -void DataSeriesHydro::reset() +bool DataSeriesHydro::saveToFolder(const AreaName& areaID, const AnyString& folder) const { - ror.reset(); - storage.reset(1, DAYS_PER_YEAR); - mingen.reset(); - count = 1; + String buffer; + buffer.clear() << folder << SEP << areaID; + /* Make sure the folder is created */ + if (IO::Directory::Create(buffer)) + { + bool ret = true; + + // Saving data + buffer.clear() << folder << SEP << areaID << SEP << "ror.txt"; + ret = ror.timeSeries.saveToCSVFile(buffer, 0) && ret; + buffer.clear() << folder << SEP << areaID << SEP << "mod.txt"; + ret = storage.timeSeries.saveToCSVFile(buffer, 0) && ret; + buffer.clear() << folder << SEP << areaID << SEP << "mingen.txt"; + ret = mingen.timeSeries.saveToCSVFile(buffer, 0) && ret; + buffer.clear() << folder << SEP << areaID << SEP << "maxHourlyGenPower.txt"; + ret = maxHourlyGenPower.timeSeries.saveToCSVFile(buffer, 0) && ret; + buffer.clear() << folder << SEP << areaID << SEP << "maxHourlyPumpPower.txt"; + ret = maxHourlyPumpPower.timeSeries.saveToCSVFile(buffer, 0) && ret; + + return ret; + } + return false; +} + +uint64_t DataSeriesHydro::memoryUsage() const +{ + return sizeof(double) + ror.memoryUsage() + storage.memoryUsage() + mingen.memoryUsage() + + maxHourlyGenPower.memoryUsage() + maxHourlyPumpPower.memoryUsage(); +} + +void DataSeriesHydro::EqualizeMaxPowerTSsizes(Area& area) +{ + std::string fatalErrorMsg = "Hydro Max Power: " + area.id.to() + " : "; + fatalErrorMsg += "generation and pumping must have the same number of TS."; + + maxPowerTScount_ + = EqualizeTSsize(maxHourlyGenPower, maxHourlyPumpPower, fatalErrorMsg, area); + + logs.info() << " '" << area.id << "': The number of hydro max power (generation and pumping) " + << "TS were both set to : " << maxPowerTScount_; } -void DataSeriesHydro::resize_ROR_STORAGE_MINGEN_whenGeneratedTS(unsigned int newWidth) +void DataSeriesHydro::setHydroModulability(Area& area) const { - // This function is called in case hydro TS are generated. - // ROR ans STORAGE are resized here, and will be overriden at some point. - // MINGEN TS are different : when generating hydro TS, mingen TS are not generated, - // but only resized, so that their size is the same as ROR and STORAGE TS. - // When resizing MINGEN : - // - If we extend mingen TS, we keep already existing TS and fill the extra ones - // with a copy of the first TS - // - if we reduce mingen TS, we remove some existing TS, but we must keep intact - // the remaining ones. - ror.resize(newWidth, HOURS_PER_YEAR); - storage.resize(newWidth, DAYS_PER_YEAR); - - // Resizing mingen (mingen has necessarily at least one column, by construction) - uint mingenOriginalSize = mingen.timeSeries.width; - mingen.timeSeries.resizeWithoutDataLost(newWidth, mingen.timeSeries.height); - if (mingenOriginalSize < newWidth) + if (MatrixTestForAtLeastOnePositiveValue(maxHourlyGenPower.timeSeries)) { - for (uint col = mingenOriginalSize; col < newWidth; ++col) - mingen.timeSeries.pasteToColumn(col, mingen[0]); + area.hydro.hydroModulable = true; } +} - count = newWidth; +uint DataSeriesHydro::TScount() const +{ + return generationTScount_; } -void DataSeriesHydro::resizeGenerationTS(unsigned int w, unsigned int h) +uint DataSeriesHydro::maxPowerTScount() const { - ror.resize(w, h); - storage.resize(w, h); - mingen.resize(w, h); - count = w; + return maxPowerTScount_; } -uint64_t DataSeriesHydro::memoryUsage() const +void DataSeriesHydro::resizeTSinDeratedMode(bool derated, + StudyVersion studyVersion, + bool usedBySolver) { - return sizeof(double) + ror.memoryUsage() + storage.memoryUsage() + mingen.memoryUsage(); + if (!(derated && usedBySolver)) + return; + + ror.averageTimeseries(); + storage.averageTimeseries(); + if (studyVersion >= StudyVersion(8,6)) + mingen.averageTimeseries(); + generationTScount_ = 1; + + if (studyVersion >= StudyVersion(9,1)) + { + maxHourlyGenPower.averageTimeseries(); + maxHourlyPumpPower.averageTimeseries(); + maxPowerTScount_ = 1; + } } } // namespace Antares::Data 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..b890dc99d0 --- /dev/null +++ b/src/libs/antares/study/scenario-builder/HydroMaxPowerTSNumberData.cpp @@ -0,0 +1,43 @@ + + +#include "antares/study/scenario-builder/HydroMaxPowerTSNumberData.h" +#include "antares/study/scenario-builder/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/TSnumberData.cpp b/src/libs/antares/study/scenario-builder/TSnumberData.cpp index 10afa139ff..712f8967c9 100644 --- a/src/libs/antares/study/scenario-builder/TSnumberData.cpp +++ b/src/libs/antares/study/scenario-builder/TSnumberData.cpp @@ -71,4 +71,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/rules.cpp b/src/libs/antares/study/scenario-builder/rules.cpp index 3840117f6f..f1dd14fe35 100644 --- a/src/libs/antares/study/scenario-builder/rules.cpp +++ b/src/libs/antares/study/scenario-builder/rules.cpp @@ -45,6 +45,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 @@ -69,6 +71,7 @@ bool Rules::reset() load.reset(study_); solar.reset(study_); hydro.reset(study_); + hydroMaxPower.reset(study_); wind.reset(study_); // Thermal @@ -229,6 +232,20 @@ bool Rules::readHydro(const AreaName::Vector& splitKey, String value, bool updat return true; } +bool Rules::readHydroMaxPower(const AreaName::Vector& splitKey, String tsNumberAsString, 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 tsNumber = fromStringToTSnumber(tsNumberAsString); + hydroMaxPower.setTSnumber(area->index, year, tsNumber); + return true; +} + bool Rules::readSolar(const AreaName::Vector& splitKey, String value, bool updaterMode) { const uint year = splitKey[2].to(); @@ -337,6 +354,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") @@ -356,6 +375,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/study.cpp b/src/libs/antares/study/study.cpp index 7ce8aae935..8e10024d93 100644 --- a/src/libs/antares/study/study.cpp +++ b/src/libs/antares/study/study.cpp @@ -1526,6 +1526,5 @@ void Study::computePThetaInfForThermalClusters() const } } } - } // namespace Antares::Data diff --git a/src/solver/hydro/include/antares/solver/hydro/management/management.h b/src/solver/hydro/include/antares/solver/hydro/management/management.h index 54f0ebd30e..ead2fe094c 100644 --- a/src/solver/hydro/include/antares/solver/hydro/management/management.h +++ b/src/solver/hydro/include/antares/solver/hydro/management/management.h @@ -128,7 +128,9 @@ class HydroManagement final //! check Weekly minimum generation is lower than available inflows bool checkWeeklyMinGeneration(uint year, const Data::Area& area) const; //! check Hourly minimum generation is lower than available inflows - bool checkHourlyMinGeneration(uint year, const Data::Area& area) const; + bool checkGenerationPowerConsistency(uint year) const; + //! return false if checkGenerationPowerConsistency or checkMinGeneration returns false + bool checksOnGenerationPowerBounds(uint year) const; //! check minimum generation is lower than available inflows bool checkMinGeneration(uint year) const; //! Prepare the net demand for each area diff --git a/src/solver/hydro/management/daily.cpp b/src/solver/hydro/management/daily.cpp index 562d733c56..4121929dd6 100644 --- a/src/solver/hydro/management/daily.cpp +++ b/src/solver/hydro/management/daily.cpp @@ -37,6 +37,7 @@ #include #include "antares/solver/variable/state.h" #include +#include using namespace Yuni; @@ -226,6 +227,10 @@ inline void HydroManagement::prepareDailyOptimalGenerations(Solver::Variable::St auto& scratchpad = scratchmap.at(&area); + auto& meanMaxDailyGenPower = scratchpad.meanMaxDailyGenPower; + + const uint tsIndex = meanMaxDailyGenPower.getSeriesIndex(y); + int initReservoirLvlMonth = area.hydro.initializeReservoirLevelDate; double reservoirCapacity = area.hydro.reservoirCapacity; @@ -236,10 +241,11 @@ inline void HydroManagement::prepareDailyOptimalGenerations(Solver::Variable::St uint dayYear = 0; - auto const& maxPower = area.hydro.maxPower; + auto const& dailyNbHoursAtGenPmax = area.hydro.dailyNbHoursAtGenPmax; - auto const& maxP = maxPower[Data::PartHydro::genMaxP]; - auto const& maxE = maxPower[Data::PartHydro::genMaxE]; + + auto const& maxP = meanMaxDailyGenPower[tsIndex]; + auto const& maxE = dailyNbHoursAtGenPmax[0]; auto& ventilationResults = ventilationResults_[area.index]; @@ -263,14 +269,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.timeSeries.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]; @@ -545,5 +550,4 @@ void HydroManagement::prepareDailyOptimalGenerations(Solver::Variable::State& st prepareDailyOptimalGenerations(state, area, y, scratchmap); }); } - } // namespace Antares diff --git a/src/solver/hydro/management/management.cpp b/src/solver/hydro/management/management.cpp index 22aec1d596..bf91fd8cff 100644 --- a/src/solver/hydro/management/management.cpp +++ b/src/solver/hydro/management/management.cpp @@ -302,40 +302,38 @@ bool HydroManagement::checkWeeklyMinGeneration(uint year, const Data::Area& area return true; } -bool HydroManagement::checkHourlyMinGeneration(uint year, const Data::Area& area) const +bool HydroManagement::checkGenerationPowerConsistency(uint year) const { - // Hourly minimum generation <= hourly max generation for each hour + bool ret = true; - auto const& srcmingen = area.hydro.series->mingen.getColumn(year); - auto const& maxPower = area.hydro.maxPower; - auto const& maxP = maxPower[Data::PartHydro::genMaxP]; + areas_.each( + [&ret, &year](const Data::Area& area) + { + + auto const& srcmingen = area.hydro.series->mingen.getColumn(year); + auto const& srcmaxgen = area.hydro.series->maxHourlyGenPower.getColumn(year); - 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; + uint const tsIndexMin = area.hydro.series->mingen.getSeriesIndex(year); + uint const tsIndexMax = area.hydro.series->maxHourlyGenPower.getSeriesIndex(year); - 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-" << area.hydro.series->mingen.getSeriesIndex(year) + 1 - << " is incompatible with the maximum generation of " << maxP[day] - << " MW."; - return false; - } - } - } - } - return true; + for (uint h = 0; h < HOURS_PER_YEAR; ++h) + { + const auto& min = srcmingen[h]; + const auto& max = srcmaxgen[h]; + + if (max < min) + { + logs.error() << "In area: " << area.name << " [hourly] minimum generation of " + << min << " MW in timestep " << h + 1 << " of TS-" << tsIndexMin + 1 + << " is incompatible with the maximum generation of " << max + << " MW in timestep " << h + 1 << " of TS-" << tsIndexMax + 1 << " MW."; + ret = false; + return; + } + } + }); + + return ret; } bool HydroManagement::checkMinGeneration(uint year) const @@ -347,8 +345,6 @@ bool HydroManagement::checkMinGeneration(uint year) const bool followLoadModulations = area.hydro.followLoadModulations; bool reservoirManagement = area.hydro.reservoirManagement; - ret = checkHourlyMinGeneration(year, area) && ret; - if (!useHeuristicTarget) return; @@ -477,6 +473,11 @@ void HydroManagement::prepareEffectiveDemand() }); } +bool HydroManagement::checksOnGenerationPowerBounds(uint year) const +{ + return (checkMinGeneration(year) && checkGenerationPowerConsistency(year)) ? true : false; +} + void HydroManagement::makeVentilation(double* randomReservoirLevel, Solver::Variable::State& state, uint y, @@ -484,7 +485,7 @@ void HydroManagement::makeVentilation(double* randomReservoirLevel, { prepareInflowsScaling(y); minGenerationScaling(y); - if (!checkMinGeneration(y)) + if (!checksOnGenerationPowerBounds(y)) { throw FatalError("hydro management: invalid minimum generation"); } diff --git a/src/solver/optimisation/opt_gestion_second_membre_cas_lineaire.cpp b/src/solver/optimisation/opt_gestion_second_membre_cas_lineaire.cpp index 65e19e37c4..63c5c7a7e4 100644 --- a/src/solver/optimisation/opt_gestion_second_membre_cas_lineaire.cpp +++ b/src/solver/optimisation/opt_gestion_second_membre_cas_lineaire.cpp @@ -83,7 +83,6 @@ void OPT_InitialiserLeSecondMembreDuProblemeLineaire(PROBLEME_HEBDO* problemeHeb for (int i = 0; i < ProblemeAResoudre->NombreDeContraintes; i++) { AdresseOuPlacerLaValeurDesCoutsMarginaux[i] = nullptr; - SecondMembre[i] = 0.0; } diff --git a/src/solver/simulation/sim_calcul_economique.cpp b/src/solver/simulation/sim_calcul_economique.cpp index 2ace94c543..f27bdb4013 100644 --- a/src/solver/simulation/sim_calcul_economique.cpp +++ b/src/solver/simulation/sim_calcul_economique.cpp @@ -580,30 +580,31 @@ void SIM_RenseignementProblemeHebdo(const Study& study, study.bindingConstraints, study.bindingConstraintsGroups, weekFirstDay, hourInWeek); - const uint dayInTheYear = study.calendar.hours[hourInYear].dayYear; - for (uint k = 0; k < nbPays; ++k) { - const auto& area = *(study.areas.byIndex[k]); + + auto& area = *(study.areas.byIndex[k]); const auto& scratchpad = scratchmap.at(&area); - double loadSeries = area.load.series.getCoefficient(year, hourInYear); - double windSeries = area.wind.series.getCoefficient(year, hourInYear); - double solarSeries = area.solar.series.getCoefficient(year, hourInYear); - double rorSeries = area.hydro.series->ror.getCoefficient(year, hourInYear); + const double hourlyLoad = area.load.series.getCoefficient(year, hourInYear); + const double hourlyWind = area.wind.series.getCoefficient(year, hourInYear); + const double hourlySolar = area.solar.series.getCoefficient(year, hourInYear); + const double hourlyROR = area.hydro.series->ror.getCoefficient(year, hourInYear); + const double hourlyHydroGenPower = area.hydro.series->maxHourlyGenPower.getCoefficient(year, hourInYear); + const double hourlyHydroPumpPower = area.hydro.series->maxHourlyPumpPower.getCoefficient(year, hourInYear); double& mustRunGen = problem.AllMustRunGeneration[hourInWeek].AllMustRunGenerationOfArea[k]; if (parameters.renewableGeneration.isAggregated()) { - mustRunGen = windSeries + solarSeries + mustRunGen = hourlyWind + hourlySolar + scratchpad.miscGenSum[hourInYear] - + rorSeries + + hourlyROR + scratchpad.mustrunSum[hourInYear]; } // Renewable if (parameters.renewableGeneration.isClusters()) { - mustRunGen = scratchpad.miscGenSum[hourInYear] + rorSeries + mustRunGen = scratchpad.miscGenSum[hourInYear] + hourlyROR + scratchpad.mustrunSum[hourInYear]; for (auto c : area.renewable.list.each_enabled()) @@ -615,20 +616,21 @@ void SIM_RenseignementProblemeHebdo(const Study& study, && "NaN detected for 'AllMustRunGeneration', probably from miscGenSum/mustrunSum"); problem.ConsommationsAbattues[hourInWeek].ConsommationAbattueDuPays[k] - = +loadSeries + = +hourlyLoad - problem.AllMustRunGeneration[hourInWeek].AllMustRunGenerationOfArea[k]; if (problem.CaracteristiquesHydrauliques[k].PresenceDHydrauliqueModulable > 0) { - problem.CaracteristiquesHydrauliques[k].ContrainteDePmaxHydrauliqueHoraire[hourInWeek] - = scratchpad.optimalMaxPower[dayInTheYear] + problem.CaracteristiquesHydrauliques[k] + .ContrainteDePmaxHydrauliqueHoraire[hourInWeek] + = hourlyHydroGenPower * problem.CaracteristiquesHydrauliques[k].WeeklyGeneratingModulation; } if (problem.CaracteristiquesHydrauliques[k].PresenceDePompageModulable) { problem.CaracteristiquesHydrauliques[k].ContrainteDePmaxPompageHoraire[hourInWeek] - = scratchpad.pumpingMaxPower[dayInTheYear] + = hourlyHydroPumpPower * problem.CaracteristiquesHydrauliques[k].WeeklyPumpingModulation; } @@ -643,10 +645,14 @@ void SIM_RenseignementProblemeHebdo(const Study& study, if (problem.CaracteristiquesHydrauliques[k].PresenceDHydrauliqueModulable > 0) { auto& area = *study.areas.byIndex[k]; + const auto& scratchpad = scratchmap.at(&area); auto& hydroSeries = area.hydro.series; + auto const& dailyMeanMaxGenPower = scratchpad.meanMaxDailyGenPower.getColumn(year); + auto const& dailyMeanMaxPumpPower = scratchpad.meanMaxDailyPumpPower.getColumn(year); auto const& srcinflows = hydroSeries->storage.getColumn(year); auto const& srcmingen = hydroSeries->mingen.getColumn(year); + for (uint j = 0; j < problem.NombreDePasDeTemps; ++j) { problem.CaracteristiquesHydrauliques[k].MingenHoraire[j] @@ -668,8 +674,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.dailyNbHoursAtGenPmax[0][day] * problem.CaracteristiquesHydrauliques[k] .WeeklyGeneratingModulation; } @@ -719,8 +724,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.dailyNbHoursAtGenPmax[0][day]; DGU_tmp[j] = DNT[day] * LUB; DGL_tmp[j] = DNT[day] * LLB; @@ -828,8 +833,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.dailyNbHoursAtPumpPmax[0][day] * problem.CaracteristiquesHydrauliques[k] .WeeklyPumpingModulation; } @@ -861,8 +866,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.dailyNbHoursAtPumpPmax[0][day]; WPU += DPC; } @@ -872,8 +877,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.dailyNbHoursAtPumpPmax[0][day]; double rc = area.hydro.reservoirCapacity; if (not area.hydro.hardBoundsOnRuleCurves) diff --git a/src/solver/simulation/timeseries-numbers.cpp b/src/solver/simulation/timeseries-numbers.cpp index 700637ea72..7de824ccb8 100644 --- a/src/solver/simulation/timeseries-numbers.cpp +++ b/src/solver/simulation/timeseries-numbers.cpp @@ -43,7 +43,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"}, @@ -52,7 +53,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) @@ -110,6 +112,15 @@ class HydroAreaNumberOfTSretriever : public AreaNumberOfTSretriever } }; +class hydroMaxPowerAreaNumberOfTSretriever : public AreaNumberOfTSretriever +{ +public: + std::vector getAreaTimeSeriesNumber(const Area& area) override + { + return {area.hydro.series->maxPowerTScount()}; + } +}; + class WindAreaNumberOfTSretriever : public AreaNumberOfTSretriever { public: @@ -237,6 +248,8 @@ bool checkIntraModalConsistency(array& nbTimeseriesByMode = make_shared(); ts_to_numberOfTSretrievers[timeSeriesTransmissionCapacities] = make_shared(); + ts_to_numberOfTSretrievers[timeSeriesHydroMaxPower] + = make_shared(); // Loop over TS kind and check intra-modal consistency mapTStoRetriever::iterator it = ts_to_numberOfTSretrievers.begin(); @@ -295,6 +308,14 @@ bool checkInterModalConsistencyForArea(const Area& area, listNumberTsOverArea.push_back(area.hydro.series->TScount()); } + // 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); + } + // Thermal : Add thermal's number of TS of each cluster in area ... indexTS = ts_to_tsIndex.at(timeSeriesThermal); if (isTSintermodal[indexTS]) @@ -393,6 +414,15 @@ void storeTSnumbersForIntraModal(const array& intramo if (isTSintramodal[indexTS] && area.hydro.series->TScount() > 1) 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 ... // ------------- @@ -481,6 +511,21 @@ void drawAndStoreTSnumbersForNOTintraModal(const array& i = (uint32_t)(floor(study.runtime->random[seedTimeseriesNumbers].next() * area.hydro.series->TScount())); } + // ------------- + // 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 ... // ------------- @@ -606,6 +651,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)]) { for (auto cluster : area.thermal.list.each_enabled()) @@ -652,7 +701,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; @@ -687,7 +737,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; })) { @@ -725,6 +776,7 @@ void TimeSeriesNumbers::StoreTimeSeriesNumbersIntoOuput(Data::Study& study, IRes study.storeTimeSeriesNumbers(resultWriter); study.storeTimeSeriesNumbers(resultWriter); study.storeTimeSeriesNumbers(resultWriter); + study.storeTimeSeriesNumbers(resultWriter); Simulation::BindingConstraintsTimeSeriesNumbersWriter ts_writer(resultWriter); ts_writer.write(study.bindingConstraintsGroups); diff --git a/src/solver/ts-generator/generator.cpp b/src/solver/ts-generator/generator.cpp index 7db121112c..c405957784 100644 --- a/src/solver/ts-generator/generator.cpp +++ b/src/solver/ts-generator/generator.cpp @@ -49,7 +49,9 @@ void ResizeGeneratedTimeSeries(Data::AreaList& areas, Data::Parameters& params) // Hydro if (params.timeSeriesToGenerate & Data::timeSeriesHydro) { - area.hydro.series->resize_ROR_STORAGE_MINGEN_whenGeneratedTS(params.nbTimeSeriesHydro); + Data::DataSeriesHydro* const series = area.hydro.series; + const uint nbSeries = params.nbTimeSeriesHydro; + series->resizeGenerationTS(nbSeries); } // Thermal diff --git a/src/solver/variable/economy/max-mrg.cpp b/src/solver/variable/economy/max-mrg.cpp index 943255f471..8c9d8874bf 100644 --- a/src/solver/variable/economy/max-mrg.cpp +++ b/src/solver/variable/economy/max-mrg.cpp @@ -119,10 +119,9 @@ 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]; + const uint y = problem.year; + const auto& P = area.hydro.series->maxHourlyGenPower; do { @@ -135,8 +134,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.getCoefficient(y, i + state.hourInTheYear) - H[i]); SM += opmrg[i] - OI[i]; } else diff --git a/src/tests/end-to-end/binding_constraints/CMakeLists.txt b/src/tests/end-to-end/binding_constraints/CMakeLists.txt index 900a781d59..1b7cf52e86 100644 --- a/src/tests/end-to-end/binding_constraints/CMakeLists.txt +++ b/src/tests/end-to-end/binding_constraints/CMakeLists.txt @@ -14,6 +14,7 @@ target_link_libraries(tests-binding_constraints PRIVATE test_utils Boost::unit_test_framework + model_antares antares-solver-simulation antares-solver-hydro antares-solver-ts-generator diff --git a/src/tests/end-to-end/simple_study/simple-study.cpp b/src/tests/end-to-end/simple_study/simple-study.cpp index 1c3111cd24..e34407c645 100644 --- a/src/tests/end-to-end/simple_study/simple-study.cpp +++ b/src/tests/end-to-end/simple_study/simple-study.cpp @@ -66,6 +66,42 @@ StudyFixture::StudyFixture() .setUnitCount(1); }; +struct HydroMaxPowerStudy : public StudyBuilder +{ + using StudyBuilder::StudyBuilder; + HydroMaxPowerStudy(); + + // Data members + Area* area = nullptr; + PartHydro* hydro = nullptr; + double loadInArea = 24000.; +}; + +HydroMaxPowerStudy::HydroMaxPowerStudy() +{ + simulationBetweenDays(0, 14); + setNumberMCyears(1); + + area = addAreaToStudy("Area"); + area->thermal.unsuppliedEnergyCost = 1; + + TimeSeriesConfigurer loadTSconfig(area->load.series.timeSeries); + loadTSconfig.setColumnCount(1).fillColumnWith(0, loadInArea); + + hydro = &area->hydro; + + TimeSeriesConfigurer genP(hydro->series->maxHourlyGenPower.timeSeries); + genP.setColumnCount(1).fillColumnWith(0, 100.); + + TimeSeriesConfigurer hydroStorage(hydro->series->storage.timeSeries); + hydroStorage.setColumnCount(1, DAYS_PER_YEAR).fillColumnWith(0, 2400.); + + TimeSeriesConfigurer genE(hydro->dailyNbHoursAtGenPmax); + genE.setColumnCount(1, DAYS_PER_YEAR).fillColumnWith(0, 24); + + hydro->reservoirCapacity = 1e6; + hydro->reservoirManagement = true; +}; BOOST_FIXTURE_TEST_SUITE(ONE_AREA__ONE_THERMAL_CLUSTER, StudyFixture) @@ -318,5 +354,51 @@ BOOST_AUTO_TEST_CASE(sts_initial_level) } BOOST_AUTO_TEST_SUITE_END() +BOOST_FIXTURE_TEST_SUITE(HYDRO_MAX_POWER, HydroMaxPowerStudy) + +BOOST_AUTO_TEST_CASE(basic) +{ + simulation->create(); + simulation->run(); + + OutputRetriever output(simulation->rawSimu()); + + BOOST_TEST(output.hydroStorage(area).hour(0) == hydro->series->maxHourlyGenPower.timeSeries[0][0], tt::tolerance(0.001)); + BOOST_TEST(output.overallCost(area).hour(0) == (loadInArea - output.hydroStorage(area).hour(0)) * area->thermal.unsuppliedEnergyCost, tt::tolerance(0.001)); +} + +BOOST_AUTO_TEST_CASE(scenario_builder) +{ + hydro->series->setMaxPowerTScount(3U); + setNumberMCyears(3); + + giveWeightToYear(4.f, 0); + giveWeightToYear(3.f, 1); + giveWeightToYear(2.f, 2); + float weightSum = study->parameters.getYearsWeightSum(); + + TimeSeriesConfigurer genP(hydro->series->maxHourlyGenPower.timeSeries); + TimeSeriesConfigurer genE(hydro->series->maxHourlyPumpPower.timeSeries); + genP.setColumnCount(3).fillColumnWith(0, 100.).fillColumnWith(1, 200.).fillColumnWith(2, 300.); + genE.setColumnCount(3).fillColumnWith(0, 0.).fillColumnWith(1, 0.).fillColumnWith(2, 0.); + ScenarioBuilderRule scenarioBuilderRule(*study); + scenarioBuilderRule.hydroMaxPower().setTSnumber(area->index, 0, 3); + scenarioBuilderRule.hydroMaxPower().setTSnumber(area->index, 1, 2); + scenarioBuilderRule.hydroMaxPower().setTSnumber(area->index, 2, 1); + + simulation->create(); + simulation->run(); + + OutputRetriever output(simulation->rawSimu()); + + double averageLoad = (4 * 300. + 3. * 200. + 2. * 100.) / weightSum; + + BOOST_TEST(hydro->series->maxHourlyGenPower.timeseriesNumbers[0][0] == 2U); + BOOST_TEST(hydro->series->maxHourlyGenPower.timeseriesNumbers[0][1] == 1U); + BOOST_TEST(hydro->series->maxHourlyGenPower.timeseriesNumbers[0][2] == 0); + BOOST_TEST(output.overallCost(area).hour(0) == loadInArea - averageLoad * area->thermal.unsuppliedEnergyCost, tt::tolerance(0.1)); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/tests/end-to-end/utils/utils.cpp b/src/tests/end-to-end/utils/utils.cpp index 3d3ef97732..4c105aaba8 100644 --- a/src/tests/end-to-end/utils/utils.cpp +++ b/src/tests/end-to-end/utils/utils.cpp @@ -56,10 +56,10 @@ void addScratchpadToEachArea(Study& study) } } } - -TimeSeriesConfigurer& TimeSeriesConfigurer::setColumnCount(unsigned int columnCount) +// Name should be changed to setTSSize +TimeSeriesConfigurer& TimeSeriesConfigurer::setColumnCount(unsigned int columnCount, unsigned rowCount) { - ts_->resize(columnCount, HOURS_PER_YEAR); + ts_->resize(columnCount, rowCount); return *this; } @@ -127,6 +127,12 @@ averageResults OutputRetriever::load(Area* area) return averageResults(result->avgdata); } +averageResults OutputRetriever::hydroStorage(Area* area) +{ + auto result = retrieveAreaResults(area); + return averageResults(result->avgdata); +} + averageResults OutputRetriever::flow(AreaLink* link) { // There is a problem here : diff --git a/src/tests/end-to-end/utils/utils.h b/src/tests/end-to-end/utils/utils.h index 9223ee45ea..a0f8f2f4fb 100644 --- a/src/tests/end-to-end/utils/utils.h +++ b/src/tests/end-to-end/utils/utils.h @@ -39,7 +39,7 @@ class TimeSeriesConfigurer public: TimeSeriesConfigurer() = default; TimeSeriesConfigurer(Matrix<>& matrix) : ts_(&matrix) {} - TimeSeriesConfigurer& setColumnCount(unsigned int columnCount); + TimeSeriesConfigurer& setColumnCount(unsigned int columnCount, unsigned rowCount = HOURS_PER_YEAR); TimeSeriesConfigurer& fillColumnWith(unsigned int column, double value); private: Matrix<>* ts_ = nullptr; @@ -90,6 +90,7 @@ class OutputRetriever averageResults overallCost(Area* area); averageResults STSLevel_PSP_Open(Area* area); averageResults load(Area* area); + averageResults hydroStorage(Area* area); averageResults flow(AreaLink* link); averageResults thermalGeneration(ThermalCluster* cluster); averageResults thermalNbUnitsON(ThermalCluster* cluster); @@ -139,6 +140,7 @@ class ScenarioBuilderRule public: ScenarioBuilderRule(Study& study); loadTSNumberData& load() { return rules_->load; } + hydroMaxPowerTSNumberData& hydroMaxPower() { return rules_->hydroMaxPower; } BindingConstraintsTSNumberData& bcGroup() { return rules_->binding_constraints; } private: diff --git a/src/tests/src/libs/antares/study/CMakeLists.txt b/src/tests/src/libs/antares/study/CMakeLists.txt index 32b083f692..97efe56cc4 100644 --- a/src/tests/src/libs/antares/study/CMakeLists.txt +++ b/src/tests/src/libs/antares/study/CMakeLists.txt @@ -6,6 +6,7 @@ add_subdirectory(output-folder) add_subdirectory(short-term-storage-input) add_subdirectory(thermal-price-definition) add_subdirectory(constraint) +add_subdirectory(parts) add_subdirectory(series) add_executable(test-study) 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 65032a839c..bf7ab69ebe 100644 --- a/src/tests/src/libs/antares/study/constraint/test_constraint.cpp +++ b/src/tests/src/libs/antares/study/constraint/test_constraint.cpp @@ -33,7 +33,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 0603e59f77..3816aa744f 100644 --- a/src/tests/src/libs/antares/study/constraint/test_group.cpp +++ b/src/tests/src/libs/antares/study/constraint/test_group.cpp @@ -29,7 +29,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..52937063c8 --- /dev/null +++ b/src/tests/src/libs/antares/study/parts/hydro/CMakeLists.txt @@ -0,0 +1,44 @@ +# Hydro data reader +set(SRC_HYDRO_READER + test-hydroreader-class.cpp) + +add_executable(test-hydro-reader ${SRC_HYDRO_READER}) + +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_link_libraries(test-hydro-series + PRIVATE + Boost::unit_test_framework + Antares::study + 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..bfe80d14e1 --- /dev/null +++ b/src/tests/src/libs/antares/study/parts/hydro/test-hydro-series.cpp @@ -0,0 +1,254 @@ +#define BOOST_TEST_MODULE test hydro series +#define BOOST_TEST_DYN_LINK + +#define WIN32_LEAN_AND_MEAN + +#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 = Antares::Data::StudyVersion(9, 1); + 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 + std::string buffer; + createFolder(base_folder, series_folder); + + // area folder + std::string 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; + std::string base_folder = fs::temp_directory_path().string(); + std::string series_folder = "series"; + std::string maxHourlyGenPower_file = "maxHourlyGenPower.txt"; + std::string maxHourlyPumpPower_file = "maxHourlyPumpPower.txt"; + std::string pathToMaxHourlyGenPower_file; + std::string pathToMaxHourlyPumpPower_file; + std::string 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; + + auto& maxHourlyGenPower = area_1->hydro.series->maxHourlyGenPower.timeSeries; + auto& maxHourlyPumpPower = area_1->hydro.series->maxHourlyPumpPower.timeSeries; + 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); + + BOOST_CHECK_EQUAL(maxHourlyGenPower.width, 3); +} + +BOOST_FIXTURE_TEST_CASE(Testing_load_power_credits_both_matrix_equal_width_and_derated, Fixture) +{ + bool ret = true; + study->parameters.derated = true; + StudyVersion studyVersion(9, 1); + bool usedBySolver = true; + + auto& maxHourlyGenPower = area_1->hydro.series->maxHourlyGenPower.timeSeries; + auto& maxHourlyPumpPower = area_1->hydro.series->maxHourlyPumpPower.timeSeries; + 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); + area_1->hydro.series->resizeTSinDeratedMode(study->parameters.derated, studyVersion, usedBySolver); + + BOOST_CHECK_EQUAL(maxHourlyGenPower.width, 1); + BOOST_CHECK_EQUAL(maxHourlyPumpPower.width, 1); +} + +BOOST_FIXTURE_TEST_CASE(Testing_load_power_credits_matrices_different_width_case_2, Fixture) +{ + bool ret = true; + + auto& maxHourlyGenPower = area_1->hydro.series->maxHourlyGenPower.timeSeries; + auto& maxHourlyPumpPower = area_1->hydro.series->maxHourlyPumpPower.timeSeries; + 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); +} + +BOOST_FIXTURE_TEST_CASE(Testing_load_power_credits_different_width_case_1, Fixture) +{ + bool ret = true; + + auto& maxHourlyGenPower = area_1->hydro.series->maxHourlyGenPower.timeSeries; + auto& maxHourlyPumpPower = area_1->hydro.series->maxHourlyPumpPower.timeSeries; + 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); + + BOOST_CHECK_EQUAL(maxHourlyGenPower.width, maxHourlyPumpPower.width); +} + +BOOST_FIXTURE_TEST_CASE(Testing_load_power_credits_different_width_case_2, Fixture) +{ + bool ret = true; + + auto& maxHourlyGenPower = area_1->hydro.series->maxHourlyGenPower.timeSeries; + auto& maxHourlyPumpPower = area_1->hydro.series->maxHourlyPumpPower.timeSeries; + 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); + + BOOST_CHECK_EQUAL(maxHourlyGenPower.width, maxHourlyPumpPower.width); +} + +BOOST_FIXTURE_TEST_CASE(Testing_load_power_credits_both_zeros, Fixture) +{ + bool ret = true; + + auto& maxHourlyGenPower = area_1->hydro.series->maxHourlyGenPower.timeSeries; + auto& maxHourlyPumpPower = area_1->hydro.series->maxHourlyPumpPower.timeSeries; + 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); + + BOOST_CHECK_EQUAL(maxHourlyGenPower.width, maxHourlyPumpPower.width); +} + +BOOST_AUTO_TEST_SUITE_END() 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..d8ddafbdbd --- /dev/null +++ b/src/tests/src/libs/antares/study/parts/hydro/test-hydroreader-class.cpp @@ -0,0 +1,183 @@ +#define BOOST_TEST_MODULE test hydro reader +#define BOOST_TEST_DYN_LINK + +#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); + + // Add areas + area_1 = study->areaAdd("Area1"); + study->areas.rebuildIndexes(); + dailyMaxPumpAndGen.reset(4U, DAYS_PER_YEAR); + reader = std::make_shared( + area_1->hydro, area_1->id.to(), area_1->name.to()); + + // Create necessary folders and files for these two areas + createFoldersAndFiles(); + + auto& gen = dailyMaxPumpAndGen[HydroMaxTimeSeriesReader::genMaxP]; + fillColumnWithSpecialEnds(gen, 300., DAYS_PER_YEAR); + + auto& pump = dailyMaxPumpAndGen[HydroMaxTimeSeriesReader::pumpMaxP]; + fillColumnWithSpecialEnds(pump, 200., DAYS_PER_YEAR); + + auto& hoursGen = dailyMaxPumpAndGen[HydroMaxTimeSeriesReader::genMaxE]; + fillColumnWithSpecialEnds(hoursGen, 20., DAYS_PER_YEAR); + + auto& hoursPump = dailyMaxPumpAndGen[HydroMaxTimeSeriesReader::pumpMaxE]; + fillColumnWithSpecialEnds(hoursPump, 14., DAYS_PER_YEAR); + + std::string buffer; + buffer.clear(); + buffer = base_folder + SEP + hydro_folder + SEP + common_folder + SEP + capacity_folder + + SEP + maxpower + area_1->id.c_str() + ".txt"; + dailyMaxPumpAndGen.saveToCSVFile(buffer, 2); + } + + void createFoldersAndFiles() + { + std::string buffer; + std::string area1_ID = area_1->id.c_str(); + std::string maxpowerArea1 = maxpower + area1_ID + ".txt"; + std::string maxDailyGenEnergy_Area1 = maxDailyGenEnergy_ + area1_ID + ".txt"; + std::string 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; + std::string base_folder = fs::temp_directory_path().string(); + std::string hydro_folder = "hydro"; + std::string series_folder = "series"; + std::string common_folder = "common"; + std::string capacity_folder = "capacity"; + std::string maxDailyGenEnergy_ = "maxDailyGenEnergy_"; + std::string maxDailyPumpEnergy_ = "maxDailyPumpEnergy_"; + std::string maxpower = "maxpower_"; + std::string maxHourlyGenPower = "maxHourlyGenPower.txt"; + std::string maxHourlyPumpPower = "maxHourlyPumpPower.txt"; + Matrix dailyMaxPumpAndGen; + + ~Fixture() + { + removeFolder(base_folder, hydro_folder); + } +}; + +BOOST_AUTO_TEST_SUITE(s) + +BOOST_FIXTURE_TEST_CASE(Testing_support_for_old_studies, Fixture) +{ + std::string buffer; + bool ret = true; + + auto& genP = area_1->hydro.series->maxHourlyGenPower.timeSeries[0]; + auto& pumpP = area_1->hydro.series->maxHourlyPumpPower.timeSeries[0]; + auto& genE = area_1->hydro.dailyNbHoursAtGenPmax[0]; + auto& pumpE = area_1->hydro.dailyNbHoursAtPumpPmax[0]; + + auto& genPReader = dailyMaxPumpAndGen[HydroMaxTimeSeriesReader::genMaxP]; + auto& pumpPReader = dailyMaxPumpAndGen[HydroMaxTimeSeriesReader::pumpMaxP]; + auto& genEReader = dailyMaxPumpAndGen[HydroMaxTimeSeriesReader::genMaxE]; + auto& pumpEReader = dailyMaxPumpAndGen[HydroMaxTimeSeriesReader::pumpMaxE]; + + buffer.clear(); + buffer = base_folder + SEP + hydro_folder; + ret = reader->read(buffer, 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 a0d301f162..3e2201ba38 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 @@ -99,9 +99,15 @@ struct Fixture // Hydro : set the nb of ready made TS nbReadyMadeTS = 12; - area_1->hydro.series->resizeGenerationTS(nbReadyMadeTS, 1); - area_2->hydro.series->resizeGenerationTS(nbReadyMadeTS, 1); - area_3->hydro.series->resizeGenerationTS(nbReadyMadeTS, 1); + area_1->hydro.series->resizeGenerationTS(nbReadyMadeTS); + area_2->hydro.series->resizeGenerationTS(nbReadyMadeTS); + area_3->hydro.series->resizeGenerationTS(nbReadyMadeTS); + + // Hydro Max Power: set the nb of ready made TS + nbReadyMadeTS = 15; + area_1->hydro.series->resizeMaxPowerTS(nbReadyMadeTS); + area_2->hydro.series->resizeMaxPowerTS(nbReadyMadeTS); + area_3->hydro.series->resizeMaxPowerTS(nbReadyMadeTS); // Links link_12 = AreaAddLinkBetweenAreas(area_1, area_2, false); @@ -238,6 +244,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 e5e09773e9..bd76b027c0 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 @@ -140,9 +140,15 @@ struct commonFixture // Hydro : set the nb of ready made TS nbReadyMadeTS = 12; - area_1->hydro.series->resizeGenerationTS(nbReadyMadeTS, 1); - area_2->hydro.series->resizeGenerationTS(nbReadyMadeTS, 1); - area_3->hydro.series->resizeGenerationTS(nbReadyMadeTS, 1); + area_1->hydro.series->resizeGenerationTS(nbReadyMadeTS); + area_2->hydro.series->resizeGenerationTS(nbReadyMadeTS); + area_3->hydro.series->resizeGenerationTS(nbReadyMadeTS); + + // Hydro Max Power : set the nb of ready made TS + nbReadyMadeTS = 15; + area_1->hydro.series->resizeMaxPowerTS(nbReadyMadeTS); + area_2->hydro.series->resizeMaxPowerTS(nbReadyMadeTS); + area_3->hydro.series->resizeMaxPowerTS(nbReadyMadeTS); // Links link_12 = AreaAddLinkBetweenAreas(area_1, area_2, false); @@ -355,6 +361,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/libs/antares/writer/test_zip_writer.cpp b/src/tests/src/libs/antares/writer/test_zip_writer.cpp index 9443c09eda..d05e0b4fcf 100644 --- a/src/tests/src/libs/antares/writer/test_zip_writer.cpp +++ b/src/tests/src/libs/antares/writer/test_zip_writer.cpp @@ -27,7 +27,7 @@ #include "antares/writer/i_writer.h" #include "antares/writer/writer_factory.h" #include "antares/benchmarking/DurationCollector.h" -#include "utils.h" +#include "files-system.h" extern "C" { 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 a2cf44499d..cdd26a923f 100644 --- a/src/tests/src/solver/simulation/test-store-timeseries-number.cpp +++ b/src/tests/src/solver/simulation/test-store-timeseries-number.cpp @@ -31,7 +31,7 @@ #include #include "antares/solver/simulation/timeseries-numbers.h" #include "antares/solver/simulation/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 7e1cd1c2bb..2f18a90f4b 100644 --- a/src/tests/src/solver/simulation/test-time_series.cpp +++ b/src/tests/src/solver/simulation/test-time_series.cpp @@ -29,7 +29,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 242bc7b5f9..fe3f0a00df 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..2b63dc09e8 --- /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 std::string& path, const std::string& 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 std::string& folder_path, const std::string& 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(std::string& path, std::string& 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 81% rename from src/tests/src/utils/utils.h rename to src/tests/src/utils/files-system.h index c30a0e5627..318dec2569 100644 --- a/src/tests/src/utils/utils.h +++ b/src/tests/src/utils/files-system.h @@ -21,8 +21,15 @@ #pragma once #include +#include +#include +#include // 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 std::string& path, const std::string& folder_name); +void createFile(const std::string& folder_path, const std::string& file_name); +void removeFolder(std::string& path, std::string& folder_name); diff --git a/src/tests/src/utils/utils.cpp b/src/tests/src/utils/utils.cpp deleted file mode 100644 index 2047f8728f..0000000000 --- a/src/tests/src/utils/utils.cpp +++ /dev/null @@ -1,31 +0,0 @@ -/* -** Copyright 2007-2024, RTE (https://www.rte-france.com) -** See AUTHORS.txt -** SPDX-License-Identifier: MPL-2.0 -** This file is part of Antares-Simulator, -** Adequacy and Performance assessment for interconnected energy networks. -** -** Antares_Simulator is free software: you can redistribute it and/or modify -** it under the terms of the Mozilla Public Licence 2.0 as published by -** the Mozilla Foundation, either version 2 of the License, or -** (at your option) any later version. -** -** 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 -** Mozilla Public Licence 2.0 for more details. -** -** You should have received a copy of the Mozilla Public Licence 2.0 -** along with Antares_Simulator. If not, see . -*/ -#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 cc671fec5d..cd2c4c6a1d 100644 --- a/src/ui/action/handler/antares-study/area/create.cpp +++ b/src/ui/action/handler/antares-study/area/create.cpp @@ -279,6 +279,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 8d775229a8..f9d219cdcd 100644 --- a/src/ui/action/handler/antares-study/area/timeseries.cpp +++ b/src/ui/action/handler/antares-study/area/timeseries.cpp @@ -51,6 +51,9 @@ DataTimeseries::DataTimeseries(Data::TimeSeriesType ts, const AnyString& areanam case Data::timeSeriesThermal: pInfos.caption << "Thermal : Timeseries"; break; + case Data::timeSeriesHydroMaxPower: + pInfos.caption << "Max-Power : Timeseries"; + break; default: break; } @@ -97,6 +100,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; } @@ -152,6 +158,11 @@ bool DataTimeseries::performWL(Context& ctx) ctx.area->hydro.series->copyGenerationTS(*source->hydro.series); break; } + case Data::timeSeriesHydroMaxPower: + { + ctx.area->hydro.series->copyMaxPowerTS(*source->hydro.series); + break; + } case Data::timeSeriesThermal: { if (ctx.cluster && ctx.originalPlant && ctx.cluster != ctx.originalPlant) diff --git a/src/ui/simulator/application/main/build/scenario-builder.cpp b/src/ui/simulator/application/main/build/scenario-builder.cpp index 5d46aaf6c0..c4963a316a 100644 --- a/src/ui/simulator/application/main/build/scenario-builder.cpp +++ b/src/ui/simulator/application/main/build/scenario-builder.cpp @@ -170,6 +170,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 { @@ -351,6 +366,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 66abc526b8..b2ab9c85fa 100644 --- a/src/ui/simulator/application/main/main.h +++ b/src/ui/simulator/application/main/main.h @@ -693,6 +693,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 e5102fff7a..d0863af5b3 100644 --- a/src/ui/simulator/toolbox/components/datagrid/renderer/area/hydromonthlypower.cpp +++ b/src/ui/simulator/toolbox/components/datagrid/renderer/area/hydromonthlypower.cpp @@ -31,45 +31,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)) @@ -87,7 +87,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) @@ -95,67 +95,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->dailyNbHoursAtGenPmax); + else if (pHydro && hoursType == HoursType::Pumping) + MatrixAncestorType::matrix(&pHydro->dailyNbHoursAtPumpPmax); 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 187379b685..987c625f06 100644 --- a/src/ui/simulator/toolbox/components/datagrid/renderer/area/hydromonthlypower.h +++ b/src/ui/simulator/toolbox/components/datagrid/renderer/area/hydromonthlypower.h @@ -34,11 +34,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 @@ -46,20 +52,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; @@ -102,7 +110,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 e4c1a16f20..16d0f1b7fd 100644 --- a/src/ui/simulator/toolbox/components/datagrid/renderer/area/timeseries.h +++ b/src/ui/simulator/toolbox/components/datagrid/renderer/area/timeseries.h @@ -326,6 +326,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.timeSeries) : 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.timeSeries) : NULL); + Renderer::ARendererArea::internalAreaChanged(area); + } +}; + // ========================= // Clusters ... // ========================= diff --git a/src/ui/simulator/toolbox/components/datagrid/renderer/cell.cpp b/src/ui/simulator/toolbox/components/datagrid/renderer/cell.cpp index 8516d20e87..dfe09e1e46 100644 --- a/src/ui/simulator/toolbox/components/datagrid/renderer/cell.cpp +++ b/src/ui/simulator/toolbox/components/datagrid/renderer/cell.cpp @@ -76,7 +76,7 @@ bool Cell::isTSgeneratorOn() const // =================== // Blank cell // =================== -blankCell::blankCell() : Cell(timeSeriesCount /*arbitrary, not used here anyway */) +blankCell::blankCell() : Cell(timeSeriesLoad /*arbitrary, not used here anyway */) { } wxString blankCell::cellValue() const @@ -100,7 +100,7 @@ IRenderer::CellStyle blankCell::cellStyle() const // Inactive cell // ======================== inactiveCell::inactiveCell(wxString toPrintInCell) : - Cell(timeSeriesCount /*arbitrary, not used here anyway */), toBePrintedInCell_(toPrintInCell) + Cell(timeSeriesLoad /*arbitrary, not used here anyway */), toBePrintedInCell_(toPrintInCell) { } diff --git a/src/ui/simulator/toolbox/components/datagrid/renderer/column.cpp b/src/ui/simulator/toolbox/components/datagrid/renderer/column.cpp index e9b3fe8ee7..e8caa9aad0 100644 --- a/src/ui/simulator/toolbox/components/datagrid/renderer/column.cpp +++ b/src/ui/simulator/toolbox/components/datagrid/renderer/column.cpp @@ -157,6 +157,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 62688f12f6..377dea0f5c 100644 --- a/src/ui/simulator/toolbox/components/datagrid/renderer/column.h +++ b/src/ui/simulator/toolbox/components/datagrid/renderer/column.h @@ -92,6 +92,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 44d3afb94a..7c2dbab232 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 @@ -63,6 +63,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 efc27ebc6e..3020f38715 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 @@ -40,6 +40,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 e41f805379..eb2520c063 100644 --- a/src/ui/simulator/toolbox/components/datagrid/renderer/ts-management.cpp +++ b/src/ui/simulator/toolbox/components/datagrid/renderer/ts-management.cpp @@ -40,6 +40,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/analyzer/analyzer.cpp b/src/ui/simulator/windows/analyzer/analyzer.cpp index 0afe2c702e..49dc8aef93 100644 --- a/src/ui/simulator/windows/analyzer/analyzer.cpp +++ b/src/ui/simulator/windows/analyzer/analyzer.cpp @@ -553,7 +553,7 @@ AnalyzerWizard::AnalyzerWizard(wxFrame* parent) : pAnalyzeSource(nullptr), pCheckRelationship(nullptr), pFileSearch(nullptr), - pTSSelected(Data::timeSeriesCount), // invalid in our case + pTSSelected(Data::timeSeriesLoad), // invalid in our case pUpdating(false) { pRefreshTimer = new AnalyzeTimer(*this); diff --git a/src/ui/simulator/windows/hydro/dailypower.cpp b/src/ui/simulator/windows/hydro/dailypower.cpp index 7f26a61791..3d9a28f29e 100644 --- a/src/ui/simulator/windows/hydro/dailypower.cpp +++ b/src/ui/simulator/windows/hydro/dailypower.cpp @@ -79,16 +79,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 3b474bb3e4..e6291f63d2 100644 --- a/src/ui/simulator/windows/hydro/series.cpp +++ b/src/ui/simulator/windows/hydro/series.cpp @@ -51,6 +51,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);