From 9e6cdcc717a4fbdd2bc11c55a939d99a90b9428a Mon Sep 17 00:00:00 2001 From: payetvin <113102157+payetvin@users.noreply.github.com> Date: Fri, 21 Jun 2024 12:01:05 +0200 Subject: [PATCH 1/3] Add ts-generation for links [ANT-1084] (#1986) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Florian Omnès <26088210+flomnes@users.noreply.github.com> Co-authored-by: Florian OMNES Co-authored-by: guilpier-code <62292552+guilpier-code@users.noreply.github.com> --- .github/workflows/ubuntu.yml | 9 ++ .github/workflows/windows-vcpkg.yml | 11 +- docs/user-guide/04-migration-guides.md | 20 ++++ src/libs/antares/study/area/links.cpp | 106 +++++++++++++++++- src/libs/antares/study/area/list.cpp | 2 +- .../antares/study/cleaner/cleaner-v20.cpp | 2 +- src/libs/antares/study/fwd.cpp | 4 + .../study/include/antares/study/area/area.h | 3 +- .../study/include/antares/study/area/links.h | 5 + .../antares/study/include/antares/study/fwd.h | 2 + .../include/antares/study/load-options.h | 4 + .../study/include/antares/study/parameters.h | 2 + src/libs/antares/study/parameters.cpp | 9 ++ .../antares/solver/simulation/solver.hxx | 1 + src/solver/ts-generator/availability.cpp | 90 ++++++++++++++- .../antares/solver/ts-generator/generator.h | 13 +++ .../antares/solver/ts-generator/prepro.h | 21 ++++ src/tools/ts-generator/main.cpp | 70 +++++++++++- 18 files changed, 362 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index c774854d16..85713eeb70 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -229,6 +229,15 @@ jobs: os: ${{ env.os }} variant: "parallel" + - name: Run tests for time series generator tool + if: ${{ env.RUN_SIMPLE_TESTS == 'true' }} + uses: ./.github/workflows/run-tests + with: + simtest-tag: ${{steps.simtest-version.outputs.prop}} + batch-name: ts-generator + os: ${{ env.os }} + variant: "tsgenerator" + - name: Run medium-tests if: ${{ env.RUN_EXTENDED_TESTS == 'true' }} uses: ./.github/workflows/run-tests diff --git a/.github/workflows/windows-vcpkg.yml b/.github/workflows/windows-vcpkg.yml index ccc8794dcb..59e931e6a8 100644 --- a/.github/workflows/windows-vcpkg.yml +++ b/.github/workflows/windows-vcpkg.yml @@ -246,6 +246,15 @@ jobs: os: ${{ env.test-platform }} variant: "parallel" + - name: Run tests for time series generator tool + if: ${{ env.RUN_SIMPLE_TESTS == 'true' }} + uses: ./.github/workflows/run-tests + with: + simtest-tag: ${{steps.simtest-version.outputs.prop}} + batch-name: ts-generator + os: ${{ env.test-platform }} + variant: "tsgenerator" + - name: Run medium-tests if: ${{ env.RUN_EXTENDED_TESTS == 'true' }} uses: ./.github/workflows/run-tests @@ -304,7 +313,7 @@ jobs: run: | cd _build cpack -G ZIP - + - name: Installer upload uses: actions/upload-artifact@v4 with: diff --git a/docs/user-guide/04-migration-guides.md b/docs/user-guide/04-migration-guides.md index 0e5f50726c..5dbc3f8885 100644 --- a/docs/user-guide/04-migration-guides.md +++ b/docs/user-guide/04-migration-guides.md @@ -1,5 +1,25 @@ # Migration guides 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.2.0 +### (TS-generator only) TS generation for link capacities +In files input/links//properties.ini, add the following properties +- tsgen_direct_XXX, +- tsgen_indirect_XXX +with XXX in +- unitcount (unsigned int, default 1) +- nominalcapacity (float) +- law.planned (string "uniform"/"geometric") +- law.forced (same) +- volatility.planned (double in [0,1]) +- volatility.forced (same) + +- "prepro" timeseries => input/links//prepro/_{direct, indirect}.txt, 365x6 values, respectively "forced outage duration", "planned outage duration", "forced outage rate", "planned outage rate", "minimum of groups in maintenance", "maximum of groups in maintenance". +- "modulation" timeseries => input/links//prepro/_mod_{direct, indirect}.txt, 8760x1 values each in [0, 1] +- number of TS to generate => generaldata.ini/General/nbtimeserieslinks (unsigned int, default value 1) + + + ## v9.1.0 ### Input #### Hydro Maximum Generation/Pumping Power diff --git a/src/libs/antares/study/area/links.cpp b/src/libs/antares/study/area/links.cpp index 4d4b6af140..40da9df149 100644 --- a/src/libs/antares/study/area/links.cpp +++ b/src/libs/antares/study/area/links.cpp @@ -21,8 +21,11 @@ #include "antares/study/area/links.h" +#include #include +#include + #include #include @@ -134,6 +137,58 @@ bool AreaLink::linkLoadTimeSeries_for_version_820_and_later(const AnyString& fol return success; } +// This function is "lazy", it only loads files if they exist +// and set a `valid` flag +bool AreaLink::loadTSGenTimeSeries(const fs::path& folder) +{ + const std::string idprepro = std::string(from->id) + "/" + std::string(with->id); + tsGeneration.prepro = + std::make_unique(idprepro, tsGeneration.unitCount); + + bool anyFileWasLoaded = false; + + // file name without suffix, .txt for general infos and mod_direct/indirect.txt + fs::path preproFile = folder / "prepro" / with->id.c_str(); + + // Prepro + fs::path filepath = preproFile; + filepath += ".txt"; + if (fs::exists(filepath)) + { + anyFileWasLoaded = true; + tsGeneration.valid = tsGeneration.prepro->data.loadFromCSVFile( + filepath.string(), + Antares::Data::PreproAvailability::preproAvailabilityMax, + DAYS_PER_YEAR) + && tsGeneration.prepro->validate(); + } + + // Modulation + filepath = preproFile; + filepath += "_mod_direct.txt"; + if (fs::exists(filepath)) + { + anyFileWasLoaded = true; + tsGeneration.valid &= tsGeneration.modulationCapacityDirect + .loadFromCSVFile(filepath.string(), 1, HOURS_PER_YEAR); + } + + filepath = preproFile; + filepath += "_mod_indirect.txt"; + if (fs::exists(filepath)) + { + anyFileWasLoaded = true; + tsGeneration.valid &= tsGeneration.modulationCapacityIndirect + .loadFromCSVFile(filepath.string(), 1, HOURS_PER_YEAR); + } + + if (anyFileWasLoaded) + { + return tsGeneration.valid; + } + return true; +} + bool AreaLink::isLinkPhysical() const { // All link types are physical, except arVirt @@ -448,13 +503,55 @@ bool handleKey(Data::AreaLink& link, const String& key, const String& value) link.filterYearByYear = stringIntoDatePrecision(value); return true; } + return false; +} + +bool handleTSGenKey(Data::LinkTsGeneration& out, + const std::string& key, + const String& value) +{ + + if (key == "unitcount") + { + return value.to(out.unitCount); + } + + if (key == "nominalcapacity") + { + return value.to(out.nominalCapacity); + } + + if (key == "law.planned") + { + return value.to(out.plannedLaw); + } + + if (key == "law.forced") + { + return value.to(out.forcedLaw); + } + + if (key == "volatility.planned") + { + return value.to(out.plannedVolatility); + } + + if (key == "volatility.forced") + { + return value.to(out.forcedVolatility); + } + + if (key == "force-no-generation") + { + return value.to(out.forceNoGeneration); + } return false; } bool AreaLinksInternalLoadFromProperty(AreaLink& link, const String& key, const String& value) { - return handleKey(link, key, value); + return handleKey(link, key, value) || handleTSGenKey(link.tsGeneration, key, value); } [[noreturn]] void logLinkDataCheckError(const AreaLink& link, const String& msg, int hour) @@ -475,7 +572,7 @@ bool AreaLinksInternalLoadFromProperty(AreaLink& link, const String& key, const } } // anonymous namespace -bool AreaLinksLoadFromFolder(Study& study, AreaList* l, Area* area, const fs::path& folder) +bool AreaLinksLoadFromFolder(Study& study, AreaList* l, Area* area, const fs::path& folder, bool loadTSGen) { // Assert assert(area); @@ -601,6 +698,11 @@ bool AreaLinksLoadFromFolder(Study& study, AreaList* l, Area* area, const fs::pa } } + if (loadTSGen) + { + ret = link.loadTSGenTimeSeries(folder) && ret; + } + // From the solver only if (study.usedByTheSolver) { diff --git a/src/libs/antares/study/area/list.cpp b/src/libs/antares/study/area/list.cpp index fd2b3bb5b7..f76afb5686 100644 --- a/src/libs/antares/study/area/list.cpp +++ b/src/libs/antares/study/area/list.cpp @@ -881,7 +881,7 @@ static bool AreaListLoadFromFolderSingleArea(Study& study, // Links { fs::path folder = fs::path(study.folderInput.c_str()) / "links" / area.id.c_str(); - ret = AreaLinksLoadFromFolder(study, list, &area, folder) && ret; + ret = AreaLinksLoadFromFolder(study, list, &area, folder, options.linksLoadTSGen) && ret; } // UI diff --git a/src/libs/antares/study/cleaner/cleaner-v20.cpp b/src/libs/antares/study/cleaner/cleaner-v20.cpp index 3c8f183be4..7d1aadf58e 100644 --- a/src/libs/antares/study/cleaner/cleaner-v20.cpp +++ b/src/libs/antares/study/cleaner/cleaner-v20.cpp @@ -362,7 +362,7 @@ bool listOfFilesAnDirectoriesToKeep(StudyCleaningInfos* infos) logs.verbosityLevel = Logs::Verbosity::Warning::level; // load all links buffer.clear() << infos->folder << "/input/links/" << area->id; - if (not AreaLinksLoadFromFolder(*study, arealist, area, buffer.c_str())) + if (not AreaLinksLoadFromFolder(*study, arealist, area, buffer.c_str(), false)) { delete arealist; delete study; diff --git a/src/libs/antares/study/fwd.cpp b/src/libs/antares/study/fwd.cpp index 100a4b127e..f45f093aaf 100644 --- a/src/libs/antares/study/fwd.cpp +++ b/src/libs/antares/study/fwd.cpp @@ -53,6 +53,8 @@ const char* SeedToCString(SeedIndex seed) return "Noise on virtual Hydro costs"; case seedHydroManagement: return "Initial reservoir levels"; + case seedTsGenLinks: + return "Links time-series generation"; case seedMax: return ""; } @@ -85,6 +87,8 @@ const char* SeedToID(SeedIndex seed) return "seed-hydro-costs"; case seedHydroManagement: return "seed-initial-reservoir-levels"; + case seedTsGenLinks: + return "seed-tsgen-links"; case seedMax: return ""; } 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 6661ab8556..6fbb69dde4 100644 --- a/src/libs/antares/study/include/antares/study/area/area.h +++ b/src/libs/antares/study/include/antares/study/area/area.h @@ -727,7 +727,8 @@ AreaLink* AreaAddLinkBetweenAreas(Area* area, Area* with, bool warning = true); bool AreaLinksLoadFromFolder(Study& s, AreaList* l, Area* area, - const std::filesystem::path& folder); + const std::filesystem::path& folder, + bool loadTSGen); /*! ** \brief Save interconnections of a given area into a folder (`input/areas/[area]/ntc`) diff --git a/src/libs/antares/study/include/antares/study/area/links.h b/src/libs/antares/study/include/antares/study/area/links.h index fd4b8e69d8..5b01ba3efd 100644 --- a/src/libs/antares/study/include/antares/study/area/links.h +++ b/src/libs/antares/study/include/antares/study/area/links.h @@ -71,6 +71,8 @@ class AreaLink final: public Yuni::NonCopyable bool loadTimeSeries(const StudyVersion& version, const AnyString& folder); + bool loadTSGenTimeSeries(const std::filesystem::path& folder); + void storeTimeseriesNumbers(Solver::IResultWriter& writer) const; //! \name Area @@ -206,6 +208,9 @@ class AreaLink final: public Yuni::NonCopyable int linkWidth; friend struct CompareLinkName; + + LinkTsGeneration tsGeneration; + }; // class AreaLink struct CompareLinkName final diff --git a/src/libs/antares/study/include/antares/study/fwd.h b/src/libs/antares/study/include/antares/study/fwd.h index 6fa2819aca..7256eda4a2 100644 --- a/src/libs/antares/study/include/antares/study/fwd.h +++ b/src/libs/antares/study/include/antares/study/fwd.h @@ -361,6 +361,8 @@ enum SeedIndex seedHydroCosts, //! Seed - Hydro management seedHydroManagement, + //! The seed for links + seedTsGenLinks, //! The number of seeds seedMax, }; diff --git a/src/libs/antares/study/include/antares/study/load-options.h b/src/libs/antares/study/include/antares/study/load-options.h index 898b641653..1ecd34b968 100644 --- a/src/libs/antares/study/include/antares/study/load-options.h +++ b/src/libs/antares/study/include/antares/study/load-options.h @@ -52,6 +52,10 @@ class StudyLoadOptions bool loadOnlyNeeded; //! Force the year-by-year flag bool forceYearByYear; + + //! Load data associated to link TS generation + bool linksLoadTSGen = false; + //! Force the derated mode bool forceDerated; diff --git a/src/libs/antares/study/include/antares/study/parameters.h b/src/libs/antares/study/include/antares/study/parameters.h index c66cf9757f..cb69fb4326 100644 --- a/src/libs/antares/study/include/antares/study/parameters.h +++ b/src/libs/antares/study/include/antares/study/parameters.h @@ -256,6 +256,8 @@ class Parameters final uint nbTimeSeriesThermal; //! Nb of timeSeries : Solar uint nbTimeSeriesSolar; + //! Nb of timeSeries : Links + uint nbLinkTStoGenerate = 1; //@} //! \name Time-series refresh diff --git a/src/libs/antares/study/parameters.cpp b/src/libs/antares/study/parameters.cpp index a598ee1566..5c0cdac583 100644 --- a/src/libs/antares/study/parameters.cpp +++ b/src/libs/antares/study/parameters.cpp @@ -528,6 +528,10 @@ static bool SGDIntLoadFamily_General(Parameters& d, { return value.to(d.nbTimeSeriesSolar); } + if (key == "nbtimeserieslinks") + { + return value.to(d.nbLinkTStoGenerate); + } // Interval values if (key == "refreshintervalload") { @@ -1054,6 +1058,10 @@ static bool SGDIntLoadFamily_SeedsMersenneTwister(Parameters& d, { return value.to(d.seed[seedTsGenSolar]); } + if (key == "seed_links") + { + return value.to(d.seed[seedTsGenLinks]); + } if (key == "seed_timeseriesnumbers") { return value.to(d.seed[seedTimeseriesNumbers]); @@ -1783,6 +1791,7 @@ void Parameters::saveToINI(IniFile& ini) const section->add("nbTimeSeriesWind", nbTimeSeriesWind); section->add("nbTimeSeriesThermal", nbTimeSeriesThermal); section->add("nbTimeSeriesSolar", nbTimeSeriesSolar); + section->add("nbtimeserieslinks", nbLinkTStoGenerate); // Refresh ParametersSaveTimeSeries(section, "refreshTimeSeries", timeSeriesToRefresh); diff --git a/src/solver/simulation/include/antares/solver/simulation/solver.hxx b/src/solver/simulation/include/antares/solver/simulation/solver.hxx index 8084130e9f..5e36fcaca8 100644 --- a/src/solver/simulation/include/antares/solver/simulation/solver.hxx +++ b/src/solver/simulation/include/antares/solver/simulation/solver.hxx @@ -938,6 +938,7 @@ static inline void logPerformedYearsInAset(setOfParallelYears& set) << " perfomed)"; std::string performedYearsToLog = ""; + std::ranges::for_each(set.yearsIndices, [&set, &performedYearsToLog](const uint& y) { diff --git a/src/solver/ts-generator/availability.cpp b/src/solver/ts-generator/availability.cpp index f9b50696df..69f62a10a1 100644 --- a/src/solver/ts-generator/availability.cpp +++ b/src/solver/ts-generator/availability.cpp @@ -50,12 +50,29 @@ AvailabilityTSGeneratorData::AvailabilityTSGeneratorData(Data::ThermalCluster* s { } +AvailabilityTSGeneratorData::AvailabilityTSGeneratorData(Data::LinkTsGeneration& source, + Data::TimeSeries& capacity, + Matrix<>& modulation, + const std::string& areaDestName): + unitCount(source.unitCount), + nominalCapacity(source.nominalCapacity), + forcedVolatility(source.forcedVolatility), + plannedVolatility(source.plannedVolatility), + forcedLaw(source.forcedLaw), + plannedLaw(source.plannedLaw), + prepro(source.prepro.get()), + series(capacity.timeSeries), + modulationCapacity(modulation[0]), + name(areaDestName) +{ +} + namespace { class GeneratorTempData final { public: - explicit GeneratorTempData(Data::Study&, unsigned); + explicit GeneratorTempData(Data::Study&, unsigned, MersenneTwister&); void generateTS(const Data::Area& area, AvailabilityTSGeneratorData& cluster) const; @@ -82,10 +99,10 @@ class GeneratorTempData final const T& duration) const; }; -GeneratorTempData::GeneratorTempData(Data::Study& study, unsigned nbOfSeriesToGen): +GeneratorTempData::GeneratorTempData(Data::Study& study, unsigned nbOfSeriesToGen, MersenneTwister& rndGenerator): derated(study.parameters.derated), nbOfSeriesToGen_(nbOfSeriesToGen), - rndgenerator(study.runtime->random[Data::seedTsGenThermal]) + rndgenerator(rndGenerator) { } @@ -592,6 +609,23 @@ std::vector getAllClustersToGen(const Data::AreaList& are return clusters; } +listOfLinks getAllLinksToGen(Data::AreaList& areas) +{ + listOfLinks links; + + areas.each( + [&links](const Data::Area& area) + { + std::ranges::for_each(area.links, [&links](auto& l) + { + if (!l.second->tsGeneration.forceNoGeneration) + links.push_back(l.second); + }); + }); + + return links; +} + void writeResultsToDisk(const Data::Study& study, Solver::IResultWriter& writer, const Matrix<>& series, @@ -617,7 +651,9 @@ bool generateThermalTimeSeries(Data::Study& study, bool archive = study.parameters.timeSeriesToArchive & Data::timeSeriesThermal; - auto generator = GeneratorTempData(study, study.parameters.nbTimeSeriesThermal); + auto generator = GeneratorTempData(study, + study.parameters.nbTimeSeriesThermal, + study.runtime->random[Data::seedTsGenThermal]); // TODO VP: parallel for (auto* cluster: clusters) @@ -636,4 +672,50 @@ bool generateThermalTimeSeries(Data::Study& study, return true; } +bool generateLinkTimeSeries(Data::Study& study, + const listOfLinks& links, + Solver::IResultWriter& writer, + const std::string& savePath) +{ + logs.info(); + logs.info() << "Generating the links time-series"; + + auto generator = GeneratorTempData(study, + study.parameters.nbLinkTStoGenerate, + study.runtime->random[Data::seedTsGenLinks]); + + for (const auto& link: links) + { + Data::TimeSeries ts(link->timeseriesNumbers); + ts.resize(study.parameters.nbLinkTStoGenerate, HOURS_PER_YEAR); + + auto& tsGenStruct = link->tsGeneration; + + if (!tsGenStruct.valid) + { + logs.error() << "Missing data for link " << link->from->id << "/" << link->with->id; + return false; + } + + // DIRECT + AvailabilityTSGeneratorData tsConfigDataDirect(tsGenStruct, ts, tsGenStruct.modulationCapacityDirect, link->with->name); + + generator.generateTS(*link->from, tsConfigDataDirect); + + std::string filePath = savePath + SEP + link->from->id + SEP + link->with->id.c_str() + + "_direct.txt"; + writeResultsToDisk(study, writer, ts.timeSeries, filePath); + + // INDIRECT + AvailabilityTSGeneratorData tsConfigDataIndirect(tsGenStruct, ts, tsGenStruct.modulationCapacityIndirect, link->with->name); + + generator.generateTS(*link->from, tsConfigDataIndirect); + + filePath = savePath + SEP + link->from->id + SEP + link->with->id.c_str() + + "_indirect.txt"; + writeResultsToDisk(study, writer, ts.timeSeries, filePath); + } + + return true; +} } // namespace Antares::TSGenerator diff --git a/src/solver/ts-generator/include/antares/solver/ts-generator/generator.h b/src/solver/ts-generator/include/antares/solver/ts-generator/generator.h index 77e09b9b79..1c32e67bdd 100644 --- a/src/solver/ts-generator/include/antares/solver/ts-generator/generator.h +++ b/src/solver/ts-generator/include/antares/solver/ts-generator/generator.h @@ -39,6 +39,10 @@ class AvailabilityTSGeneratorData { public: explicit AvailabilityTSGeneratorData(Data::ThermalCluster*); + AvailabilityTSGeneratorData(Data::LinkTsGeneration&, + Data::TimeSeries&, + Matrix<>& modulation, + const std::string& name); const unsigned& unitCount; const double& nominalCapacity; @@ -58,6 +62,8 @@ class AvailabilityTSGeneratorData const std::string& name; }; +using listOfLinks = std::vector; + void ResizeGeneratedTimeSeries(Data::AreaList& areas, Data::Parameters& params); /*! @@ -71,9 +77,16 @@ bool generateThermalTimeSeries(Data::Study& study, Solver::IResultWriter& writer, const std::string& savePath); +bool generateLinkTimeSeries(Data::Study& study, + const listOfLinks& links, + Solver::IResultWriter& writer, + const std::string& savePath); + std::vector getAllClustersToGen(const Data::AreaList& areas, bool globalThermalTSgeneration); +listOfLinks getAllLinksToGen(Data::AreaList& areas); + /*! ** \brief Destroy all TS Generators */ diff --git a/src/solver/ts-generator/include/antares/solver/ts-generator/prepro.h b/src/solver/ts-generator/include/antares/solver/ts-generator/prepro.h index cc02a50619..ac36a826ea 100644 --- a/src/solver/ts-generator/include/antares/solver/ts-generator/prepro.h +++ b/src/solver/ts-generator/include/antares/solver/ts-generator/prepro.h @@ -115,6 +115,27 @@ class PreproAvailability unsigned int unitCount; }; // class PreproAvailability +struct LinkTsGeneration +{ + unsigned unitCount = 0; + double nominalCapacity = 0; + + double forcedVolatility = 0.; + double plannedVolatility = 0.; + + Data::StatisticalLaw forcedLaw = LawUniform; + Data::StatisticalLaw plannedLaw = LawUniform; + + std::unique_ptr prepro; + + Matrix<> modulationCapacityDirect; + Matrix<> modulationCapacityIndirect; + + bool valid = false; + + bool forceNoGeneration = false; +}; + } // namespace Antares::Data #endif // __ANTARES_LIBS_STUDY_PARTS_THERMAL_PREPRO_HXX__ diff --git a/src/tools/ts-generator/main.cpp b/src/tools/ts-generator/main.cpp index f59a5dc614..8f23f60619 100644 --- a/src/tools/ts-generator/main.cpp +++ b/src/tools/ts-generator/main.cpp @@ -35,6 +35,10 @@ #include #include +using namespace Antares; + +namespace fs = std::filesystem; + struct Settings { std::string studyFolder; @@ -43,6 +47,11 @@ struct Settings bool allThermal = false; /// generate TS for a list "area.cluster;area2.cluster2;" std::string thermalListToGen = ""; + + /// generate TS for all links if activated + bool allLinks = false; + /// generate TS for a list "area.link;area2.link2;" + std::string linksListToGen = ""; }; std::unique_ptr createTsGeneratorParser(Settings& settings) @@ -58,7 +67,14 @@ std::unique_ptr createTsGeneratorParser(Settings& settings ' ', "thermal", "Generate TS for a list of area IDs and thermal clusters IDs, " - "usage:\n\t--thermal=\"areaID.clusterID;area2ID.clusterID\""); + "\nusage: --thermal=\"areaID.clusterID;area2ID.clusterID\""); + + parser->addFlag(settings.allLinks, ' ', "all-links", "Generate TS capacities for all links"); + parser->addFlag(settings.linksListToGen, + ' ', + "links", + "Generate TS capacities for a list of 2 area IDs, " + "usage: --links=\"areaID.area2ID;area3ID.area1ID\""); parser->remainingArguments(settings.studyFolder); @@ -95,8 +111,32 @@ std::vector getClustersToGen(Data::AreaList& areas, return clusters; } +TSGenerator::listOfLinks getLinksToGen(Data::AreaList& areas, const std::string& linksToGen) +{ + TSGenerator::listOfLinks links; + const auto ids = splitStringIntoPairs(linksToGen, ';', '.'); + + for (const auto& [areaFromID, areaWithID]: ids) + { + logs.info() << "Searching for link: " << areaFromID << "/" << areaWithID; + + auto* link = areas.findLink(areaFromID, areaWithID); + if (!link) + { + logs.warning() << "Link not found: " << areaFromID << "/" << areaWithID; + continue; + } + + links.emplace_back(link); + } + + return links; +} + int main(int argc, char* argv[]) { + logs.applicationName("ts-generator"); + Settings settings; auto parser = createTsGeneratorParser(settings); @@ -119,9 +159,16 @@ int main(int argc, char* argv[]) return 1; } + if (settings.allLinks && !settings.linksListToGen.empty()) + { + logs.error() << "Conflicting options, either choose all links or a list"; + return 1; + } + auto study = std::make_shared(true); Data::StudyLoadOptions studyOptions; studyOptions.prepareOutput = true; + studyOptions.linksLoadTSGen = true; if (!study->loadFromFolder(settings.studyFolder, studyOptions)) { @@ -149,7 +196,8 @@ int main(int argc, char* argv[]) nullptr, durationCollector); - const auto thermalSavePath = std::filesystem::path("ts-generator") / "thermal"; + const auto thermalSavePath = fs::path("ts-generator") / "thermal"; + const auto linksSavePath = fs::path("ts-generator") / "links"; // THERMAL std::vector clusters; @@ -167,10 +215,28 @@ int main(int argc, char* argv[]) logs.debug() << c->id(); } + // LINKS + TSGenerator::listOfLinks links; + if (settings.allLinks) + { + links = TSGenerator::getAllLinksToGen(study->areas); + } + else if (!settings.linksListToGen.empty()) + { + links = getLinksToGen(study->areas, settings.linksListToGen); + } + + for (auto& l: links) + { + logs.debug() << l->getName(); + } + bool ret = TSGenerator::generateThermalTimeSeries(*study, clusters, *resultWriter, thermalSavePath.string()); + ret = TSGenerator::generateLinkTimeSeries(*study, links, *resultWriter, linksSavePath.string()) + && ret; return !ret; // return 0 for success } From d23369185ef4851a2b9c63345aab73f2b0cddea1 Mon Sep 17 00:00:00 2001 From: payetvin <113102157+payetvin@users.noreply.github.com> Date: Fri, 21 Jun 2024 13:36:38 +0200 Subject: [PATCH 2/3] Separation of loading and validation for parameters [ANT-1213] (#2177) --- .../study/include/antares/study/parameters.h | 5 +- src/libs/antares/study/load.cpp | 8 +- src/libs/antares/study/parameters.cpp | 153 ++++++++---------- .../study/parameters/parameters-tests.cpp | 4 + 4 files changed, 78 insertions(+), 92 deletions(-) diff --git a/src/libs/antares/study/include/antares/study/parameters.h b/src/libs/antares/study/include/antares/study/parameters.h index cb69fb4326..791e20f865 100644 --- a/src/libs/antares/study/include/antares/study/parameters.h +++ b/src/libs/antares/study/include/antares/study/parameters.h @@ -137,6 +137,8 @@ class Parameters final */ void fixBadValues(); + void validateOptions(const StudyLoadOptions&); + /*! ** \brief Try to detect then fix refresh intervals */ @@ -503,8 +505,7 @@ class Parameters final private: //! Load data from an INI file bool loadFromINI(const IniFile& ini, - const StudyVersion& version, - const StudyLoadOptions& options); + const StudyVersion& version); void resetPlayedYears(uint nbOfYears); diff --git a/src/libs/antares/study/load.cpp b/src/libs/antares/study/load.cpp index 00214717e8..4555e106bd 100644 --- a/src/libs/antares/study/load.cpp +++ b/src/libs/antares/study/load.cpp @@ -83,7 +83,13 @@ bool Study::internalLoadIni(const String& path, const StudyLoadOptions& options) } // Load the general data buffer.clear() << folderSettings << SEP << "generaldata.ini"; - if (!parameters.loadFromFile(buffer, header.version, options)) + bool errorWhileLoading = !parameters.loadFromFile(buffer, header.version, options); + + parameters.validateOptions(options); + + parameters.fixBadValues(); + + if (errorWhileLoading) { if (options.loadOnlyNeeded) { diff --git a/src/libs/antares/study/parameters.cpp b/src/libs/antares/study/parameters.cpp index 5c0cdac583..7337b9c8b0 100644 --- a/src/libs/antares/study/parameters.cpp +++ b/src/libs/antares/study/parameters.cpp @@ -566,43 +566,11 @@ static bool SGDIntLoadFamily_General(Parameters& d, if (key == "simulation.start") { - uint day; - if (not value.to(day)) - { - return false; - } - if (day == 0) - { - day = 1; - } - else - { - if (day > 365) - { - day = 365; - } - --day; - } - d.simulationDays.first = day; - return true; + return value.to(d.simulationDays.first); } if (key == "simulation.end") { - uint day; - if (not value.to(day)) - { - return false; - } - if (day == 0) - { - day = 1; - } - else if (day > 365) - { - day = 365; - } - d.simulationDays.end = day; // not included - return true; + return value.to(d.simulationDays.end); } if (key == "thematic-trimming") @@ -1158,8 +1126,7 @@ bool firstKeyLetterIsValid(const String& name) } bool Parameters::loadFromINI(const IniFile& ini, - const StudyVersion& version, - const StudyLoadOptions& options) + const StudyVersion& version) { // Reset inner data reset(); @@ -1229,62 +1196,10 @@ bool Parameters::loadFromINI(const IniFile& ini, } } - // forcing value - if (options.nbYears != 0) - { - if (options.nbYears > nbYears) - { - // The variable `yearsFilter` must be enlarged - yearsFilter.resize(options.nbYears, false); - } - nbYears = options.nbYears; - - // Resize years weight (add or remove item) - if (yearsWeight.size() != nbYears) - { - yearsWeight.resize(nbYears, 1.f); - } - } - - // Simulation mode - // ... Enforcing simulation mode - if (options.forceMode != SimulationMode::Unknown) - { - mode = options.forceMode; - logs.info() << " forcing the simulation mode " << SimulationModeToCString(mode); - } - else - { - logs.info() << " simulation mode: " << SimulationModeToCString(mode); - } - - if (options.forceDerated) - { - derated = true; - } - - namedProblems = options.namedProblems; - - handleOptimizationOptions(options); - - // Attempt to fix bad values if any - fixBadValues(); - fixRefreshIntervals(); fixGenRefreshForNTC(); - // Specific action before launching a simulation - if (options.usedByTheSolver) - { - prepareForSimulation(options); - } - - if (options.mpsToExport || options.namedProblems) - { - this->include.exportMPS = mpsExportStatus::EXPORT_BOTH_OPTIMS; - } - // We currently always returns true to not block any loading process // Anyway we already have reported all problems return true; @@ -1375,6 +1290,66 @@ void Parameters::fixBadValues() { nbTimeSeriesSolar = 1; } + + if (simulationDays.first == 0) + simulationDays.first = 1; + else + { + simulationDays.first = std::clamp(simulationDays.first, 1u, 365u); + --simulationDays.first; // value between 0 and 364 for edge cases + } + + simulationDays.end = std::clamp(simulationDays.end, 1u, 365u); +} + +void Parameters::validateOptions(const StudyLoadOptions& options) +{ + if (options.forceDerated) + { + derated = true; + } + // forcing value + if (options.nbYears != 0) + { + if (options.nbYears > nbYears) + { + // The variable `yearsFilter` must be enlarged + yearsFilter.resize(options.nbYears, false); + } + nbYears = options.nbYears; + + // Resize years weight (add or remove item) + if (yearsWeight.size() != nbYears) + { + yearsWeight.resize(nbYears, 1.f); + } + } + + // Simulation mode + // ... Enforcing simulation mode + if (options.forceMode != SimulationMode::Unknown) + { + mode = options.forceMode; + logs.info() << " forcing the simulation mode " << SimulationModeToCString(mode); + } + else + { + logs.info() << " simulation mode: " << SimulationModeToCString(mode); + } + // Specific action before launching a simulation + if (options.usedByTheSolver) + { + prepareForSimulation(options); + } + + if (options.mpsToExport || options.namedProblems) + { + this->include.exportMPS = mpsExportStatus::EXPORT_BOTH_OPTIMS; + } + + namedProblems = options.namedProblems; + + handleOptimizationOptions(options); } uint64_t Parameters::memoryUsage() const @@ -1990,7 +1965,7 @@ bool Parameters::loadFromFile(const AnyString& filename, IniFile ini; if (ini.open(filename)) { - return loadFromINI(ini, version, options); + return loadFromINI(ini, version); } // Error otherwise diff --git a/src/tests/src/libs/antares/study/parameters/parameters-tests.cpp b/src/tests/src/libs/antares/study/parameters/parameters-tests.cpp index 14ee6a4435..07c8e7fa1d 100644 --- a/src/tests/src/libs/antares/study/parameters/parameters-tests.cpp +++ b/src/tests/src/libs/antares/study/parameters/parameters-tests.cpp @@ -70,6 +70,8 @@ BOOST_FIXTURE_TEST_CASE(loadValid, Fixture) writeValidFile(); p.loadFromFile(path.string(), version, options); + p.validateOptions(options); + p.fixBadValues(); BOOST_CHECK_EQUAL(p.nbYears, 5); BOOST_CHECK_EQUAL(p.seed[seedTsGenThermal], 5489); @@ -93,6 +95,8 @@ BOOST_FIXTURE_TEST_CASE(invalidValues, Fixture) { writeInvalidFile(); BOOST_CHECK(p.loadFromFile(path.string(), version, options)); + p.validateOptions(options); + p.fixBadValues(); BOOST_CHECK_EQUAL(p.nbYears, 1); BOOST_CHECK_EQUAL(p.useCustomScenario, 0); From b3f3fc0b9147816adf22f9f29d17b83be4e5125c Mon Sep 17 00:00:00 2001 From: guilpier-code <62292552+guilpier-code@users.noreply.github.com> Date: Fri, 21 Jun 2024 13:42:07 +0200 Subject: [PATCH 3/3] Hydro final lvl (CR 25) to merge in develop [ANT-1084] (#1521) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR attempts some code enhancements on branch **feature/final-reservoir-level-cr25**. Therefore, this PR's associated branch (**fix/hydro-final-levels-cr25--try-fixes**) is based on **feature/final-reservoir-level-cr25**. - [x] in class **FinalLevelInflowsModifier**, - try to reduce the number of data members, - making data members **private** whenever it's possible, - define a clear interface with a smallest number of public functions - and so make the more methods private as possible - [x] **Unit tests** : clarify their intention and reduce their size - [ ] **caution** : could not remove yet the **initialize** method. This method should be merged with the constructor, but when we call the constructor, all input pieces of data are not available. that's why **initialize** exists. We should try to make these pieces of data available, for instance by changing the order of data loading For any purpose, here are doc resources : - specs [here](https://rteinternational.sharepoint.com/:w:/r/sites/AntaresUser'sClub-Interne/_layouts/15/Doc.aspx?sourcedoc=%7B7774488E-7E4C-4AEC-B11F-1C9728B96AC0%7D&file=Functional_Specifications-Final_reservoir_level_V0.1-MA.docx&action=default&mobileredirect=true) - a discussion on that matter [here](https://github.com/AntaresSimulatorTeam/Antares_Simulator/discussions/1145) --- ### CAUTION : **Some cleaning and corrections were introduced in a new pull request** (see [this PR](https://github.com/AntaresSimulatorTeam/Antares_Simulator/pull/2040)). This new PR was merged into this one. **This PR contains a test under the form of a study. This study should be downloaded and added to the right test repo.** --- ### Left to to - [ ] Merge from branch develop and remove conflicts - [ ] Check that all tests exist (unit test for raising failures and study case for fine working) - [ ] what else ? --------- Co-authored-by: Milos A Co-authored-by: Milos <97689304+Milos-RTEi@users.noreply.github.com> Co-authored-by: Florian Omnès Co-authored-by: NikolaIlic --- docs/user-guide/04-migration-guides.md | 4 + src/CMakeLists.txt | 1 + src/antares-deps | 1 + src/libs/antares/study/CMakeLists.txt | 2 + src/libs/antares/study/area/area.cpp | 3 + .../antares/study/parts/hydro/container.h | 5 +- .../study/parts/hydro/finalLevelValidator.h | 73 +++++ .../study/scenario-builder/hydroLevelsData.h | 24 +- .../antares/study/scenario-builder/rules.h | 9 +- .../study/include/antares/study/study.h | 5 +- .../antares/study/parts/hydro/container.cpp | 3 +- .../study/parts/hydro/finalLevelValidator.cpp | 157 ++++++++++ .../scenario-builder/hydroLevelsData.cpp | 15 +- .../antares/study/scenario-builder/rules.cpp | 129 ++++---- .../solver/hydro/management/management.h | 2 + src/solver/hydro/management/daily.cpp | 2 + src/solver/hydro/management/management.cpp | 22 +- src/solver/hydro/management/monthly.cpp | 32 +- src/solver/simulation/CMakeLists.txt | 59 ++-- .../hydro-final-reservoir-level-functions.cpp | 68 +++++ .../hydro-final-reservoir-level-functions.h | 37 +++ .../antares/solver/simulation/solver.hxx | 22 +- .../end-to-end/simple_study/simple-study.cpp | 3 +- src/tests/end-to-end/utils/utils.cpp | 1 + .../test-sc-builder-file-read-line.cpp | 70 ++++- .../test-sc-builder-file-save.cpp | 31 +- .../src/solver/simulation/CMakeLists.txt | 29 ++ ...-hydro-final-reservoir-level-functions.cpp | 275 ++++++++++++++++++ .../main/build/scenario-builder.cpp | 30 +- src/ui/simulator/application/main/main.h | 3 +- src/ui/simulator/cmake/components.cmake | 2 + ...io-builder-hydro-final-levels-renderer.cpp | 82 ++++++ ...ario-builder-hydro-final-levels-renderer.h | 56 ++++ ...scenario-builder-hydro-levels-renderer.cpp | 30 +- 34 files changed, 1120 insertions(+), 167 deletions(-) create mode 160000 src/antares-deps create mode 100644 src/libs/antares/study/include/antares/study/parts/hydro/finalLevelValidator.h create mode 100644 src/libs/antares/study/parts/hydro/finalLevelValidator.cpp create mode 100644 src/solver/simulation/hydro-final-reservoir-level-functions.cpp create mode 100644 src/solver/simulation/include/antares/solver/simulation/hydro-final-reservoir-level-functions.h create mode 100644 src/tests/src/solver/simulation/test-hydro-final-reservoir-level-functions.cpp create mode 100644 src/ui/simulator/toolbox/components/datagrid/renderer/scenario-builder-hydro-final-levels-renderer.cpp create mode 100644 src/ui/simulator/toolbox/components/datagrid/renderer/scenario-builder-hydro-final-levels-renderer.h diff --git a/docs/user-guide/04-migration-guides.md b/docs/user-guide/04-migration-guides.md index 5dbc3f8885..c423bc5346 100644 --- a/docs/user-guide/04-migration-guides.md +++ b/docs/user-guide/04-migration-guides.md @@ -129,6 +129,10 @@ For each thermal cluster, in existing file **input/thermal/clusters/<area> For each thermal cluster, new files added **input/thermal/series/<area>/<cluster>/CO2Cost.txt** and **input/thermal/series/<area>/<cluster>/fuelCost.txt**. **fuelCost.txt** and **CO2Cost.txt** must either have one column, or the same number of columns as existing file **series.txt** (availability). The number of rows for these new matrices is 8760. +#### Hydro Final Reservoir Level +In the existing file **settings/scenariobuilder.dat**, under **<ruleset>** section following properties added (if final reservoir level specified, different from `init`): +* **hfl,<area>,<year> = <hfl-value>** + ### Output #### Scenarized RHS for binding constraints Add directory **bindingconstraints** to output directory **ts-numbers**. For every binding constraint group, add a file **ts-numbers/bindingconstraints/<group>.txt** containing the TS numbers used for that group. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 51fdc67271..862c7fdbc8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,6 +5,7 @@ set(ANTARES_VERSION_HI 9) set(ANTARES_VERSION_LO 1) set(ANTARES_VERSION_REVISION 0) + # Beta release set(ANTARES_BETA 0) set(ANTARES_RC 0) diff --git a/src/antares-deps b/src/antares-deps new file mode 160000 index 0000000000..0d6bebfb90 --- /dev/null +++ b/src/antares-deps @@ -0,0 +1 @@ +Subproject commit 0d6bebfb901e47ec6ac1c73cb61a522803c81b98 diff --git a/src/libs/antares/study/CMakeLists.txt b/src/libs/antares/study/CMakeLists.txt index c0b5b84c56..c90d543fff 100644 --- a/src/libs/antares/study/CMakeLists.txt +++ b/src/libs/antares/study/CMakeLists.txt @@ -122,6 +122,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/finalLevelValidator.h + parts/hydro/finalLevelValidator.cpp include/antares/study/parts/hydro/hydromaxtimeseriesreader.h parts/hydro/hydromaxtimeseriesreader.cpp ) diff --git a/src/libs/antares/study/area/area.cpp b/src/libs/antares/study/area/area.cpp index 77f0fe6dd9..2984704716 100644 --- a/src/libs/antares/study/area/area.cpp +++ b/src/libs/antares/study/area/area.cpp @@ -45,6 +45,7 @@ void Area::internalInitialize() } Area::Area(): + hydro(*this), reserves(fhrMax, HOURS_PER_YEAR), miscGen(fhhMax, HOURS_PER_YEAR) { @@ -52,6 +53,7 @@ Area::Area(): } Area::Area(const AnyString& name): + hydro(*this), reserves(fhrMax, HOURS_PER_YEAR), miscGen(fhhMax, HOURS_PER_YEAR) { @@ -62,6 +64,7 @@ Area::Area(const AnyString& name): Area::Area(const AnyString& name, const AnyString& id): + hydro(*this), reserves(fhrMax, HOURS_PER_YEAR), miscGen(fhhMax, HOURS_PER_YEAR) { 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 0297540a4d..e09970b79c 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 @@ -21,6 +21,7 @@ #ifndef __ANTARES_LIBS_STUDY_PARTS_HYDRO_CONTAINER_H__ #define __ANTARES_LIBS_STUDY_PARTS_HYDRO_CONTAINER_H__ +#include #include "../../fwd.h" #include "allocation.h" #include "prepro.h" @@ -80,7 +81,7 @@ class PartHydro /*! ** \brief Default Constructor */ - PartHydro(); + PartHydro(const Data::Area& area); //! Destructor ~PartHydro(); @@ -165,6 +166,8 @@ class PartHydro Matrix dailyNbHoursAtGenPmax; Matrix dailyNbHoursAtPumpPmax; + std::vector> deltaBetweenFinalAndInitialLevels; + private: static bool checkReservoirLevels(const Study& study); static bool checkProperties(Study& study); diff --git a/src/libs/antares/study/include/antares/study/parts/hydro/finalLevelValidator.h b/src/libs/antares/study/include/antares/study/parts/hydro/finalLevelValidator.h new file mode 100644 index 0000000000..518e29d40b --- /dev/null +++ b/src/libs/antares/study/include/antares/study/parts/hydro/finalLevelValidator.h @@ -0,0 +1,73 @@ +/* +** 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 +*/ +#pragma once + +#include "antares/study/parts/hydro/container.h" + +namespace Antares::Data +{ +class PartHydro; + +class FinalLevelValidator +{ +public: + FinalLevelValidator(PartHydro& hydro, + unsigned int areaIndex, + const AreaName areaName, + double initialLevel, + double finalLevel, + const unsigned int year, + const unsigned int lastSimulationDay, + const unsigned int firstMonthOfSimulation); + bool check(); + bool finalLevelFineForUse(); + +private: + bool wasSetInScenarioBuilder(); + bool compatibleWithReservoirProperties(); + bool skippingFinalLevelUse(); + bool checkForInfeasibility(); + bool hydroAllocationStartMatchesSimulation() const; + bool isFinalLevelReachable() const; + double calculateTotalInflows() const; + bool isBetweenRuleCurves() const; + + // Data from simulation + unsigned int year_ = 0; + unsigned int lastSimulationDay_ = 0; + unsigned int firstMonthOfSimulation_ = 0; + + // Data from area + PartHydro& hydro_; + unsigned int areaIndex_; + const AreaName areaName_; + double initialLevel_; + double finalLevel_; + + bool finalLevelFineForUse_ = false; +}; +} // namespace Antares::Data diff --git a/src/libs/antares/study/include/antares/study/scenario-builder/hydroLevelsData.h b/src/libs/antares/study/include/antares/study/scenario-builder/hydroLevelsData.h index 0f01fb9c32..42aff6336a 100644 --- a/src/libs/antares/study/include/antares/study/scenario-builder/hydroLevelsData.h +++ b/src/libs/antares/study/include/antares/study/scenario-builder/hydroLevelsData.h @@ -22,6 +22,7 @@ #define __LIBS_STUDY_SCENARIO_BUILDER_DATA_HYDRO_LEVELS_H__ #include "scBuilderDataInterface.h" +#include namespace Antares { @@ -39,7 +40,10 @@ class hydroLevelsData final: public dataInterface using MatrixType = Matrix; public: - // We use default constructor and destructor + // Constructor + + hydroLevelsData(const std::string& iniFilePrefix, + std::function applyToTarget); //! \name Data manupulation //@{ @@ -51,7 +55,7 @@ class hydroLevelsData final: public dataInterface /*! ** \brief Export the data into a mere INI file */ - void saveToINIFile(const Study& study, Yuni::IO::File::Stream& file) const; + void saveToINIFile(const Study& study, Yuni::IO::File::Stream& file) const override; /*! ** \brief Assign a single value @@ -71,11 +75,15 @@ class hydroLevelsData final: public dataInterface void set_value(uint x, uint y, double value); - bool apply(Study& study); + bool apply(Study& study) override; private: //! Hydro levels overlay (0 if auto) MatrixType pHydroLevelsRules; + // prefix to be added when calling saveToINIFileHydroLevel + const std::string addToPrefix_; + + std::function applyToTarget_; }; // class hydroLevelsData @@ -105,6 +113,16 @@ inline double hydroLevelsData::get_value(uint x, uint y) const return pHydroLevelsRules.entry[y][x]; } +inline void initLevelApply(Study& study, Matrix& matrix) +{ + study.scenarioInitialHydroLevels.copyFrom(matrix); +} + +inline void finalLevelApply(Study& study, Matrix& matrix) +{ + study.scenarioFinalHydroLevels.copyFrom(matrix); +} + } // namespace ScenarioBuilder } // namespace Data } // namespace Antares 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 b79d92be86..ff5261d64a 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 @@ -119,8 +119,10 @@ class Rules final: private Yuni::NonCopyable //! Renewable (array [0..pAreaCount - 1]) std::vector renewable; - //! hydro levels - hydroLevelsData hydroLevels; + //! hydro initial levels + hydroLevelsData hydroInitialLevels = {"hl,", initLevelApply}; + //! hydro final levels + hydroLevelsData hydroFinalLevels = {"hfl,", finalLevelApply}; // Links NTC std::vector linksNTC; @@ -135,7 +137,8 @@ class Rules final: private Yuni::NonCopyable bool readWind(const AreaName::Vector& instrs, String value, bool updaterMode); bool readHydro(const AreaName::Vector& instrs, 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 readInitialHydroLevels(const AreaName::Vector& instrs, String value, bool updaterMode); + bool readFinalHydroLevels(const AreaName::Vector& instrs, String value, bool updaterMode); bool readLink(const AreaName::Vector& instrs, String value, bool updaterMode); bool readBindingConstraints(const AreaName::Vector& splitKey, String value); diff --git a/src/libs/antares/study/include/antares/study/study.h b/src/libs/antares/study/include/antares/study/study.h index 709bab62fe..03e2df8bed 100644 --- a/src/libs/antares/study/include/antares/study/study.h +++ b/src/libs/antares/study/include/antares/study/study.h @@ -588,8 +588,9 @@ class Study: public Yuni::NonCopyable, public LayerData ScenarioBuilder::Sets* scenarioRules = nullptr; //@} - TimeSeries::TS scenarioHydroLevels; - + TimeSeries::TS scenarioInitialHydroLevels; + // Hydro Final Levels + TimeSeries::TS scenarioFinalHydroLevels; /*! ** \brief Runtime informations ** diff --git a/src/libs/antares/study/parts/hydro/container.cpp b/src/libs/antares/study/parts/hydro/container.cpp index d955da1c81..c9dc476de5 100644 --- a/src/libs/antares/study/parts/hydro/container.cpp +++ b/src/libs/antares/study/parts/hydro/container.cpp @@ -32,7 +32,7 @@ using namespace Yuni; namespace Antares::Data { -PartHydro::PartHydro(): +PartHydro::PartHydro(const Data::Area& area) : interDailyBreakdown(0.), intraDailyModulation(2.), intermonthlyBreakdown(0), @@ -161,6 +161,7 @@ bool PartHydro::LoadFromFolder(Study& study, const AnyString& folder) area.hydro.initializeReservoirLevelDate = 0; area.hydro.reservoirCapacity = 0.; area.hydro.pumpingEfficiency = 1.; + area.hydro.deltaBetweenFinalAndInitialLevels.resize(study.parameters.nbYears); if (study.header.version >= StudyVersion(9, 1)) { diff --git a/src/libs/antares/study/parts/hydro/finalLevelValidator.cpp b/src/libs/antares/study/parts/hydro/finalLevelValidator.cpp new file mode 100644 index 0000000000..2229f75f97 --- /dev/null +++ b/src/libs/antares/study/parts/hydro/finalLevelValidator.cpp @@ -0,0 +1,157 @@ +/* +** 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/parts/hydro/finalLevelValidator.h" + +namespace Antares::Data +{ + +FinalLevelValidator::FinalLevelValidator(PartHydro& hydro, + unsigned int areaIndex, + const AreaName areaName, // gp : to std::string + double initialLevel, + double finalLevel, + const unsigned int year, + const unsigned int lastSimulationDay, + const unsigned int firstMonthOfSimulation) + : hydro_(hydro), + areaName_(areaName), + areaIndex_(areaIndex), + initialLevel_(initialLevel), + finalLevel_(finalLevel), + year_(year), + lastSimulationDay_(lastSimulationDay), + firstMonthOfSimulation_(firstMonthOfSimulation) +{ +} + +bool FinalLevelValidator::check() +{ + if (skippingFinalLevelUse()) + return true; + if (! checkForInfeasibility()) + return false; + finalLevelFineForUse_ = true; + return true; +} + +bool FinalLevelValidator::skippingFinalLevelUse() +{ + if(! wasSetInScenarioBuilder()) + return true; + if (! compatibleWithReservoirProperties()) + return true; + return false; +} + +bool FinalLevelValidator::wasSetInScenarioBuilder() +{ + return ! isnan(finalLevel_); +} + +bool FinalLevelValidator::compatibleWithReservoirProperties() +{ + if (hydro_.reservoirManagement && !hydro_.useWaterValue) + return true; + + logs.warning() << "Final reservoir level not applicable! Year:" << year_ + 1 + << ", Area:" << areaName_ + << ". Check: Reservoir management = Yes, Use water values = No and proper initial " + "reservoir level is provided "; + return false; +} + +bool FinalLevelValidator::checkForInfeasibility() +{ + bool checksOk = hydroAllocationStartMatchesSimulation(); + checksOk = isFinalLevelReachable() && checksOk; + checksOk = isBetweenRuleCurves() && checksOk; + + return checksOk; +} + +bool FinalLevelValidator::hydroAllocationStartMatchesSimulation() const +{ + int initReservoirLvlMonth = hydro_.initializeReservoirLevelDate; // month [0-11] + if (lastSimulationDay_ == DAYS_PER_YEAR && initReservoirLvlMonth == firstMonthOfSimulation_) + return true; + + logs.error() << "Year " << year_ + 1 << ", area '" << areaName_ << "' : " + << "Hydro allocation must start on the 1st simulation month and " + << "simulation last a whole year"; + return false; +} + +bool FinalLevelValidator::isFinalLevelReachable() const +{ + double reservoirCapacity = hydro_.reservoirCapacity; + double totalYearInflows = calculateTotalInflows(); + + if ((finalLevel_ - initialLevel_) * reservoirCapacity > totalYearInflows) + { + logs.error() << "Year: " << year_ + 1 << ". Area: " << areaName_ + << ". Incompatible total inflows: " << totalYearInflows + << " with initial: " << initialLevel_ + << " and final: " << finalLevel_ << " reservoir levels."; + return false; + } + return true; +} + +double FinalLevelValidator::calculateTotalInflows() const +{ + // calculate yearly inflows + auto const& srcinflows = hydro_.series->storage.getColumn(year_); + + double totalYearInflows = 0.0; + for (unsigned int day = 0; day < DAYS_PER_YEAR; ++day) + totalYearInflows += srcinflows[day]; + return totalYearInflows; +} + +bool FinalLevelValidator::isBetweenRuleCurves() const +{ + double lowLevelLastDay = hydro_.reservoirLevel[Data::PartHydro::minimum][DAYS_PER_YEAR - 1]; + double highLevelLastDay = hydro_.reservoirLevel[Data::PartHydro::maximum][DAYS_PER_YEAR - 1]; + + if (finalLevel_ < lowLevelLastDay || finalLevel_ > highLevelLastDay) + { + logs.error() << "Year: " << year_ + 1 << ". Area: " << areaName_ + << ". Specifed final reservoir level: " << finalLevel_ + << " is incompatible with reservoir level rule curve [" << lowLevelLastDay + << " , " << highLevelLastDay << "]"; + return false; + } + return true; +} + +bool FinalLevelValidator::finalLevelFineForUse() +{ + return finalLevelFineForUse_; +} + +} // namespace Antares::Data diff --git a/src/libs/antares/study/scenario-builder/hydroLevelsData.cpp b/src/libs/antares/study/scenario-builder/hydroLevelsData.cpp index 8abb019366..3295df4503 100644 --- a/src/libs/antares/study/scenario-builder/hydroLevelsData.cpp +++ b/src/libs/antares/study/scenario-builder/hydroLevelsData.cpp @@ -28,6 +28,13 @@ namespace Antares::Data::ScenarioBuilder { +hydroLevelsData::hydroLevelsData(const std::string& iniFilePrefix, + std::function applyToTarget) : + addToPrefix_(iniFilePrefix), + applyToTarget_(applyToTarget) +{ +} + bool hydroLevelsData::reset(const Study& study) { const uint nbYears = study.parameters.nbYears; @@ -40,10 +47,6 @@ bool hydroLevelsData::reset(const Study& study) void hydroLevelsData::saveToINIFile(const Study& study, Yuni::IO::File::Stream& file) const { - // Prefix - CString<512, false> prefix; - prefix += "hl,"; - // Turning values into strings (precision 4) std::ostringstream value_into_string; value_into_string << std::setprecision(4); @@ -65,7 +68,7 @@ void hydroLevelsData::saveToINIFile(const Study& study, Yuni::IO::File::Stream& } assert(index < study.areas.size()); value_into_string << value; - file << prefix << study.areas.byIndex[index]->id << ',' << y << " = " + file << addToPrefix_ << study.areas.byIndex[index]->id << ',' << y << " = " << value_into_string.str() << '\n'; value_into_string.str(std::string()); // Clearing converter } @@ -79,7 +82,7 @@ void hydroLevelsData::set_value(uint x, uint y, double value) bool hydroLevelsData::apply(Study& study) { - study.scenarioHydroLevels.copyFrom(pHydroLevelsRules); + applyToTarget_(study, pHydroLevelsRules); return true; } diff --git a/src/libs/antares/study/scenario-builder/rules.cpp b/src/libs/antares/study/scenario-builder/rules.cpp index fb39a275fd..fd79f07f7c 100644 --- a/src/libs/antares/study/scenario-builder/rules.cpp +++ b/src/libs/antares/study/scenario-builder/rules.cpp @@ -59,7 +59,8 @@ void Rules::saveToINIFile(Yuni::IO::File::Stream& file) const linksNTC[i].saveToINIFile(study_, file); } // hydro levels - hydroLevels.saveToINIFile(study_, file); + hydroInitialLevels.saveToINIFile(study_, file); + hydroFinalLevels.saveToINIFile(study_, file); } binding_constraints.saveToINIFile(study_, file); file << '\n'; @@ -95,7 +96,8 @@ bool Rules::reset() renewable[i].reset(study_); } - hydroLevels.reset(study_); + hydroInitialLevels.reset(study_); + hydroFinalLevels.reset(study_); // links NTC linksNTC.clear(); @@ -263,7 +265,7 @@ bool Rules::readSolar(const AreaName::Vector& splitKey, String value, bool updat return true; } -bool Rules::readHydroLevels(const AreaName::Vector& splitKey, String value, bool updaterMode) +bool Rules::readInitialHydroLevels(const AreaName::Vector& splitKey, String value, bool updaterMode) { const uint year = splitKey[2].to(); const AreaName& areaname = splitKey[1]; @@ -275,7 +277,21 @@ bool Rules::readHydroLevels(const AreaName::Vector& splitKey, String value, bool } double val = fromStringToHydroLevel(value, 1.); - hydroLevels.setTSnumber(area->index, year, val); + hydroInitialLevels.setTSnumber(area->index, year, val); + return true; +} + +bool Rules::readFinalHydroLevels(const AreaName::Vector& splitKey, String value, bool updaterMode) +{ + const uint year = splitKey[2].to(); + const AreaName& areaname = splitKey[1]; + + const Data::Area* area = getArea(areaname, updaterMode); + if (!area) + return false; + + double finalLevel = fromStringToHydroLevel(value, 1.); + hydroFinalLevels.setTSnumber(area->index, year, finalLevel); return true; } @@ -389,7 +405,11 @@ bool Rules::readLine(const AreaName::Vector& splitKey, String value, bool update } else if (kind_of_scenario == "hl") { - return readHydroLevels(splitKey, value, updaterMode); + return readInitialHydroLevels(splitKey, value, updaterMode); + } + else if (kind_of_scenario == "hfl") + { + return readFinalHydroLevels(splitKey, value, updaterMode); } else if (kind_of_scenario == "ntc") { @@ -402,63 +422,64 @@ bool Rules::readLine(const AreaName::Vector& splitKey, String value, bool update return false; } - bool Rules::apply() +bool Rules::apply() +{ + bool returned_status = true; + if (pAreaCount) { - bool returned_status = true; - if (pAreaCount) + returned_status = load.apply(study_) && returned_status; + returned_status = solar.apply(study_) && returned_status; + returned_status = hydro.apply(study_) && returned_status; + returned_status = wind.apply(study_) && returned_status; + for (uint i = 0; i != pAreaCount; ++i) { - returned_status = load.apply(study_) && returned_status; - returned_status = solar.apply(study_) && returned_status; - returned_status = hydro.apply(study_) && returned_status; - returned_status = wind.apply(study_) && returned_status; - for (uint i = 0; i != pAreaCount; ++i) - { - returned_status = thermal[i].apply(study_) && returned_status; - returned_status = renewable[i].apply(study_) && returned_status; - returned_status = linksNTC[i].apply(study_) && returned_status; - } - returned_status = hydroLevels.apply(study_) && returned_status; - returned_status = binding_constraints.apply(study_) && returned_status; + returned_status = thermal[i].apply(study_) && returned_status; + returned_status = renewable[i].apply(study_) && returned_status; + returned_status = linksNTC[i].apply(study_) && returned_status; } - else + returned_status = hydroInitialLevels.apply(study_) && returned_status; + returned_status = hydroFinalLevels.apply(study_) && returned_status; + returned_status = binding_constraints.apply(study_) && returned_status; + } + else + { + returned_status = false; + } + return returned_status; +} + +void Rules::sendWarningsForDisabledClusters() +{ + for (auto it = disabledClustersOnRuleActive.begin(); + it != disabledClustersOnRuleActive.end(); + it++) + { + std::vector& scenariiForCurrentCluster = it->second; + int nbScenariiForCluster = (int)scenariiForCurrentCluster.size(); + std::vector::iterator itv = scenariiForCurrentCluster.begin(); + + // Listing the 10 first years for which the current cluster was given a specific TS + // number in the scenario builder. Note that this list of years size could be less then + // 10, but are at least 1. + std::string listYears = std::to_string(*itv); + itv++; + for (int year_count = 1; itv != scenariiForCurrentCluster.end() && year_count < 10; + itv++, year_count++) { - returned_status = false; + listYears += ", " + std::to_string(*itv); } - return returned_status; - } - void Rules::sendWarningsForDisabledClusters() - { - for (auto it = disabledClustersOnRuleActive.begin(); - it != disabledClustersOnRuleActive.end(); - it++) + // Adding last scenario to the list + if (nbScenariiForCluster > 10) { - std::vector& scenariiForCurrentCluster = it->second; - int nbScenariiForCluster = (int)scenariiForCurrentCluster.size(); - std::vector::iterator itv = scenariiForCurrentCluster.begin(); - - // Listing the 10 first years for which the current cluster was given a specific TS - // number in the scenario builder. Note that this list of years size could be less then - // 10, but are at least 1. - std::string listYears = std::to_string(*itv); - itv++; - for (int year_count = 1; itv != scenariiForCurrentCluster.end() && year_count < 10; - itv++, year_count++) - { - listYears += ", " + std::to_string(*itv); - } - - // Adding last scenario to the list - if (nbScenariiForCluster > 10) - { - listYears += ", ..., " + std::to_string(scenariiForCurrentCluster.back()); - } - - logs.warning() << "Cluster " << it->first - << " not found: it may be disabled, though given TS numbers in sc " - "builder for year(s) :"; - logs.warning() << listYears; + listYears += ", ..., " + std::to_string(scenariiForCurrentCluster.back()); } + + logs.warning() << "Cluster " << it->first + << " not found: it may be disabled, though given TS numbers in sc " + "builder for year(s) :"; + logs.warning() << listYears; } +} } // namespace Antares::Data::ScenarioBuilder 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 3050515ca5..b642e441c2 100644 --- a/src/solver/hydro/include/antares/solver/hydro/management/management.h +++ b/src/solver/hydro/include/antares/solver/hydro/management/management.h @@ -122,6 +122,8 @@ class HydroManagement final private: //! Prepare inflows scaling for each area void prepareInflowsScaling(uint year); + //! prepare data for Final reservoir level + void changeInflowsToAccommodateFinalLevels(uint yearIndex); //! Prepare minimum generation scaling for each area void minGenerationScaling(uint year); //! check Monthly minimum generation is lower than available inflows diff --git a/src/solver/hydro/management/daily.cpp b/src/solver/hydro/management/daily.cpp index 5c6bcab897..f7e4665159 100644 --- a/src/solver/hydro/management/daily.cpp +++ b/src/solver/hydro/management/daily.cpp @@ -362,6 +362,7 @@ inline void HydroManagement::prepareDailyOptimalGenerations( if (debugData) { + dayYear = 0; for (uint month = 0; month != 12; ++month) { auto daysPerMonth = calendar_.months[month].days; @@ -371,6 +372,7 @@ inline void HydroManagement::prepareDailyOptimalGenerations( auto dYear = day + dayYear; debugData->DailyTargetGen[dYear] = dailyTargetGen[dYear]; } + dayYear += daysPerMonth; } } diff --git a/src/solver/hydro/management/management.cpp b/src/solver/hydro/management/management.cpp index 6cea703442..703235fc61 100644 --- a/src/solver/hydro/management/management.cpp +++ b/src/solver/hydro/management/management.cpp @@ -383,8 +383,25 @@ bool HydroManagement::checkMinGeneration(uint year) const return ret; } -void HydroManagement::prepareNetDemand(uint year, - Data::SimulationMode mode, +void HydroManagement::changeInflowsToAccommodateFinalLevels(uint year) +{ + areas_.each([this, &year](Data::Area& area) + { + auto& data = tmpDataByArea_[&area]; + + if (!area.hydro.deltaBetweenFinalAndInitialLevels[year].has_value()) + return; + + // Must be done before prepareMonthlyTargetGenerations + double delta = area.hydro.deltaBetweenFinalAndInitialLevels[year].value(); + if (delta < 0) + data.inflows[0] -= delta; + else if (delta > 0) + data.inflows[11] -= delta; + }); +} + +void HydroManagement::prepareNetDemand(uint year, Data::SimulationMode mode, const Antares::Data::Area::ScratchMap& scratchmap) { areas_.each( @@ -527,6 +544,7 @@ void HydroManagement::makeVentilation(double* randomReservoirLevel, throw FatalError("hydro management: invalid minimum generation"); } + changeInflowsToAccommodateFinalLevels(y); prepareNetDemand(y, parameters_.mode, scratchmap); prepareEffectiveDemand(); diff --git a/src/solver/hydro/management/monthly.cpp b/src/solver/hydro/management/monthly.cpp index 62ce897eb8..aa10b7c10e 100644 --- a/src/solver/hydro/management/monthly.cpp +++ b/src/solver/hydro/management/monthly.cpp @@ -166,8 +166,6 @@ void HydroManagement::prepareMonthlyOptimalGenerations(double* random_reservoir_ lvi = random_reservoir_level[indexArea]; } - indexArea++; - double solutionCost = 0.; double solutionCostNoised = 0.; @@ -292,20 +290,22 @@ void HydroManagement::prepareMonthlyOptimalGenerations(double* random_reservoir_ auto monthName = calendar_.text.months[simulationMonth].name; - buffer << monthName[0] << monthName[1] << monthName[2] << '\t'; - buffer << '\t'; - buffer << data.inflows[realmonth] << '\t'; - buffer << data.MTG[realmonth] << '\t'; - buffer << data.MOG[realmonth] / area.hydro.reservoirCapacity << '\t'; - buffer << data.MOL[realmonth] << '\t'; - buffer << minLvl[firstDay] << '\t'; - buffer << maxLvl[firstDay] << '\t'; - buffer << '\n'; - } - auto content = buffer.str(); - resultWriter_.addEntryFromBuffer(path.str(), content); - } - }); + buffer << monthName[0] << monthName[1] << monthName[2] << '\t'; + buffer << '\t'; + buffer << data.inflows[realmonth] << '\t'; + buffer << data.MTG[realmonth] << '\t'; + buffer << data.MOG[realmonth] / area.hydro.reservoirCapacity << '\t'; + buffer << data.MOL[realmonth] << '\t'; + buffer << minLvl[firstDay] << '\t'; + buffer << maxLvl[firstDay] << '\t'; + buffer << '\n'; + } + auto content = buffer.str(); + resultWriter_.addEntryFromBuffer(path.str(), content); + } + + indexArea++; + }); } } // namespace Antares diff --git a/src/solver/simulation/CMakeLists.txt b/src/solver/simulation/CMakeLists.txt index 3c59c59d55..0d2bc666eb 100644 --- a/src/solver/simulation/CMakeLists.txt +++ b/src/solver/simulation/CMakeLists.txt @@ -22,35 +22,36 @@ set(SRC_SIMULATION apply-scenario.cpp - # Solver - include/antares/solver/simulation/solver_utils.h - solver_utils.cpp - include/antares/solver/simulation/solver.h - include/antares/solver/simulation/solver.hxx - include/antares/solver/simulation/solver.data.h - solver.data.cpp - include/antares/solver/simulation/common-eco-adq.h - common-eco-adq.cpp - common-hydro-remix.cpp - common-hydro-levels.cpp - include/antares/solver/simulation/adequacy.h - adequacy.cpp - include/antares/solver/simulation/economy.h - economy.cpp - include/antares/solver/simulation/base_post_process.h - base_post_process.cpp - include/antares/solver/simulation/opt_time_writer.h - opt_time_writer.cpp - include/antares/solver/simulation/adequacy_patch_runtime_data.h - adequacy_patch_runtime_data.cpp - include/antares/solver/simulation/ITimeSeriesNumbersWriter.h - TimeSeriesNumbersWriter.cpp - include/antares/solver/simulation/BindingConstraintsTimeSeriesNumbersWriter.h - - economy_mode.cpp - adequacy_mode.cpp - include/antares/solver/simulation/economy_mode.h - include/antares/solver/simulation/adequacy_mode.h + # Solver + include/antares/solver/simulation/solver_utils.h + solver_utils.cpp + include/antares/solver/simulation/solver.h + include/antares/solver/simulation/solver.hxx + include/antares/solver/simulation/solver.data.h + solver.data.cpp + include/antares/solver/simulation/common-eco-adq.h + common-eco-adq.cpp + common-hydro-remix.cpp + common-hydro-levels.cpp + include/antares/solver/simulation/adequacy.h + adequacy.cpp + include/antares/solver/simulation/economy.h + economy.cpp + include/antares/solver/simulation/base_post_process.h + base_post_process.cpp + include/antares/solver/simulation/opt_time_writer.h + opt_time_writer.cpp + include/antares/solver/simulation/adequacy_patch_runtime_data.h + adequacy_patch_runtime_data.cpp + include/antares/solver/simulation/ITimeSeriesNumbersWriter.h + include/antares/solver/simulation/hydro-final-reservoir-level-functions.h + hydro-final-reservoir-level-functions.cpp + TimeSeriesNumbersWriter.cpp + include/antares/solver/simulation/BindingConstraintsTimeSeriesNumbersWriter.h + economy_mode.cpp + adequacy_mode.cpp + include/antares/solver/simulation/economy_mode.h + include/antares/solver/simulation/adequacy_mode.h ) source_group("simulation" FILES ${SRC_SIMULATION}) diff --git a/src/solver/simulation/hydro-final-reservoir-level-functions.cpp b/src/solver/simulation/hydro-final-reservoir-level-functions.cpp new file mode 100644 index 0000000000..32f2d4dfa2 --- /dev/null +++ b/src/solver/simulation/hydro-final-reservoir-level-functions.cpp @@ -0,0 +1,68 @@ +/* +** Copyright 2007-2023 RTE +** Authors: RTE-international / Redstork / 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/solver/simulation/hydro-final-reservoir-level-functions.h" +#include "antares/study/parts/hydro/finalLevelValidator.h" +#include + +namespace Antares::Solver +{ + +void CheckFinalReservoirLevelsConfiguration(const Data::Study& study) +{ + study.areas.each([&study](Data::Area &area) + { + uint nbYears = study.parameters.nbYears; + for (uint year = 0; year != nbYears; ++year) + { + if (! study.parameters.yearsFilter.at(year)) + continue; + + double initialLevel = study.scenarioInitialHydroLevels.entry[area.index][year]; + double finalLevel = study.scenarioFinalHydroLevels.entry[area.index][year]; + + Data::FinalLevelValidator validator(area.hydro, + area.index, + area.name, + initialLevel, + finalLevel, + year, + study.parameters.simulationDays.end, + study.parameters.firstMonthInYear); + if (! validator.check()) + { + throw FatalError("hydro final level : infeasibility"); + } + if (validator.finalLevelFineForUse()) + { + area.hydro.deltaBetweenFinalAndInitialLevels[year] = finalLevel - initialLevel; + } + } + }); +} // End function CheckFinalReservoirLevelsConfiguration + +} // namespace Antares::Solver \ No newline at end of file diff --git a/src/solver/simulation/include/antares/solver/simulation/hydro-final-reservoir-level-functions.h b/src/solver/simulation/include/antares/solver/simulation/hydro-final-reservoir-level-functions.h new file mode 100644 index 0000000000..4b60c4cc8f --- /dev/null +++ b/src/solver/simulation/include/antares/solver/simulation/hydro-final-reservoir-level-functions.h @@ -0,0 +1,37 @@ +/* +** Copyright 2007-2023 RTE +** Authors: RTE-international / Redstork / 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 __SOLVER_SIMULATION_HYDRO_FINAL_RESERVOIR_PRE_CHECKS_H__ +#define __SOLVER_SIMULATION_HYDRO_FINAL_RESERVOIR_PRE_CHECKS_H__ + +#include "antares/study/study.h" + +namespace Antares::Solver +{ +void CheckFinalReservoirLevelsConfiguration(const Data::Study& study); +} // namespace Antares::Solver + +#endif // __SOLVER_SIMULATION_HYDRO_FINAL_RESERVOIR_PRE_CHECKS_H__ diff --git a/src/solver/simulation/include/antares/solver/simulation/solver.hxx b/src/solver/simulation/include/antares/solver/simulation/solver.hxx index 5e36fcaca8..232fa520fc 100644 --- a/src/solver/simulation/include/antares/solver/simulation/solver.hxx +++ b/src/solver/simulation/include/antares/solver/simulation/solver.hxx @@ -39,6 +39,9 @@ #include "antares/solver/simulation/timeseries-numbers.h" #include "antares/solver/ts-generator/generator.h" + +#include "hydro-final-reservoir-level-functions.h" + namespace Antares::Solver::Simulation { @@ -349,6 +352,7 @@ void ISimulation::run() if (study.parameters.useCustomScenario) { ApplyCustomScenario(study); + CheckFinalReservoirLevelsConfiguration(study); } // Launching the simulation for all years @@ -744,15 +748,15 @@ void ISimulation::computeRandomNumbers( max[firstDayOfMonth], randomHydroGenerator); - // Possibly update the intial level from scenario builder - if (study.parameters.useCustomScenario) - { - double levelFromScenarioBuilder = study.scenarioHydroLevels[areaIndex][y]; - if (levelFromScenarioBuilder >= 0.) - { - randomLevel = levelFromScenarioBuilder; - } - } + // Possibly update the intial level from scenario builder + if (study.parameters.useCustomScenario) + { + double levelFromScenarioBuilder = study.scenarioInitialHydroLevels[areaIndex][y]; + if (levelFromScenarioBuilder >= 0.) + { + randomLevel = levelFromScenarioBuilder; + } + } // Current area's hydro starting (or initial) level computation // (no matter if the year is performed or not, we always draw a random initial 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 e5813befd8..bfad50326f 100644 --- a/src/tests/end-to-end/simple_study/simple-study.cpp +++ b/src/tests/end-to-end/simple_study/simple-study.cpp @@ -78,11 +78,12 @@ struct HydroMaxPowerStudy: public StudyBuilder HydroMaxPowerStudy::HydroMaxPowerStudy() { simulationBetweenDays(0, 14); - setNumberMCyears(1); area = addAreaToStudy("Area"); area->thermal.unsuppliedEnergyCost = 1; + setNumberMCyears(1); + TimeSeriesConfigurer loadTSconfig(area->load.series.timeSeries); loadTSconfig.setColumnCount(1).fillColumnWith(0, loadInArea); diff --git a/src/tests/end-to-end/utils/utils.cpp b/src/tests/end-to-end/utils/utils.cpp index 769683412d..a42891ea45 100644 --- a/src/tests/end-to-end/utils/utils.cpp +++ b/src/tests/end-to-end/utils/utils.cpp @@ -219,6 +219,7 @@ void StudyBuilder::setNumberMCyears(unsigned int nbYears) { study->parameters.resetPlaylist(nbYears); study->areas.resizeAllTimeseriesNumbers(nbYears); + study->areas.each([&](Data::Area& area) { area.hydro.deltaBetweenFinalAndInitialLevels.resize(nbYears); }); } void StudyBuilder::playOnlyYear(unsigned int year) 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 36346b2014..7879559fb0 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 @@ -346,7 +346,7 @@ BOOST_FIXTURE_TEST_CASE( } // ======================== -// Tests on Hydro levels +// Tests on Hydro initial levels // ======================== BOOST_FIXTURE_TEST_CASE(on_area1_and_on_year_17__hydro_level_0_123_is_chosen__reading_OK, Fixture) { @@ -355,12 +355,10 @@ BOOST_FIXTURE_TEST_CASE(on_area1_and_on_year_17__hydro_level_0_123_is_chosen__re AreaName::Vector splitKey = {"hl", "area 1", yearNumber}; my_rule.readLine(splitKey, level); - BOOST_CHECK_EQUAL(my_rule.hydroLevels.get_value(yearNumber.to(), area_1->index), - level.to()); + BOOST_CHECK_EQUAL(my_rule.hydroInitialLevels.get_value(yearNumber.to(), area_1->index), level.to()); - BOOST_CHECK(my_rule.apply()); - BOOST_CHECK_EQUAL(study->scenarioHydroLevels[area_1->index][yearNumber.to()], - level.to()); + BOOST_CHECK(my_rule.apply()); + BOOST_CHECK_EQUAL(study->scenarioInitialHydroLevels[area_1->index][yearNumber.to()], level.to()); } BOOST_FIXTURE_TEST_CASE( @@ -372,10 +370,10 @@ BOOST_FIXTURE_TEST_CASE( AreaName::Vector splitKey = {"hl", "area 2", yearNumber}; BOOST_CHECK(my_rule.readLine(splitKey, level)); - BOOST_CHECK_EQUAL(my_rule.hydroLevels.get_value(yearNumber.to(), area_2->index), 1.); + BOOST_CHECK_EQUAL(my_rule.hydroInitialLevels.get_value(yearNumber.to(), area_2->index), 1.); - BOOST_CHECK(my_rule.apply()); - BOOST_CHECK_EQUAL(study->scenarioHydroLevels[area_2->index][yearNumber.to()], 1.); + BOOST_CHECK(my_rule.apply()); + BOOST_CHECK_EQUAL(study->scenarioInitialHydroLevels[area_2->index][yearNumber.to()], 1.); } BOOST_FIXTURE_TEST_CASE( @@ -387,10 +385,58 @@ BOOST_FIXTURE_TEST_CASE( AreaName::Vector splitKey = {"hl", "area 3", yearNumber}; BOOST_CHECK(my_rule.readLine(splitKey, level)); - BOOST_CHECK_EQUAL(my_rule.hydroLevels.get_value(yearNumber.to(), area_3->index), 0.); + BOOST_CHECK_EQUAL(my_rule.hydroInitialLevels.get_value(yearNumber.to(), area_3->index), 0.); - BOOST_CHECK(my_rule.apply()); - BOOST_CHECK_EQUAL(study->scenarioHydroLevels[area_3->index][yearNumber.to()], 0.); + BOOST_CHECK(my_rule.apply()); + BOOST_CHECK_EQUAL(study->scenarioInitialHydroLevels[area_3->index][yearNumber.to()], 0.); +} + +// ======================== +// Tests on Hydro final levels +// ======================== +BOOST_FIXTURE_TEST_CASE(on_area1_and_on_year_8__hydro_level_0_342_is_chosen__reading_OK, Fixture) +{ + AreaName yearNumber = "8"; + String level = "0.342"; + AreaName::Vector splitKey = {"hfl", "area 1", yearNumber}; + my_rule.readLine(splitKey, level, false); + + BOOST_CHECK_EQUAL(my_rule.hydroFinalLevels.get_value(yearNumber.to(), area_1->index), + level.to()); + + BOOST_CHECK(my_rule.apply()); + BOOST_CHECK_EQUAL(study->scenarioFinalHydroLevels[area_1->index][yearNumber.to()], + level.to()); +} + +BOOST_FIXTURE_TEST_CASE(on_area2_and_on_year_1__hydro_level_2_4_is_chosen_level_lowered_to_1__reading_OK, Fixture) +{ + AreaName yearNumber = "1"; + String level = "2.4"; + AreaName::Vector splitKey = {"hfl", "area 2", yearNumber}; + BOOST_CHECK(my_rule.readLine(splitKey, level, false)); + + BOOST_CHECK_EQUAL(my_rule.hydroFinalLevels.get_value(yearNumber.to(), area_2->index), + 1.); + + BOOST_CHECK(my_rule.apply()); + BOOST_CHECK_EQUAL(study->scenarioFinalHydroLevels[area_2->index][yearNumber.to()], + 1.); +} + +BOOST_FIXTURE_TEST_CASE(on_area3_and_on_year_3__hydro_level_neg_5_2_is_chosen__level_raised_to_0__reading_OK, Fixture) +{ + AreaName yearNumber = "3"; + String level = "-5.2"; + AreaName::Vector splitKey = {"hfl", "area 3", yearNumber}; + BOOST_CHECK(my_rule.readLine(splitKey, level, false)); + + BOOST_CHECK_EQUAL(my_rule.hydroFinalLevels.get_value(yearNumber.to(), area_3->index), + 0.); + + BOOST_CHECK(my_rule.apply()); + BOOST_CHECK_EQUAL(study->scenarioFinalHydroLevels[area_3->index][yearNumber.to()], + 0.); } // ====================== 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 e1b0666b7d..13d5a38b48 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 @@ -411,15 +411,15 @@ BOOST_FIXTURE_TEST_CASE( } // ======================== -// Tests on Hydro levels +// Tests on Hydro initial levels // ======================== BOOST_FIXTURE_TEST_CASE( HYDRO_LEVEL__TS_number_for_many_areas_and_years__generated_and_ref_sc_buider_files_are_identical, saveFixture) { - my_rule->hydroLevels.setTSnumber(area_1->index, 9, 9); - my_rule->hydroLevels.setTSnumber(area_3->index, 18, 7); - my_rule->hydroLevels.setTSnumber(area_1->index, 5, 8); + my_rule->hydroInitialLevels.setTSnumber(area_1->index, 9, 9); + my_rule->hydroInitialLevels.setTSnumber(area_3->index, 18, 7); + my_rule->hydroInitialLevels.setTSnumber(area_1->index, 5, 8); saveScenarioBuilder(); @@ -433,6 +433,27 @@ BOOST_FIXTURE_TEST_CASE( BOOST_CHECK(files_identical(path_to_generated_file, referenceFile.path())); } +// ======================== +// Tests on Hydro final levels +// ======================== +BOOST_FIXTURE_TEST_CASE(HYDRO_FINAL_LEVEL__TS_number_for_many_areas_and_years__generated_and_ref_sc_buider_files_are_identical, saveFixture) +{ + my_rule->hydroFinalLevels.setTSnumber(area_1->index, 4, 8); + my_rule->hydroFinalLevels.setTSnumber(area_2->index, 11, 3); + my_rule->hydroFinalLevels.setTSnumber(area_3->index, 15, 2); + + saveScenarioBuilder(); + + // Build reference scenario builder file + referenceFile.append("[my rule name]"); + referenceFile.append("hfl,area 1,4 = 8"); + referenceFile.append("hfl,area 2,11 = 3"); + referenceFile.append("hfl,area 3,15 = 2"); + referenceFile.write(); + + BOOST_CHECK(files_identical(path_to_generated_file, referenceFile.path())); +} + // ====================== // Tests on Links NTC // ====================== @@ -496,7 +517,7 @@ BOOST_FIXTURE_TEST_CASE( my_rule->renewable[area_3->index].setTSnumber(rnCluster_32.get(), 5, 13); my_rule->linksNTC[area_1->index].setDataForLink(link_13, 19, 8); my_rule->linksNTC[area_2->index].setDataForLink(link_23, 2, 4); - my_rule->hydroLevels.setTSnumber(area_1->index, 5, 8); + my_rule->hydroInitialLevels.setTSnumber(area_1->index, 5, 8); my_rule->binding_constraints.setTSnumber("group3", 10, 6); saveScenarioBuilder(); diff --git a/src/tests/src/solver/simulation/CMakeLists.txt b/src/tests/src/solver/simulation/CMakeLists.txt index e056ac388d..3ba8f09755 100644 --- a/src/tests/src/solver/simulation/CMakeLists.txt +++ b/src/tests/src/solver/simulation/CMakeLists.txt @@ -89,3 +89,32 @@ add_test(NAME time_series COMMAND test-time_series) set_property(TEST time_series PROPERTY LABELS unit) +# =================================== +# Tests on hydro final reservoir level functions +# =================================== + +add_executable(test-hydro_final test-hydro-final-reservoir-level-functions.cpp) + +target_include_directories(test-hydro_final + PRIVATE + "${src_solver_simulation}" + "${src_libs_antares_study}" +) +target_link_libraries(test-hydro_final + PRIVATE + Boost::unit_test_framework + Antares::study + antares-solver-simulation + Antares::array +) + +# Linux +if(UNIX AND NOT APPLE) + target_link_libraries(test-hydro_final PRIVATE stdc++fs) +endif() + +set_target_properties(test-hydro_final PROPERTIES FOLDER Unit-tests) + +add_test(NAME hydro_final COMMAND test-hydro_final) + +set_property(TEST hydro_final PROPERTY LABELS unit) \ No newline at end of file diff --git a/src/tests/src/solver/simulation/test-hydro-final-reservoir-level-functions.cpp b/src/tests/src/solver/simulation/test-hydro-final-reservoir-level-functions.cpp new file mode 100644 index 0000000000..d4a6c3512b --- /dev/null +++ b/src/tests/src/solver/simulation/test-hydro-final-reservoir-level-functions.cpp @@ -0,0 +1,275 @@ +// +// Created by Nikola Ilic on 23/06/23. +// + +#define BOOST_TEST_MODULE hydro - final - level +#define WIN32_LEAN_AND_MEAN +#include + +#include "include/antares/solver/simulation/hydro-final-reservoir-level-functions.h" +#include "include/antares/study/parts/hydro/finalLevelValidator.h" +#include + +using namespace Antares::Solver; +using namespace Antares::Data; + + +struct Fixture +{ + Fixture(const Fixture& f) = delete; + Fixture(const Fixture&& f) = delete; + Fixture& operator=(const Fixture& f) = delete; + Fixture& operator=(const Fixture&& f) = delete; + Fixture() + { + // Simulation last day must be 365 so that final level checks succeeds + study->parameters.simulationDays.end = 365; + study->parameters.firstMonthInYear = january; + uint nbYears = study->parameters.nbYears = 2; + + area_1 = study->areaAdd("Area1"); + area_2 = study->areaAdd("Area2"); + + area_1->hydro.reservoirManagement = true; + area_2->hydro.reservoirManagement = true; + + area_1->hydro.useWaterValue = false; + area_2->hydro.useWaterValue = false; + + // Level date must be 0, see hydroAllocationStartMatchesSimulation function + area_1->hydro.initializeReservoirLevelDate = 0; + area_2->hydro.initializeReservoirLevelDate = 0; + + area_1->hydro.reservoirCapacity = 340.; + area_2->hydro.reservoirCapacity = 300.; + + // Set reservoir max and min daily levels, but just for the last day in year + area_1->hydro.reservoirLevel.resize(3, DAYS_PER_YEAR); + area_1->hydro.reservoirLevel[PartHydro::minimum][DAYS_PER_YEAR - 1] = 2.4; + area_1->hydro.reservoirLevel[PartHydro::maximum][DAYS_PER_YEAR - 1] = 6.5; + + area_2->hydro.reservoirLevel.resize(3, DAYS_PER_YEAR); + area_2->hydro.reservoirLevel[PartHydro::minimum][DAYS_PER_YEAR - 1] = 2.7; + area_2->hydro.reservoirLevel[PartHydro::maximum][DAYS_PER_YEAR - 1] = 6.4; + + // Resize vector final levels delta with initial levels + area_1->hydro.deltaBetweenFinalAndInitialLevels.resize(nbYears); + area_2->hydro.deltaBetweenFinalAndInitialLevels.resize(nbYears); + + + // Scenario builder for initial and final reservoir levels + // ------------------------------------------------------- + uint areasCount = study->areas.size(); + + study->parameters.yearsFilter.assign(2, true); + + study->scenarioInitialHydroLevels.resize(nbYears, areasCount); + study->scenarioFinalHydroLevels.resize(nbYears, areasCount); + + study->scenarioInitialHydroLevels[0][0] = 2.3; + study->scenarioInitialHydroLevels[0][1] = 4.2; + study->scenarioInitialHydroLevels[1][0] = 1.5; + study->scenarioInitialHydroLevels[1][1] = 2.4; + + study->scenarioFinalHydroLevels[0][0] = 3.4; + study->scenarioFinalHydroLevels[0][1] = 5.1; + study->scenarioFinalHydroLevels[1][0] = 3.5; + study->scenarioFinalHydroLevels[1][1] = 4.3; + + // Inflows time series matrices + // ----------------------------- + uint nbInflowTS = 2; + // ... Area 1 : Inflows time series numbers for each year + area_1->hydro.series->timeseriesNumbers.reset(nbYears); + area_1->hydro.series->timeseriesNumbers[0] = 0; + area_1->hydro.series->timeseriesNumbers[1] = 1; + // ... Area 1 : Inflows time series + area_1->hydro.series->storage.resize(nbInflowTS, 365); + area_1->hydro.series->storage.timeSeries.fill(200.); + area_1->hydro.series->storage[0][0] = 200. + 1.; + area_1->hydro.series->storage[0][DAYS_PER_YEAR - 1] = 200. + 2.; + + // ... Area 2 : time series numbers for each year + area_2->hydro.series->timeseriesNumbers.reset(nbYears); + area_2->hydro.series->timeseriesNumbers[0] = 0; + area_2->hydro.series->timeseriesNumbers[1] = 1; + // ... Area 2 : Inflows time series + area_2->hydro.series->storage.resize(nbInflowTS, 365); + area_2->hydro.series->storage.timeSeries.fill(300.); + area_2->hydro.series->storage[0][0] = 300. + 1.; //DAYS_PER_YEAR + area_2->hydro.series->storage[0][DAYS_PER_YEAR - 1] = 300. + 2.; + } + + ~Fixture() = default; + + Study::Ptr study = std::make_shared(); + Area* area_1; + Area* area_2; +}; + +BOOST_FIXTURE_TEST_SUITE(final_level_validator, Fixture) + +BOOST_AUTO_TEST_CASE(all_parameters_good___check_succeeds_and_final_level_is_usable) +{ + uint year = 0; + FinalLevelValidator validator(area_1->hydro, + area_1->index, + area_1->name, + study->scenarioInitialHydroLevels[area_1->index][year], + study->scenarioFinalHydroLevels[area_1->index][year], + year, + study->parameters.simulationDays.end, + study->parameters.firstMonthInYear); + + BOOST_CHECK_EQUAL(validator.check(), true); + BOOST_CHECK_EQUAL(validator.finalLevelFineForUse(), true); +} + +BOOST_AUTO_TEST_CASE(no_reservoir_management___check_succeeds_but_final_level_not_usable) +{ + uint year = 0; + area_1->hydro.reservoirManagement = false; + FinalLevelValidator validator(area_1->hydro, + area_1->index, + area_1->name, + study->scenarioInitialHydroLevels[area_1->index][year], + study->scenarioFinalHydroLevels[area_1->index][year], + year, + study->parameters.simulationDays.end, + study->parameters.firstMonthInYear); + + BOOST_CHECK_EQUAL(validator.check(), true); + BOOST_CHECK_EQUAL(validator.finalLevelFineForUse(), false); +} + +BOOST_AUTO_TEST_CASE(use_water_value_is_true___check_succeeds_but_final_level_not_usable) +{ + area_1->hydro.useWaterValue = true; + uint year = 0; + + FinalLevelValidator validator(area_1->hydro, + area_1->index, + area_1->name, + study->scenarioInitialHydroLevels[area_1->index][year], + study->scenarioFinalHydroLevels[area_1->index][year], + year, + study->parameters.simulationDays.end, + study->parameters.firstMonthInYear); + + BOOST_CHECK_EQUAL(validator.check(), true); + BOOST_CHECK_EQUAL(validator.finalLevelFineForUse(), false); +} + +BOOST_AUTO_TEST_CASE(final_level_not_set_by_user____check_succeeds_but_final_level_not_usable) +{ + uint year = 0; + study->scenarioFinalHydroLevels[area_1->index][year] = std::numeric_limits::quiet_NaN(); + + FinalLevelValidator validator(area_1->hydro, + area_1->index, + area_1->name, + study->scenarioInitialHydroLevels[area_1->index][year], + study->scenarioFinalHydroLevels[area_1->index][year], + year, + study->parameters.simulationDays.end, + study->parameters.firstMonthInYear); + + BOOST_CHECK_EQUAL(validator.check(), true); + BOOST_CHECK_EQUAL(validator.finalLevelFineForUse(), false); +} + +BOOST_AUTO_TEST_CASE(initial_level_month_and_simulation_first_month_different___check_fails_and_final_level_not_usable) +{ + uint year = 0; + area_1->hydro.initializeReservoirLevelDate = 3; // initialize reservoir level != January + + FinalLevelValidator validator(area_1->hydro, + area_1->index, + area_1->name, + study->scenarioInitialHydroLevels[area_1->index][year], + study->scenarioFinalHydroLevels[area_1->index][year], + year, + study->parameters.simulationDays.end, + study->parameters.firstMonthInYear); + + BOOST_CHECK_EQUAL(validator.check(), false); + BOOST_CHECK_EQUAL(validator.finalLevelFineForUse(), false); +} + +BOOST_AUTO_TEST_CASE(simulation_does_last_a_whole_year___check_fails_and_final_level_not_usable) +{ + uint year = 0; + study->parameters.simulationDays.end = 300; + + FinalLevelValidator validator(area_1->hydro, + area_1->index, + area_1->name, + study->scenarioInitialHydroLevels[area_1->index][year], + study->scenarioFinalHydroLevels[area_1->index][year], + year, + study->parameters.simulationDays.end, + study->parameters.firstMonthInYear); + + BOOST_CHECK_EQUAL(validator.check(), false); + BOOST_CHECK_EQUAL(validator.finalLevelFineForUse(), false); +} + +BOOST_AUTO_TEST_CASE(final_level_out_of_rule_curves___check_fails_and_final_level_not_usable) +{ + uint year = 0; + // Rule Curves on last simulation day = [2.4 - 6.5] + study->scenarioFinalHydroLevels[area_1->index][year] = 6.6; + + FinalLevelValidator validator(area_1->hydro, + area_1->index, + area_1->name, + study->scenarioInitialHydroLevels[area_1->index][year], + study->scenarioFinalHydroLevels[area_1->index][year], + year, + study->parameters.simulationDays.end, + study->parameters.firstMonthInYear); + + BOOST_CHECK_EQUAL(validator.check(), false); + BOOST_CHECK_EQUAL(validator.finalLevelFineForUse(), false); +} + +BOOST_AUTO_TEST_CASE(final_level_unreachable_because_of_too_few_inflows___check_fails_and_final_level_not_usable) +{ + area_1->hydro.reservoirCapacity = 185000; + uint year = 0; + study->scenarioInitialHydroLevels[area_1->index][year] = 10; + study->scenarioFinalHydroLevels[area_1->index][year] = 50; + + // Inflows = 200 MWh/day = 73 000 MWh/year + // (50 - 10) x Reservoir capacity == 74 000 > 73 000. + FinalLevelValidator validator(area_1->hydro, + area_1->index, + area_1->name, + study->scenarioInitialHydroLevels[area_1->index][year], + study->scenarioFinalHydroLevels[area_1->index][year], + year, + study->parameters.simulationDays.end, + study->parameters.firstMonthInYear); + + BOOST_CHECK_EQUAL(validator.check(), false); + BOOST_CHECK_EQUAL(validator.finalLevelFineForUse(), false); +} + +BOOST_AUTO_TEST_CASE(check_all_areas_final_levels_when_config_is_ok___all_checks_succeed) +{ + CheckFinalReservoirLevelsConfiguration(*study); + + // Checks on Area 1 modifier + BOOST_CHECK_EQUAL(area_1->hydro.deltaBetweenFinalAndInitialLevels[0].has_value(), true); + BOOST_CHECK_EQUAL(area_1->hydro.deltaBetweenFinalAndInitialLevels[1].has_value(), true); + BOOST_CHECK_EQUAL(area_1->hydro.deltaBetweenFinalAndInitialLevels[0].value(), 3.4 - 2.3); + BOOST_CHECK_EQUAL(area_1->hydro.deltaBetweenFinalAndInitialLevels[1].value(), 5.1 - 4.2); + + // Checks on Area 2 modifier + BOOST_CHECK_EQUAL(area_2->hydro.deltaBetweenFinalAndInitialLevels[0].has_value(), true); + BOOST_CHECK_EQUAL(area_2->hydro.deltaBetweenFinalAndInitialLevels[1].has_value(), true); + BOOST_CHECK_EQUAL(area_2->hydro.deltaBetweenFinalAndInitialLevels[0].value(), 3.5 - 1.5); + BOOST_CHECK_EQUAL(area_2->hydro.deltaBetweenFinalAndInitialLevels[1].value(), 4.3 - 2.4); +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/src/ui/simulator/application/main/build/scenario-builder.cpp b/src/ui/simulator/application/main/build/scenario-builder.cpp index 5d46aaf6c0..350c1633f7 100644 --- a/src/ui/simulator/application/main/build/scenario-builder.cpp +++ b/src/ui/simulator/application/main/build/scenario-builder.cpp @@ -35,6 +35,7 @@ #include "toolbox/components/datagrid/renderer/scenario-builder-wind-renderer.h" #include "toolbox/components/datagrid/renderer/scenario-builder-solar-renderer.h" #include "toolbox/components/datagrid/renderer/scenario-builder-hydro-levels-renderer.h" +#include "toolbox/components/datagrid/renderer/scenario-builder-hydro-final-levels-renderer.h" #include "toolbox/components/datagrid/renderer/scenario-builder-ntc-renderer.h" using namespace Yuni; @@ -199,8 +200,8 @@ class solarScBuilderPageMaker final : public simpleScBuilderPageMaker } }; -// Hydro levels ... -class hydroLevelsScBuilderPageMaker final : public simpleScBuilderPageMaker +// Hydro Initial levels ... +class hydroInitialLevelsScBuilderPageMaker final : public simpleScBuilderPageMaker { using simpleScBuilderPageMaker::simpleScBuilderPageMaker; @@ -210,7 +211,22 @@ class hydroLevelsScBuilderPageMaker final : public simpleScBuilderPageMaker } Notebook::Page* addPageToNotebook() override { - return notebook()->add(grid(), wxT("hydro levels"), wxT("Hydro Levels")); + return notebook()->add(grid(), wxT("hydro initial levels"), wxT("Hydro Initial Levels")); + } +}; + +// Hydro Final levels ... +class hydroFinalLevelsScBuilderPageMaker final : public simpleScBuilderPageMaker +{ + using simpleScBuilderPageMaker::simpleScBuilderPageMaker; + + Renderer::ScBuilderRendererBase* getRenderer() override + { + return new_check_allocation(); + } + Notebook::Page* addPageToNotebook() override + { + return notebook()->add(grid(), wxT("hydro final levels"), wxT("Hydro Final Levels")); } }; @@ -366,9 +382,13 @@ void ApplWnd::createNBScenarioBuilder() pScenarioBuilderNotebook->addSeparator(); - hydroLevelsScBuilderPageMaker hydroLevelsSBpageMaker(scenarioBuilderPanel, + hydroInitialLevelsScBuilderPageMaker hydroInitialLevelsSBpageMaker(scenarioBuilderPanel, + pScenarioBuilderNotebook); + pageScBuilderHydroInitialLevels = hydroInitialLevelsSBpageMaker.createPage(); + + hydroFinalLevelsScBuilderPageMaker hydroFinalLevelsSBpageMaker(scenarioBuilderPanel, pScenarioBuilderNotebook); - pageScBuilderHydroLevels = hydroLevelsSBpageMaker.createPage(); + pageScBuilderHydroFinalLevels = hydroFinalLevelsSBpageMaker.createPage(); } void ApplWnd::createNBOutputViewer() diff --git a/src/ui/simulator/application/main/main.h b/src/ui/simulator/application/main/main.h index aa16c92f33..0b1f9dd8bb 100644 --- a/src/ui/simulator/application/main/main.h +++ b/src/ui/simulator/application/main/main.h @@ -693,7 +693,8 @@ class ApplWnd final : public Component::Frame::WxLocalFrame, public Yuni::IEvent Component::Notebook::Page* pageScBuilderSolar; Component::Notebook::Page* pageScBuilderNTC; Component::Notebook::Page* pageScBuilderRenewable; - Component::Notebook::Page* pageScBuilderHydroLevels; + Component::Notebook::Page* pageScBuilderHydroInitialLevels; + Component::Notebook::Page* pageScBuilderHydroFinalLevels; //! A context menu for the map wxMenu* pMapContextMenu; diff --git a/src/ui/simulator/cmake/components.cmake b/src/ui/simulator/cmake/components.cmake index ad9dfba7a7..0586928a48 100644 --- a/src/ui/simulator/cmake/components.cmake +++ b/src/ui/simulator/cmake/components.cmake @@ -92,6 +92,8 @@ SET(SRC_TOOLBOX_COM_DBGRID_RENDERERS toolbox/components/datagrid/renderer/scenario-builder-solar-renderer.h toolbox/components/datagrid/renderer/scenario-builder-hydro-levels-renderer.h toolbox/components/datagrid/renderer/scenario-builder-hydro-levels-renderer.cpp + toolbox/components/datagrid/renderer/scenario-builder-hydro-final-levels-renderer.h + toolbox/components/datagrid/renderer/scenario-builder-hydro-final-levels-renderer.cpp toolbox/components/datagrid/renderer/scenario-builder-ntc-renderer.h toolbox/components/datagrid/renderer/scenario-builder-ntc-renderer.cpp toolbox/components/datagrid/renderer/layers.cpp diff --git a/src/ui/simulator/toolbox/components/datagrid/renderer/scenario-builder-hydro-final-levels-renderer.cpp b/src/ui/simulator/toolbox/components/datagrid/renderer/scenario-builder-hydro-final-levels-renderer.cpp new file mode 100644 index 0000000000..809767e8e6 --- /dev/null +++ b/src/ui/simulator/toolbox/components/datagrid/renderer/scenario-builder-hydro-final-levels-renderer.cpp @@ -0,0 +1,82 @@ +/* +** Copyright 2007-2023 RTE +** Authors: RTE-international / Redstork / 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 "scenario-builder-hydro-final-levels-renderer.h" +#include "antares/study/scenario-builder/scBuilderUtils.h" + +using namespace Antares::Data::ScenarioBuilder; + +namespace Antares +{ +namespace Component +{ +namespace Datagrid +{ +namespace Renderer +{ +wxString hydroFinalLevelsScBuilderRenderer::cellValue(int x, int y) const +{ + const double d = cellNumericValue(x, y); + return (std::isnan(d)) ? wxString() << wxT("init") : wxString() << fromHydroLevelToString(d); +} + +bool hydroFinalLevelsScBuilderRenderer::cellValue(int x, int y, const Yuni::String& value) +{ + if (!(!study) && !(!pRules) && (uint)x < study->parameters.nbYears + && (uint)y < study->areas.size()) + { + assert((uint)y < pRules->hydroFinalLevels.width()); + assert((uint)x < pRules->hydroFinalLevels.height()); + double val = fromStringToHydroLevel(value, 100.) / 100.; + pRules->hydroFinalLevels.set_value(x, y, val); + return true; + } + return false; +} + +double hydroFinalLevelsScBuilderRenderer::cellNumericValue(int x, int y) const +{ + if (!(!study) && !(!pRules) && (uint)x < study->parameters.nbYears + && (uint)y < study->areas.size()) + { + assert((uint)y < pRules->hydroFinalLevels.width()); + assert((uint)x < pRules->hydroFinalLevels.height()); + return pRules->hydroFinalLevels.get_value(x, y) * 100.; + } + return 0.; +} + +IRenderer::CellStyle hydroFinalLevelsScBuilderRenderer::cellStyle(int x, int y) const +{ + bool valid = (!(!study) && !(!pRules) && std::isnan(cellNumericValue(x, y))); + return valid ? cellStyleDefaultCenterDisabled : cellStyleDefaultCenter; +} + +} // namespace Renderer +} // namespace Datagrid +} // namespace Component +} // namespace Antares diff --git a/src/ui/simulator/toolbox/components/datagrid/renderer/scenario-builder-hydro-final-levels-renderer.h b/src/ui/simulator/toolbox/components/datagrid/renderer/scenario-builder-hydro-final-levels-renderer.h new file mode 100644 index 0000000000..85a7c87e0f --- /dev/null +++ b/src/ui/simulator/toolbox/components/datagrid/renderer/scenario-builder-hydro-final-levels-renderer.h @@ -0,0 +1,56 @@ +/* +** Copyright 2007-2023 RTE +** Authors: RTE-international / Redstork / 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_TOOLBOX_COMPONENT_DATAGRID_RENDERER_HYDRO_FINAL_LEVELS_SCENARIO_BUILDER_H__ +#define __ANTARES_TOOLBOX_COMPONENT_DATAGRID_RENDERER_HYDRO_FINAL_LEVELS_SCENARIO_BUILDER_H__ + +#include "scenario-builder-renderer-base.h" + +namespace Antares +{ +namespace Component +{ +namespace Datagrid +{ +namespace Renderer +{ +class hydroFinalLevelsScBuilderRenderer : public ScBuilderRendererAreasAsRows +{ +public: + hydroFinalLevelsScBuilderRenderer() = default; + + wxString cellValue(int x, int y) const; + bool cellValue(int x, int y, const Yuni::String& value); + double cellNumericValue(int x, int y) const; + IRenderer::CellStyle cellStyle(int x, int y) const; +}; // class hydroLevelsScBuilderRenderer + +} // namespace Renderer +} // namespace Datagrid +} // namespace Component +} // namespace Antares + +#endif // __ANTARES_TOOLBOX_COMPONENT_DATAGRID_RENDERER_HYDRO_FINAL_LEVELS_SCENARIO_BUILDER_H__ diff --git a/src/ui/simulator/toolbox/components/datagrid/renderer/scenario-builder-hydro-levels-renderer.cpp b/src/ui/simulator/toolbox/components/datagrid/renderer/scenario-builder-hydro-levels-renderer.cpp index ae8e43076f..4dce437a7d 100644 --- a/src/ui/simulator/toolbox/components/datagrid/renderer/scenario-builder-hydro-levels-renderer.cpp +++ b/src/ui/simulator/toolbox/components/datagrid/renderer/scenario-builder-hydro-levels-renderer.cpp @@ -40,30 +40,26 @@ wxString hydroLevelsScBuilderRenderer::cellValue(int x, int y) const bool hydroLevelsScBuilderRenderer::cellValue(int x, int y, const Yuni::String& value) { - if (!(!study) && !(!pRules) && (uint)x < study->parameters.nbYears) + if (!(!study) && !(!pRules) && (uint)x < study->parameters.nbYears + && (uint)y < study->areas.size()) { - if ((uint)y < study->areas.size()) - { - assert((uint)y < pRules->hydroLevels.width()); - assert((uint)x < pRules->hydroLevels.height()); - double val = fromStringToHydroLevel(value, 100.) / 100.; - pRules->hydroLevels.set_value(x, y, val); - return true; - } + assert((uint)y < pRules->hydroInitialLevels.width()); + assert((uint)x < pRules->hydroInitialLevels.height()); + double val = fromStringToHydroLevel(value, 100.) / 100.; + pRules->hydroInitialLevels.set_value(x, y, val); + return true; } return false; } double hydroLevelsScBuilderRenderer::cellNumericValue(int x, int y) const { - if (!(!study) && !(!pRules) && (uint)x < study->parameters.nbYears) + if (!(!study) && !(!pRules) && (uint)x < study->parameters.nbYears + && (uint)y < study->areas.size()) { - if ((uint)y < study->areas.size()) - { - assert((uint)y < pRules->hydroLevels.width()); - assert((uint)x < pRules->hydroLevels.height()); - return pRules->hydroLevels.get_value(x, y) * 100.; - } + assert((uint)y < pRules->hydroInitialLevels.width()); + assert((uint)x < pRules->hydroInitialLevels.height()); + return pRules->hydroInitialLevels.get_value(x, y) * 100.; } return 0.; } @@ -71,7 +67,7 @@ double hydroLevelsScBuilderRenderer::cellNumericValue(int x, int y) const IRenderer::CellStyle hydroLevelsScBuilderRenderer::cellStyle(int x, int y) const { bool valid = (!(!study) && !(!pRules) && std::isnan(cellNumericValue(x, y))); - return (valid) ? cellStyleDefaultCenterDisabled : cellStyleDefaultCenter; + return valid ? cellStyleDefaultCenterDisabled : cellStyleDefaultCenter; } } // namespace Renderer