diff --git a/src/libs/antares/study/area/links.cpp b/src/libs/antares/study/area/links.cpp index 40da9df149..96a304d28e 100644 --- a/src/libs/antares/study/area/links.cpp +++ b/src/libs/antares/study/area/links.cpp @@ -23,10 +23,7 @@ #include #include - -#include - -#include +#include #include #include @@ -137,58 +134,6 @@ 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 @@ -360,7 +305,16 @@ AreaLink* AreaAddLinkBetweenAreas(Area* area, Area* with, bool warning) namespace // anonymous { -bool handleKey(Data::AreaLink& link, const String& key, const String& value) + +bool isPropertyUsedForLinkTSgeneration(const std::string& key) +{ + std::array listKeys + = {"unitcount", "nominalcapacity", "law.planned", "law.forced", + "volatility.planned", "volatility.forced", "force-no-generation"}; + return std::find(listKeys.begin(), listKeys.end(), key) != listKeys.end(); +} + +bool AreaLinksInternalLoadFromProperty(AreaLink& link, const String& key, const String& value) { if (key == "hurdles-cost") { @@ -503,55 +457,9 @@ 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) || handleTSGenKey(link.tsGeneration, key, value); + // Properties used by TS generator only. + // We just skip them (otherwise : reading error) + return isPropertyUsedForLinkTSgeneration(key.to()); } [[noreturn]] void logLinkDataCheckError(const AreaLink& link, const String& msg, int hour) @@ -572,7 +480,7 @@ bool AreaLinksInternalLoadFromProperty(AreaLink& link, const String& key, const } } // anonymous namespace -bool AreaLinksLoadFromFolder(Study& study, AreaList* l, Area* area, const fs::path& folder, bool loadTSGen) +bool AreaLinksLoadFromFolder(Study& study, AreaList* areaList, Area* area, const fs::path& folder) { // Assert assert(area); @@ -594,16 +502,16 @@ bool AreaLinksLoadFromFolder(Study& study, AreaList* l, Area* area, const fs::pa for (auto* s = ini.firstSection; s; s = s->next) { // Getting the name of the area - std::string buffer = transformNameIntoID(s->name); + const std::string targetAreaName = transformNameIntoID(s->name); // Trying to find it - Area* linkedWith = AreaListLFind(l, buffer.c_str()); - if (!linkedWith) + Area* targetArea = AreaListLFind(areaList, targetAreaName.c_str()); + if (!targetArea) { logs.error() << '`' << s->name << "`: Impossible to find the area"; continue; } - AreaLink* lnk = AreaAddLinkBetweenAreas(area, linkedWith); + AreaLink* lnk = AreaAddLinkBetweenAreas(area, targetArea); if (!lnk) { logs.error() << "Impossible to create a link between two areas"; @@ -698,11 +606,6 @@ 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 f76afb5686..fd2b3bb5b7 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, options.linksLoadTSGen) && ret; + ret = AreaLinksLoadFromFolder(study, list, &area, folder) && ret; } // UI diff --git a/src/libs/antares/study/cleaner/cleaner-v20.cpp b/src/libs/antares/study/cleaner/cleaner-v20.cpp index 7d1aadf58e..3c8f183be4 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(), false)) + if (not AreaLinksLoadFromFolder(*study, arealist, area, buffer.c_str())) { delete arealist; delete study; diff --git a/src/libs/antares/study/fwd.cpp b/src/libs/antares/study/fwd.cpp index f45f093aaf..100a4b127e 100644 --- a/src/libs/antares/study/fwd.cpp +++ b/src/libs/antares/study/fwd.cpp @@ -53,8 +53,6 @@ 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 ""; } @@ -87,8 +85,6 @@ 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 6fbb69dde4..6661ab8556 100644 --- a/src/libs/antares/study/include/antares/study/area/area.h +++ b/src/libs/antares/study/include/antares/study/area/area.h @@ -727,8 +727,7 @@ AreaLink* AreaAddLinkBetweenAreas(Area* area, Area* with, bool warning = true); bool AreaLinksLoadFromFolder(Study& s, AreaList* l, Area* area, - const std::filesystem::path& folder, - bool loadTSGen); + const std::filesystem::path& folder); /*! ** \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 5b01ba3efd..fd4b8e69d8 100644 --- a/src/libs/antares/study/include/antares/study/area/links.h +++ b/src/libs/antares/study/include/antares/study/area/links.h @@ -71,8 +71,6 @@ 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 @@ -208,9 +206,6 @@ 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 7256eda4a2..6fa2819aca 100644 --- a/src/libs/antares/study/include/antares/study/fwd.h +++ b/src/libs/antares/study/include/antares/study/fwd.h @@ -361,8 +361,6 @@ 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 1ecd34b968..41eee86cbb 100644 --- a/src/libs/antares/study/include/antares/study/load-options.h +++ b/src/libs/antares/study/include/antares/study/load-options.h @@ -53,9 +53,6 @@ class StudyLoadOptions //! 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 73ba5b5f77..204afcdd0d 100644 --- a/src/libs/antares/study/include/antares/study/parameters.h +++ b/src/libs/antares/study/include/antares/study/parameters.h @@ -257,8 +257,6 @@ 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/include/antares/study/parts/hydro/container.h b/src/libs/antares/study/include/antares/study/parts/hydro/container.h index 6748c830e7..9bece26ff3 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 @@ -29,6 +29,60 @@ namespace Antares::Data { + + //! The maximum number of days in a year +constexpr size_t dayYearCount = 366; + +struct DailyDemand +{ + //! Net demand, for each day of the year, for each area + double DLN = 0.; + //! Daily local effective load + double DLE = 0.; +}; + +struct MonthlyGenerationTargetData +{ + //! Monthly local effective demand + double MLE = 0.; + //! Monthly optimal generation + double MOG = 0.; + //! Monthly optimal level + double MOL = 0.; + //! Monthly target generations + double MTG = 0.; +}; + +//! Hydro Management Data for a given area +struct TimeDependantHydroManagementData +{ + std::array daily{0}; + std::array monthly{0}; +}; + +//! Area Hydro Management Data for a given year +struct AreaDependantHydroManagementData +{ + //! inflows + std::array inflows{}; + //! monthly minimal generation + std::array mingens{}; + + //! daily minimal generation + std::array dailyMinGen{}; + + // Data for minGen<->inflows preChecks + //! monthly total mingen + std::array totalMonthMingen{}; + //! monthly total inflows + std::array totalMonthInflows{}; + //! yearly total mingen + double totalYearMingen = 0; + //! yearly total inflows + double totalYearInflows = 0; + +}; // struct AreaDependantHydroManagementData + /*! ** \brief Hydro for a single area */ @@ -165,6 +219,7 @@ class PartHydro // which contains other time. Matrix dailyNbHoursAtGenPmax; Matrix dailyNbHoursAtPumpPmax; + std::unordered_map managementData; std::vector> deltaBetweenFinalAndInitialLevels; diff --git a/src/libs/antares/study/parameters.cpp b/src/libs/antares/study/parameters.cpp index dbfb7f81e2..af0aedc14b 100644 --- a/src/libs/antares/study/parameters.cpp +++ b/src/libs/antares/study/parameters.cpp @@ -530,7 +530,9 @@ static bool SGDIntLoadFamily_General(Parameters& d, } if (key == "nbtimeserieslinks") { - return value.to(d.nbLinkTStoGenerate); + // This data is among solver data, but is useless while running a simulation + // Only by TS generator. We skip it here (otherwise, we get a reading error). + return true; } // Interval values if (key == "refreshintervalload") @@ -1026,10 +1028,6 @@ 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]); @@ -1046,6 +1044,10 @@ static bool SGDIntLoadFamily_SeedsMersenneTwister(Parameters& d, return value.to(d.seed[sd]); } } + if (key == "seed-tsgen-links") + { + return true; // Useless for solver, belongs to TS generator + } } } return false; @@ -1766,7 +1768,6 @@ 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/libs/antares/study/runtime/runtime.cpp b/src/libs/antares/study/runtime/runtime.cpp index 035faf86ac..1b093f4d2a 100644 --- a/src/libs/antares/study/runtime/runtime.cpp +++ b/src/libs/antares/study/runtime/runtime.cpp @@ -175,6 +175,7 @@ void StudyRuntimeInfos::initializeRangeLimits(const Study& study, StudyRangeLimi limits.month[rangeCount] = limits.month[rangeEnd] - limits.month[rangeBegin] + 1; // year limits.year[rangeBegin] = 0; + /// reminder to get rangeLimits.year[Data::rangeEnd] limits.year[rangeEnd] = study.parameters.nbYears - 1; limits.year[rangeCount] = study.parameters.effectiveNbYears; diff --git a/src/solver/application/CMakeLists.txt b/src/solver/application/CMakeLists.txt index 413e99543b..9f554a4b9f 100644 --- a/src/solver/application/CMakeLists.txt +++ b/src/solver/application/CMakeLists.txt @@ -1,10 +1,12 @@ set(HEADERS include/antares/application/application.h + include/antares/application/ScenarioBuilderOwner.h ) set(SRC_APPLICATION ${HEADERS} application.cpp process-priority.cpp + ScenarioBuilderOwner.cpp ) source_group("application" FILES ${SRC_APPLICATION}) @@ -29,10 +31,10 @@ target_link_libraries(application ) target_include_directories(application - PUBLIC + PUBLIC $ ) -install(DIRECTORY include/antares +install(DIRECTORY include/antares DESTINATION "include" ) \ No newline at end of file diff --git a/src/solver/application/ScenarioBuilderOwner.cpp b/src/solver/application/ScenarioBuilderOwner.cpp new file mode 100644 index 0000000000..ef6084fed5 --- /dev/null +++ b/src/solver/application/ScenarioBuilderOwner.cpp @@ -0,0 +1,57 @@ +/* +** Copyright 2007-2024, RTE (https://www.rte-france.com) +** See AUTHORS.txt +** SPDX-License-Identifier: MPL-2.0 +** This file is part of Antares-Simulator, +** Adequacy and Performance assessment for interconnected energy networks. +** +** Antares_Simulator is free software: you can redistribute it and/or modify +** it under the terms of the Mozilla Public Licence 2.0 as published by +** the Mozilla Foundation, either version 2 of the License, or +** (at your option) any later version. +** +** Antares_Simulator is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** Mozilla Public Licence 2.0 for more details. +** +** You should have received a copy of the Mozilla Public Licence 2.0 +** along with Antares_Simulator. If not, see . +*/ + +#include +#include +#include "antares/solver/simulation/apply-scenario.h" +#include "antares/solver/simulation/timeseries-numbers.h" +#include "antares/solver/ts-generator/generator.h" +#include "antares/study/study.h" + +Antares::Solver::ScenarioBuilderOwner::ScenarioBuilderOwner(Data::Study& study): + study_(study) +{ +} + +void Antares::Solver::ScenarioBuilderOwner::callScenarioBuilder() { + TSGenerator::ResizeGeneratedTimeSeries(study_.areas, study_.parameters); + + // Sampled time-series Numbers + // We will resize all matrix related to the time-series numbers + // This operation can be done once since the number of years is constant + // for a single simulation + study_.resizeAllTimeseriesNumbers(1 + study_.runtime->rangeLimits.year[Data::rangeEnd]); + if (not TimeSeriesNumbers::CheckNumberOfColumns(study_.areas)) + { + throw FatalError( + "Inconsistent number of time-series detected. Please check your input data."); + } + + if (not TimeSeriesNumbers::Generate(study_)) + { + throw FatalError("An unrecoverable error has occurred. Can not continue."); + } + if (study_.parameters.useCustomScenario) + { + ApplyCustomScenario(study_); + } +} + diff --git a/src/solver/application/application.cpp b/src/solver/application/application.cpp index 90d5e559c5..4a47abd1ff 100644 --- a/src/solver/application/application.cpp +++ b/src/solver/application/application.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -232,6 +233,8 @@ void Application::readDataForTheStudy(Data::StudyLoadOptions& options) // Apply transformations needed by the solver only (and not the interface for example) study.performTransformationsBeforeLaunchingSimulation(); + ScenarioBuilderOwner(study).callScenarioBuilder(); + // alloc global vectors SIM_AllocationTableaux(study); } @@ -256,7 +259,6 @@ void Application::startSimulation(Data::StudyLoadOptions& options) pStudy = std::make_unique(true /* for the solver */); pParameters = &(pStudy->parameters); - readDataForTheStudy(options); postParametersChecks(); diff --git a/src/solver/application/include/antares/application/ScenarioBuilderOwner.h b/src/solver/application/include/antares/application/ScenarioBuilderOwner.h new file mode 100644 index 0000000000..f536737be0 --- /dev/null +++ b/src/solver/application/include/antares/application/ScenarioBuilderOwner.h @@ -0,0 +1,44 @@ +/* +** Copyright 2007-2024, RTE (https://www.rte-france.com) +** See AUTHORS.txt +** SPDX-License-Identifier: MPL-2.0 +** This file is part of Antares-Simulator, +** Adequacy and Performance assessment for interconnected energy networks. +** +** Antares_Simulator is free software: you can redistribute it and/or modify +** it under the terms of the Mozilla Public Licence 2.0 as published by +** the Mozilla Foundation, either version 2 of the License, or +** (at your option) any later version. +** +** Antares_Simulator is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** Mozilla Public Licence 2.0 for more details. +** +** You should have received a copy of the Mozilla Public Licence 2.0 +** along with Antares_Simulator. If not, see . +*/ +#pragma once + +namespace Antares +{ +namespace Data +{ +class Study; +} + +namespace Solver +{ + +class ScenarioBuilderOwner +{ +public: + explicit ScenarioBuilderOwner(Antares::Data::Study& study); + + void callScenarioBuilder(); + +private: + Antares::Data::Study& study_; +}; +} // namespace Solver +} // namespace Antares diff --git a/src/solver/application/include/antares/application/application.h b/src/solver/application/include/antares/application/application.h index 788aff68e9..be114f2d88 100644 --- a/src/solver/application/include/antares/application/application.h +++ b/src/solver/application/include/antares/application/application.h @@ -130,5 +130,6 @@ class Application final: public Yuni::IEventObserver. +*/ +#pragma once +#include +#include "antares/date/date.h" +#include "antares/solver/hydro/management/MinGenerationScaling.h" +#include "antares/solver/hydro/management/PrepareInflows.h" +#include "antares/study/study.h" +namespace Antares +{ + +class HydroInputsChecker +{ +public: + explicit HydroInputsChecker(Antares::Data::Study& study); + void Execute(uint year); + +private: + Data::AreaList& areas_; + const Data::Parameters& parameters_; + const Date::Calendar& calendar_; + Data::SimulationMode simulationMode_; + const uint firstYear_; + const uint endYear_; + PrepareInflows prepareInflows_; + MinGenerationScaling minGenerationScaling_; + const Data::TimeSeries::TS& scenarioInitialHydroLevels_; + const Data::TimeSeries::TS& scenarioFinalHydroLevels_; + + //! return false if checkGenerationPowerConsistency or checkMinGeneration returns false + bool checkMonthlyMinGeneration(uint year, const Data::Area& area) const; + //! check Yearly minimum generation is lower than available inflows + bool checkYearlyMinGeneration(uint year, const Data::Area& area) const; + //! check Weekly minimum generation is lower than available inflows + bool checkWeeklyMinGeneration(uint year, const Data::Area& area) const; + //! check Hourly minimum generation is lower than available inflows + bool checkGenerationPowerConsistency(uint year) const; + //! return false if checkGenerationPowerConsistency or checkMinGeneration returns false + bool checksOnGenerationPowerBounds(uint year) const; + //! check minimum generation is lower than available inflows + bool checkMinGeneration(uint year) const; +}; + +} // namespace Antares diff --git a/src/solver/hydro/include/antares/solver/hydro/management/MinGenerationScaling.h b/src/solver/hydro/include/antares/solver/hydro/management/MinGenerationScaling.h new file mode 100644 index 0000000000..31feb6fa53 --- /dev/null +++ b/src/solver/hydro/include/antares/solver/hydro/management/MinGenerationScaling.h @@ -0,0 +1,41 @@ +/* +** Copyright 2007-2024, RTE (https://www.rte-france.com) +** See AUTHORS.txt +** SPDX-License-Identifier: MPL-2.0 +** This file is part of Antares-Simulator, +** Adequacy and Performance assessment for interconnected energy networks. +** +** Antares_Simulator is free software: you can redistribute it and/or modify +** it under the terms of the Mozilla Public Licence 2.0 as published by +** the Mozilla Foundation, either version 2 of the License, or +** (at your option) any later version. +** +** Antares_Simulator is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** Mozilla Public Licence 2.0 for more details. +** +** You should have received a copy of the Mozilla Public Licence 2.0 +** along with Antares_Simulator. If not, see . +*/ +#pragma once + +#include +#include "antares/date/date.h" + +namespace Antares +{ + +//! Prepare minimum generation scaling for each area +class MinGenerationScaling +{ +public: + MinGenerationScaling(Data::AreaList& areas, const Date::Calendar& calendar); + void Run(uint year); + +private: + Data::AreaList& areas_; + const Date::Calendar& calendar_; +}; + +} // namespace Antares diff --git a/src/solver/hydro/include/antares/solver/hydro/management/PrepareInflows.h b/src/solver/hydro/include/antares/solver/hydro/management/PrepareInflows.h new file mode 100644 index 0000000000..79b7eae0a5 --- /dev/null +++ b/src/solver/hydro/include/antares/solver/hydro/management/PrepareInflows.h @@ -0,0 +1,43 @@ +/* +** Copyright 2007-2024, RTE (https://www.rte-france.com) +** See AUTHORS.txt +** SPDX-License-Identifier: MPL-2.0 +** This file is part of Antares-Simulator, +** Adequacy and Performance assessment for interconnected energy networks. +** +** Antares_Simulator is free software: you can redistribute it and/or modify +** it under the terms of the Mozilla Public Licence 2.0 as published by +** the Mozilla Foundation, either version 2 of the License, or +** (at your option) any later version. +** +** Antares_Simulator is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** Mozilla Public Licence 2.0 for more details. +** +** You should have received a copy of the Mozilla Public Licence 2.0 +** along with Antares_Simulator. If not, see . +*/ +#pragma once +#include +#include "antares/date/date.h" + +namespace Antares +{ + +//! Prepare inflows scaling for each area +class PrepareInflows +{ +public: + PrepareInflows(Data::AreaList& areas, const Date::Calendar& calendar); + void Run(uint year); + +private: + void LoadInflows(uint year); + //! prepare data for Final reservoir level + void ChangeInflowsToAccommodateFinalLevels(uint year); + Data::AreaList& areas_; + const Date::Calendar& calendar_; +}; + +} // namespace Antares diff --git a/src/solver/simulation/include/antares/solver/simulation/hydro-final-reservoir-level-functions.h b/src/solver/hydro/include/antares/solver/hydro/management/hydro-final-reservoir-level-functions.h similarity index 77% rename from src/solver/simulation/include/antares/solver/simulation/hydro-final-reservoir-level-functions.h rename to src/solver/hydro/include/antares/solver/hydro/management/hydro-final-reservoir-level-functions.h index 4b60c4cc8f..9ded556947 100644 --- a/src/solver/simulation/include/antares/solver/simulation/hydro-final-reservoir-level-functions.h +++ b/src/solver/hydro/include/antares/solver/hydro/management/hydro-final-reservoir-level-functions.h @@ -31,7 +31,11 @@ namespace Antares::Solver { -void CheckFinalReservoirLevelsConfiguration(const Data::Study& study); +void CheckFinalReservoirLevelsConfiguration(Data::AreaList& areas, + const Data::Parameters& parameters, + const Data::TimeSeries::TS& scenarioInitialHydroLevels, + const Data::TimeSeries::TS& scenarioFinalHydroLevels, + uint year); } // namespace Antares::Solver #endif // __SOLVER_SIMULATION_HYDRO_FINAL_RESERVOIR_PRE_CHECKS_H__ 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 b642e441c2..7d8f5360c5 100644 --- a/src/solver/hydro/include/antares/solver/hydro/management/management.h +++ b/src/solver/hydro/include/antares/solver/hydro/management/management.h @@ -47,49 +47,6 @@ double GammaVariable(double a, MersenneTwister& random); } // namespace Solver -enum -{ - //! The maximum number of days in a year - dayYearCount = 366 -}; - -//! Temporary data -struct TmpDataByArea -{ - //! Monthly local effective demand - double MLE[12]; - //! Monthly optimal generation - double MOG[12]; - //! Monthly optimal level - double MOL[12]; - //! Monthly target generations - double MTG[12]; - //! inflows - double inflows[12]; - //! monthly minimal generation - std::array mingens; - - //! Net demand, for each day of the year, for each area - double DLN[dayYearCount]; - //! Daily local effective load - double DLE[dayYearCount]; - //! Daily optimized Generation - double DOG[dayYearCount]; - //! daily minimal generation - std::array dailyMinGen; - - // Data for minGen<->inflows preChecks - //! monthly total mingen - std::array totalMonthMingen; - //! monthly total inflows - std::array totalMonthInflows; - //! yearly total mingen - double totalYearMingen; - //! yearly total inflows - double totalYearInflows; - -}; // struct TmpDataByArea - typedef struct { std::vector HydrauliqueModulableQuotidien; /* indice par jour */ @@ -100,6 +57,8 @@ typedef struct } VENTILATION_HYDRO_RESULTS_BY_AREA; using HYDRO_VENTILATION_RESULTS = std::vector; +using HydroSpecificMap = std::unordered_map; class HydroManagement final { @@ -120,47 +79,42 @@ 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 - bool checkMonthlyMinGeneration(uint year, const Data::Area& area) const; - //! check Yearly minimum generation is lower than available inflows - bool checkYearlyMinGeneration(uint year, const Data::Area& area) const; - //! check Weekly minimum generation is lower than available inflows - bool checkWeeklyMinGeneration(uint year, const Data::Area& area) const; - //! check Hourly minimum generation is lower than available inflows - bool checkGenerationPowerConsistency(uint year) const; - //! return false if checkGenerationPowerConsistency or checkMinGeneration returns false - bool checksOnGenerationPowerBounds(uint year) const; - //! check minimum generation is lower than available inflows - bool checkMinGeneration(uint year) const; //! Prepare the net demand for each area - void prepareNetDemand(uint year, - Data::SimulationMode mode, - const Antares::Data::Area::ScratchMap& scratchmap); + void prepareNetDemand( + uint year, + Data::SimulationMode mode, + const Antares::Data::Area::ScratchMap& scratchmap, + HydroSpecificMap& hydro_specific_map); //! Prepare the effective demand for each area - void prepareEffectiveDemand(); + void prepareEffectiveDemand( + uint year, + HydroSpecificMap& hydro_specific_map); //! Monthly Optimal generations - void prepareMonthlyOptimalGenerations(double* random_reservoir_level, uint y); + void prepareMonthlyOptimalGenerations( + double* random_reservoir_level, + uint y, + HydroSpecificMap& hydro_specific_map); //! Monthly target generations // note: inflows may have two different types, if in swap mode or not // \return The total inflow for the whole year - double prepareMonthlyTargetGenerations(Data::Area& area, TmpDataByArea& data); - - void prepareDailyOptimalGenerations(uint y, - Antares::Data::Area::ScratchMap& scratchmap); - - void prepareDailyOptimalGenerations(Data::Area& area, - uint y, - Antares::Data::Area::ScratchMap& scratchmap); + double prepareMonthlyTargetGenerations( + Data::Area& area, + Antares::Data::AreaDependantHydroManagementData& data, + Antares::Data::TimeDependantHydroManagementData& hydro_specific); + + void prepareDailyOptimalGenerations( + uint y, + Antares::Data::Area::ScratchMap& scratchmap, + HydroSpecificMap& hydro_specific_map); + + void prepareDailyOptimalGenerations( + Data::Area& area, + uint y, + Antares::Data::Area::ScratchMap& scratchmap, + Antares::Data::TimeDependantHydroManagementData& hydro_specific); private: - std::unordered_map tmpDataByArea_; const Data::AreaList& areas_; const Date::Calendar& calendar_; const Data::Parameters& parameters_; diff --git a/src/solver/hydro/management/HydroInputsChecker.cpp b/src/solver/hydro/management/HydroInputsChecker.cpp new file mode 100644 index 0000000000..613e760f74 --- /dev/null +++ b/src/solver/hydro/management/HydroInputsChecker.cpp @@ -0,0 +1,204 @@ +/* +** Copyright 2007-2024, RTE (https://www.rte-france.com) +** See AUTHORS.txt +** SPDX-License-Identifier: MPL-2.0 +** This file is part of Antares-Simulator, +** Adequacy and Performance assessment for interconnected energy networks. +** +** Antares_Simulator is free software: you can redistribute it and/or modify +** it under the terms of the Mozilla Public Licence 2.0 as published by +** the Mozilla Foundation, either version 2 of the License, or +** (at your option) any later version. +** +** Antares_Simulator is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** Mozilla Public Licence 2.0 for more details. +** +** You should have received a copy of the Mozilla Public Licence 2.0 +** along with Antares_Simulator. If not, see . +*/ +#include "antares/solver/hydro/management/HydroInputsChecker.h" + +#include + +#include +#include "antares/antares/fatal-error.h" +#include "antares/solver/hydro/management/hydro-final-reservoir-level-functions.h" +#include "antares/solver/hydro/monthly/h2o_m_donnees_annuelles.h" +#include "antares/solver/hydro/monthly/h2o_m_fonctions.h" +#include "antares/solver/simulation/common-eco-adq.h" + +namespace Antares +{ + +HydroInputsChecker::HydroInputsChecker(Antares::Data::Study& study): + areas_(study.areas), + parameters_(study.parameters), + calendar_(study.calendar), + simulationMode_(study.runtime->mode), + firstYear_(0), + endYear_(1 + study.runtime->rangeLimits.year[Data::rangeEnd]), + prepareInflows_(study.areas, study.calendar), + minGenerationScaling_(study.areas, study.calendar), + scenarioInitialHydroLevels_(study.scenarioInitialHydroLevels), + scenarioFinalHydroLevels_(study.scenarioFinalHydroLevels) +{ +} + +void HydroInputsChecker::Execute(uint year) +{ + prepareInflows_.Run(year); + minGenerationScaling_.Run(year); + + if (!checksOnGenerationPowerBounds(year)) + { + throw FatalError("hydro inputs checks: invalid minimum generation"); + } + if (parameters_.useCustomScenario) + { + CheckFinalReservoirLevelsConfiguration(areas_, parameters_, scenarioInitialHydroLevels_, scenarioFinalHydroLevels_, year); + } +} + +bool HydroInputsChecker::checksOnGenerationPowerBounds(uint year) const +{ + return checkMinGeneration(year) && checkGenerationPowerConsistency(year); +} + +bool HydroInputsChecker::checkMinGeneration(uint year) const +{ + bool ret = true; + areas_.each( + [this, &ret, &year](const Data::Area& area) + { + bool useHeuristicTarget = area.hydro.useHeuristicTarget; + bool followLoadModulations = area.hydro.followLoadModulations; + bool reservoirManagement = area.hydro.reservoirManagement; + + if (!useHeuristicTarget) + { + return; + } + + if (!followLoadModulations) + { + ret = checkWeeklyMinGeneration(year, area) && ret; + return; + } + + if (reservoirManagement) + { + ret = checkYearlyMinGeneration(year, area) && ret; + } + else + { + ret = checkMonthlyMinGeneration(year, area) && ret; + } + }); + return ret; +} + +bool HydroInputsChecker::checkWeeklyMinGeneration(uint year, const Data::Area& area) const +{ + const auto& srcinflows = area.hydro.series->storage.getColumn(year); + const auto& srcmingen = area.hydro.series->mingen.getColumn(year); + // Weekly minimum generation <= Weekly inflows for each week + for (uint week = 0; week < calendar_.maxWeeksInYear - 1; ++week) + { + double totalWeekMingen = 0.0; + double totalWeekInflows = 0.0; + for (uint hour = calendar_.weeks[week].hours.first; + hour < calendar_.weeks[week].hours.end && hour < HOURS_PER_YEAR; + ++hour) + { + totalWeekMingen += srcmingen[hour]; + } + + for (uint day = calendar_.weeks[week].daysYear.first; + day < calendar_.weeks[week].daysYear.end; + ++day) + { + totalWeekInflows += srcinflows[day]; + } + if (totalWeekMingen > totalWeekInflows) + { + logs.error() << "In Area " << area.name << " the minimum generation of " + << totalWeekMingen << " MW in week " << week + 1 << " of TS-" + << area.hydro.series->mingen.getSeriesIndex(year) + 1 + << " is incompatible with the inflows of " << totalWeekInflows << " MW."; + return false; + } + } + return true; +} + +bool HydroInputsChecker::checkYearlyMinGeneration(uint year, const Data::Area& area) const +{ + const auto& data = area.hydro.managementData.at(year); + if (data.totalYearMingen > data.totalYearInflows) + { + // Yearly minimum generation <= Yearly inflows + logs.error() << "In Area " << area.name << " the minimum generation of " + << data.totalYearMingen << " MW of TS-" + << area.hydro.series->mingen.getSeriesIndex(year) + 1 + << " is incompatible with the inflows of " << data.totalYearInflows << " MW."; + return false; + } + return true; +} + +bool HydroInputsChecker::checkMonthlyMinGeneration(uint year, const Data::Area& area) const +{ + const auto& data = area.hydro.managementData.at(year); + for (uint month = 0; month != 12; ++month) + { + uint realmonth = calendar_.months[month].realmonth; + // Monthly minimum generation <= Monthly inflows for each month + if (data.totalMonthMingen[realmonth] > data.totalMonthInflows[realmonth]) + { + logs.error() << "In Area " << area.name << " the minimum generation of " + << data.totalMonthMingen[realmonth] << " MW in month " << month + 1 + << " of TS-" << area.hydro.series->mingen.getSeriesIndex(year) + 1 + << " is incompatible with the inflows of " + << data.totalMonthInflows[realmonth] << " MW."; + return false; + } + } + return true; +} + +bool HydroInputsChecker::checkGenerationPowerConsistency(uint year) const +{ + bool ret = true; + + areas_.each( + [&ret, &year](const Data::Area& area) + { + const auto& srcmingen = area.hydro.series->mingen.getColumn(year); + const auto& srcmaxgen = area.hydro.series->maxHourlyGenPower.getColumn(year); + + const uint tsIndexMin = area.hydro.series->mingen.getSeriesIndex(year); + const uint tsIndexMax = area.hydro.series->maxHourlyGenPower.getSeriesIndex(year); + + for (uint h = 0; h < HOURS_PER_YEAR; ++h) + { + const auto& min = srcmingen[h]; + const auto& max = srcmaxgen[h]; + + if (max < min) + { + logs.error() << "In area: " << area.name << " [hourly] minimum generation of " + << min << " MW in timestep " << h + 1 << " of TS-" << tsIndexMin + 1 + << " is incompatible with the maximum generation of " << max + << " MW in timestep " << h + 1 << " of TS-" << tsIndexMax + 1 + << " MW."; + ret = false; + return; + } + } + }); + + return ret; +} +} // namespace Antares diff --git a/src/solver/hydro/management/MinGenerationScaling.cpp b/src/solver/hydro/management/MinGenerationScaling.cpp new file mode 100644 index 0000000000..08c9bf87d1 --- /dev/null +++ b/src/solver/hydro/management/MinGenerationScaling.cpp @@ -0,0 +1,92 @@ +/* +** Copyright 2007-2024, RTE (https://www.rte-france.com) +** See AUTHORS.txt +** SPDX-License-Identifier: MPL-2.0 +** This file is part of Antares-Simulator, +** Adequacy and Performance assessment for interconnected energy networks. +** +** Antares_Simulator is free software: you can redistribute it and/or modify +** it under the terms of the Mozilla Public Licence 2.0 as published by +** the Mozilla Foundation, either version 2 of the License, or +** (at your option) any later version. +** +** Antares_Simulator is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** Mozilla Public Licence 2.0 for more details. +** +** You should have received a copy of the Mozilla Public Licence 2.0 +** along with Antares_Simulator. If not, see . +*/ + +#include "antares/solver/hydro/management/MinGenerationScaling.h" + +#include + +namespace Antares +{ +MinGenerationScaling::MinGenerationScaling(Data::AreaList& areas, const Date::Calendar& calendar): + areas_(areas), + calendar_(calendar) +{ +} + +void MinGenerationScaling::Run(uint year) +{ + areas_.each( + // un-const because now data is a member of area [&](const Data::Area& area) + [this, &year](Data::Area& area) + { + const auto& srcmingen = area.hydro.series->mingen.getColumn(year); + + auto& data = area.hydro.managementData[year]; + double totalYearMingen = 0.0; + + for (uint month = 0; month != 12; ++month) + { + uint realmonth = calendar_.months[month].realmonth; + uint firstDayOfMonth = calendar_.months[month].daysYear.first; + uint firstDayOfNextMonth = calendar_.months[month].daysYear.end; + + double totalMonthMingen = std::accumulate(srcmingen + firstDayOfMonth * 24, + srcmingen + firstDayOfNextMonth * 24, + 0.); + + data.totalMonthMingen[realmonth] = totalMonthMingen; + totalYearMingen += totalMonthMingen; + + if (!(area.hydro.reservoirCapacity < 1e-4)) + { + if (area.hydro.reservoirManagement) + { + // Set monthly mingen, used later for h2o_m + data.mingens[realmonth] = totalMonthMingen / (area.hydro.reservoirCapacity); + assert(!std::isnan(data.mingens[month]) && "nan value detect in mingen"); + } + else + { + data.mingens[realmonth] = totalMonthMingen; + } + } + else + { + data.mingens[realmonth] = totalMonthMingen; + } + + // Set daily mingen, used later for h2o_d + uint simulationMonth = calendar_.mapping.months[realmonth]; + auto daysPerMonth = calendar_.months[simulationMonth].days; + uint firstDay = calendar_.months[simulationMonth].daysYear.first; + uint endDay = firstDay + daysPerMonth; + + for (uint day = firstDay; day != endDay; ++day) + { + data.dailyMinGen[day] = std::accumulate(srcmingen + day * 24, + srcmingen + day * 24 + 24, + 0.); + } + } + data.totalYearMingen = totalYearMingen; + }); +} +} // namespace Antares diff --git a/src/solver/hydro/management/PrepareInflows.cpp b/src/solver/hydro/management/PrepareInflows.cpp new file mode 100644 index 0000000000..f1a43e71fd --- /dev/null +++ b/src/solver/hydro/management/PrepareInflows.cpp @@ -0,0 +1,86 @@ + +#include "antares/solver/hydro/management/PrepareInflows.h" + +namespace Antares +{ + +PrepareInflows::PrepareInflows(Data::AreaList& areas, const Date::Calendar& calendar): + areas_(areas), + calendar_(calendar) +{ +} + +void PrepareInflows::Run(uint year){ + LoadInflows(year); + ChangeInflowsToAccommodateFinalLevels(year); +} +void PrepareInflows::LoadInflows(uint year) +{ + areas_.each( + // un-const because now data is a member of area [&](const Data::Area& area) + [&](Data::Area& area) + { + const auto& srcinflows = area.hydro.series->storage.getColumn(year); + + auto& data = area.hydro.managementData[year]; + double totalYearInflows = 0.0; + + for (uint month = 0; month != 12; ++month) + { + uint realmonth = calendar_.months[month].realmonth; + + double totalMonthInflows = 0.0; + + uint firstDayOfMonth = calendar_.months[month].daysYear.first; + + uint firstDayOfNextMonth = calendar_.months[month].daysYear.end; + + for (uint d = firstDayOfMonth; d != firstDayOfNextMonth; ++d) + { + totalMonthInflows += srcinflows[d]; + } + + data.totalMonthInflows[realmonth] = totalMonthInflows; + totalYearInflows += totalMonthInflows; + + if (not(area.hydro.reservoirCapacity < 1e-4)) + { + if (area.hydro.reservoirManagement) + { + data.inflows[realmonth] = totalMonthInflows / (area.hydro.reservoirCapacity); + assert(!std::isnan(data.inflows[month]) && "nan value detect in inflows"); + } + else + { + data.inflows[realmonth] = totalMonthInflows; + } + } + else + { + data.inflows[realmonth] = totalMonthInflows; + } + } + data.totalYearInflows = totalYearInflows; + }); +} + +void PrepareInflows::ChangeInflowsToAccommodateFinalLevels(uint year) +{ + areas_.each([this, &year](Data::Area& area) + { + auto& data = area.hydro.managementData[year]; + + 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; + }); +} + + +} // namespace Antares diff --git a/src/solver/hydro/management/daily.cpp b/src/solver/hydro/management/daily.cpp index 1c43aca726..db6f671445 100644 --- a/src/solver/hydro/management/daily.cpp +++ b/src/solver/hydro/management/daily.cpp @@ -93,7 +93,7 @@ struct DebugData std::array previousMonthWaste{0}; Solver::IResultWriter& pWriter; - const TmpDataByArea& data; + const Antares::Data::AreaDependantHydroManagementData& data; const VENTILATION_HYDRO_RESULTS_BY_AREA& ventilationResults; const double* srcinflows; const MaxPowerType& maxP; @@ -102,15 +102,18 @@ struct DebugData const ReservoirLevelType& lowLevel; const double reservoirCapacity; + const Antares::Data::TimeDependantHydroManagementData& hydro_specific; + DebugData(Solver::IResultWriter& writer, - const TmpDataByArea& data, + const Antares::Data::AreaDependantHydroManagementData& data, const VENTILATION_HYDRO_RESULTS_BY_AREA& ventilationResults, const double* srcinflows, const MaxPowerType& maxP, const MaxPowerType& maxE, const double* dailyTargetGen, const ReservoirLevelType& lowLevel, - double reservoirCapacity): + double reservoirCapacity, + const Antares::Data::TimeDependantHydroManagementData& hydro_specific): pWriter(writer), data(data), ventilationResults(ventilationResults), @@ -119,7 +122,8 @@ struct DebugData maxE(maxE), dailyTargetGen(dailyTargetGen), lowLevel(lowLevel), - reservoirCapacity(reservoirCapacity) + reservoirCapacity(reservoirCapacity), + hydro_specific(hydro_specific) { OVF.fill(0); DEV.fill(0); @@ -141,7 +145,8 @@ struct DebugData { double value = ventilationResults.HydrauliqueModulableQuotidien[day]; buffer << day << '\t' << value << '\t' << OPP[day] << '\t' << DailyTargetGen[day] - << '\t' << data.DLE[day] << '\t' << data.DLN[day]; + << '\t' << hydro_specific.daily[day].DLE << '\t' + << hydro_specific.daily[day].DLN; buffer << '\n'; } auto buffer_str = buffer.str(); @@ -157,7 +162,7 @@ struct DebugData path << "debug" << SEP << "solver" << SEP << (1 + y) << SEP << "daily." << areaName.c_str() << ".txt"; - buffer << "\tNiveau init : " << data.MOL[initReservoirLvlMonth] << "\n"; + buffer << "\tNiveau init : " << hydro_specific.monthly[initReservoirLvlMonth].MOL << "\n"; for (uint month = 0; month != MONTHS_PER_YEAR; ++month) { uint realmonth = (initReservoirLvlMonth + month) % MONTHS_PER_YEAR; @@ -202,9 +207,9 @@ struct DebugData buffer << '\t' << deviationMax[realmonth] * 100 << '\t' << '\t' << violationMax[realmonth] * 100 << '\t' << '\t' << WASTE[realmonth] * 100 << '\t' << CoutTotal[realmonth] << '\t' - << (data.MOG[realmonth] / reservoirCapacity) * 100 << '\t' << '\t' - << '\t' << '\t' << '\t' - << (data.MOG[realmonth] / reservoirCapacity + << (hydro_specific.monthly[realmonth].MOG / reservoirCapacity) * 100 + << '\t' << '\t' << '\t' << '\t' << '\t' + << (hydro_specific.monthly[realmonth].MOG / reservoirCapacity + previousMonthWaste[realmonth]) * 100; } @@ -219,13 +224,14 @@ struct DebugData }; inline void HydroManagement::prepareDailyOptimalGenerations( - Data::Area& area, - uint y, - Antares::Data::Area::ScratchMap& scratchmap) + Data::Area& area, + uint y, + Antares::Data::Area::ScratchMap& scratchmap, + Antares::Data::TimeDependantHydroManagementData& hydro_specific) { const auto srcinflows = area.hydro.series->storage.getColumn(y); - auto& data = tmpDataByArea_[&area]; + auto& data = area.hydro.managementData[y]; auto& scratchpad = scratchmap.at(&area); @@ -262,7 +268,8 @@ inline void HydroManagement::prepareDailyOptimalGenerations( maxE, dailyTargetGen, lowLevel, - reservoirCapacity); + reservoirCapacity, + hydro_specific); } for (uint month = 0; month != MONTHS_PER_YEAR; ++month) @@ -317,9 +324,9 @@ inline void HydroManagement::prepareDailyOptimalGenerations( for (uint day = 0; day != daysPerMonth; ++day) { auto dYear = day + dayYear; - if (data.DLE[dYear] > demandMax) + if (hydro_specific.daily[dYear].DLE > demandMax) { - demandMax = data.DLE[dYear]; + demandMax = hydro_specific.daily[dYear].DLE; } } @@ -331,23 +338,24 @@ inline void HydroManagement::prepareDailyOptimalGenerations( for (uint day = 0; day != daysPerMonth; ++day) { auto dYear = day + dayYear; - coeff += std::pow(data.DLE[dYear] / demandMax, + coeff += std::pow(hydro_specific.daily[dYear].DLE / demandMax, area.hydro.interDailyBreakdown); } - coeff = data.MOG[realmonth] / coeff; + coeff = hydro_specific.monthly[realmonth].MOG / coeff; for (uint day = 0; day != daysPerMonth; ++day) { auto dYear = day + dayYear; dailyTargetGen[dYear] = coeff - * std::pow(data.DLE[dYear] / demandMax, + * std::pow(hydro_specific.daily[dYear].DLE + / demandMax, area.hydro.interDailyBreakdown); } } else { assert(daysPerMonth > 0); - double coeff = data.MOG[realmonth] / daysPerMonth; + double coeff = hydro_specific.monthly[realmonth].MOG / daysPerMonth; for (uint day = 0; day != daysPerMonth; ++day) { @@ -391,7 +399,7 @@ inline void HydroManagement::prepareDailyOptimalGenerations( DONNEES_MENSUELLES* problem = H2O_J_Instanciation(); H2O_J_AjouterBruitAuCout(*problem); problem->NombreDeJoursDuMois = (int)daysPerMonth; - problem->TurbineDuMois = data.MOG[realmonth]; + problem->TurbineDuMois = hydro_specific.monthly[realmonth].MOG; uint dayMonth = 0; for (uint day = firstDay; day != endDay; ++day) @@ -440,7 +448,7 @@ inline void HydroManagement::prepareDailyOptimalGenerations( else { - double monthInitialLevel = data.MOL[initReservoirLvlMonth]; + double monthInitialLevel = hydro_specific.monthly[initReservoirLvlMonth].MOL; double wasteFromPreviousMonth = 0.; Hydro_problem_costs h2o2_optim_costs(parameters_); @@ -466,7 +474,7 @@ inline void HydroManagement::prepareDailyOptimalGenerations( problem.NombreDeJoursDuMois = (int)daysPerMonth; - problem.TurbineDuMois = (data.MOG[realmonth] + wasteFromPreviousMonth) + problem.TurbineDuMois = (hydro_specific.monthly[realmonth].MOG + wasteFromPreviousMonth) / reservoirCapacity; problem.NiveauInitialDuMois = monthInitialLevel; problem.reservoirCapacity = reservoirCapacity; @@ -549,10 +557,13 @@ inline void HydroManagement::prepareDailyOptimalGenerations( } } -void HydroManagement::prepareDailyOptimalGenerations(uint y, - Antares::Data::Area::ScratchMap& scratchmap) +void HydroManagement::prepareDailyOptimalGenerations( + uint y, + Antares::Data::Area::ScratchMap& scratchmap, + HydroSpecificMap& hydro_specific_map) { - areas_.each([this, &scratchmap, &y](Data::Area& area) - { prepareDailyOptimalGenerations(area, y, scratchmap); }); + areas_.each( + [this, &scratchmap, &y, &hydro_specific_map](Data::Area& area) + { prepareDailyOptimalGenerations(area, y, scratchmap, hydro_specific_map[&area]); }); } } // namespace Antares diff --git a/src/solver/hydro/management/hydro-final-reservoir-level-functions.cpp b/src/solver/hydro/management/hydro-final-reservoir-level-functions.cpp new file mode 100644 index 0000000000..3aa1d7e426 --- /dev/null +++ b/src/solver/hydro/management/hydro-final-reservoir-level-functions.cpp @@ -0,0 +1,69 @@ +/* +** 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/hydro/management/hydro-final-reservoir-level-functions.h" + +#include +#include "antares/study/parts/hydro/finalLevelValidator.h" + +namespace Antares::Solver +{ + +void CheckFinalReservoirLevelsConfiguration(Data::AreaList& areas, + const Data::Parameters& parameters, + const Data::TimeSeries::TS& scenarioInitialHydroLevels, + const Data::TimeSeries::TS& scenarioFinalHydroLevels, + uint year) +{ + if (!parameters.yearsFilter.at(year)) + return; + + areas.each([&areas, ¶meters, &scenarioInitialHydroLevels, &scenarioFinalHydroLevels, year](Data::Area &area) + { + double initialLevel = scenarioInitialHydroLevels.entry[area.index][year]; + double finalLevel = scenarioFinalHydroLevels.entry[area.index][year]; + + Data::FinalLevelValidator validator(area.hydro, + area.index, + area.name, + initialLevel, + finalLevel, + year, + parameters.simulationDays.end, + 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 diff --git a/src/solver/hydro/management/management.cpp b/src/solver/hydro/management/management.cpp index 703235fc61..98b3b0b562 100644 --- a/src/solver/hydro/management/management.cpp +++ b/src/solver/hydro/management/management.cpp @@ -140,279 +140,22 @@ HydroManagement::HydroManagement(const Data::AreaList& areas, } } -void HydroManagement::prepareInflowsScaling(uint year) +void HydroManagement::prepareNetDemand( + uint year, + Data::SimulationMode mode, + const Antares::Data::Area::ScratchMap& scratchmap, + HydroSpecificMap& hydro_specific_map) { areas_.each( - [this, &year](const Data::Area& area) - { - const auto& srcinflows = area.hydro.series->storage.getColumn(year); - - auto& data = tmpDataByArea_[&area]; - double totalYearInflows = 0.0; - - for (uint month = 0; month != 12; ++month) - { - uint realmonth = calendar_.months[month].realmonth; - - double totalMonthInflows = 0.0; - - uint firstDayOfMonth = calendar_.months[month].daysYear.first; - - uint firstDayOfNextMonth = calendar_.months[month].daysYear.end; - - for (uint d = firstDayOfMonth; d != firstDayOfNextMonth; ++d) - { - totalMonthInflows += srcinflows[d]; - } - - data.totalMonthInflows[realmonth] = totalMonthInflows; - totalYearInflows += totalMonthInflows; - - if (not(area.hydro.reservoirCapacity < 1e-4)) - { - if (area.hydro.reservoirManagement) - { - data.inflows[realmonth] = totalMonthInflows / (area.hydro.reservoirCapacity); - assert(!std::isnan(data.inflows[month]) && "nan value detect in inflows"); - } - else - { - data.inflows[realmonth] = totalMonthInflows; - } - } - else - { - data.inflows[realmonth] = totalMonthInflows; - } - } - data.totalYearInflows = totalYearInflows; - }); -} - -void HydroManagement::minGenerationScaling(uint year) -{ - areas_.each( - [this, &year](const Data::Area& area) - { - const auto& srcmingen = area.hydro.series->mingen.getColumn(year); - - auto& data = tmpDataByArea_[&area]; - double totalYearMingen = 0.0; - - for (uint month = 0; month != 12; ++month) - { - uint realmonth = calendar_.months[month].realmonth; - uint firstDayOfMonth = calendar_.months[month].daysYear.first; - uint firstDayOfNextMonth = calendar_.months[month].daysYear.end; - - double totalMonthMingen = std::accumulate(srcmingen + firstDayOfMonth * 24, - srcmingen + firstDayOfNextMonth * 24, - 0.); - - data.totalMonthMingen[realmonth] = totalMonthMingen; - totalYearMingen += totalMonthMingen; - - if (!(area.hydro.reservoirCapacity < 1e-4)) - { - if (area.hydro.reservoirManagement) - { - // Set monthly mingen, used later for h2o_m - data.mingens[realmonth] = totalMonthMingen / (area.hydro.reservoirCapacity); - assert(!std::isnan(data.mingens[month]) && "nan value detect in mingen"); - } - else - { - data.mingens[realmonth] = totalMonthMingen; - } - } - else - { - data.mingens[realmonth] = totalMonthMingen; - } - - // Set daily mingen, used later for h2o_d - uint simulationMonth = calendar_.mapping.months[realmonth]; - auto daysPerMonth = calendar_.months[simulationMonth].days; - uint firstDay = calendar_.months[simulationMonth].daysYear.first; - uint endDay = firstDay + daysPerMonth; - - for (uint day = firstDay; day != endDay; ++day) - { - data.dailyMinGen[day] = std::accumulate(srcmingen + day * 24, - srcmingen + day * 24 + 24, - 0.); - } - } - data.totalYearMingen = totalYearMingen; - }); -} - -bool HydroManagement::checkMonthlyMinGeneration(uint year, const Data::Area& area) const -{ - const auto& data = tmpDataByArea_.at(&area); - for (uint month = 0; month != 12; ++month) - { - uint realmonth = calendar_.months[month].realmonth; - // Monthly minimum generation <= Monthly inflows for each month - if (data.totalMonthMingen[realmonth] > data.totalMonthInflows[realmonth]) - { - logs.error() << "In Area " << area.name << " the minimum generation of " - << data.totalMonthMingen[realmonth] << " MW in month " << month + 1 - << " of TS-" << area.hydro.series->mingen.getSeriesIndex(year) + 1 - << " is incompatible with the inflows of " - << data.totalMonthInflows[realmonth] << " MW."; - return false; - } - } - return true; -} - -bool HydroManagement::checkYearlyMinGeneration(uint year, const Data::Area& area) const -{ - const auto& data = tmpDataByArea_.at(&area); - if (data.totalYearMingen > data.totalYearInflows) - { - // Yearly minimum generation <= Yearly inflows - logs.error() << "In Area " << area.name << " the minimum generation of " - << data.totalYearMingen << " MW of TS-" - << area.hydro.series->mingen.getSeriesIndex(year) + 1 - << " is incompatible with the inflows of " << data.totalYearInflows << " MW."; - return false; - } - return true; -} - -bool HydroManagement::checkWeeklyMinGeneration(uint year, const Data::Area& area) const -{ - const auto& srcinflows = area.hydro.series->storage.getColumn(year); - const auto& srcmingen = area.hydro.series->mingen.getColumn(year); - // Weekly minimum generation <= Weekly inflows for each week - for (uint week = 0; week < calendar_.maxWeeksInYear - 1; ++week) - { - double totalWeekMingen = 0.0; - double totalWeekInflows = 0.0; - for (uint hour = calendar_.weeks[week].hours.first; - hour < calendar_.weeks[week].hours.end && hour < HOURS_PER_YEAR; - ++hour) - { - totalWeekMingen += srcmingen[hour]; - } - - for (uint day = calendar_.weeks[week].daysYear.first; - day < calendar_.weeks[week].daysYear.end; - ++day) - { - totalWeekInflows += srcinflows[day]; - } - if (totalWeekMingen > totalWeekInflows) - { - logs.error() << "In Area " << area.name << " the minimum generation of " - << totalWeekMingen << " MW in week " << week + 1 << " of TS-" - << area.hydro.series->mingen.getSeriesIndex(year) + 1 - << " is incompatible with the inflows of " << totalWeekInflows << " MW."; - return false; - } - } - return true; -} - -bool HydroManagement::checkGenerationPowerConsistency(uint year) const -{ - bool ret = true; - - areas_.each( - [&ret, &year](const Data::Area& area) - { - const auto& srcmingen = area.hydro.series->mingen.getColumn(year); - const auto& srcmaxgen = area.hydro.series->maxHourlyGenPower.getColumn(year); - - const uint tsIndexMin = area.hydro.series->mingen.getSeriesIndex(year); - const uint tsIndexMax = area.hydro.series->maxHourlyGenPower.getSeriesIndex(year); - - for (uint h = 0; h < HOURS_PER_YEAR; ++h) - { - const auto& min = srcmingen[h]; - const auto& max = srcmaxgen[h]; - - if (max < min) - { - logs.error() << "In area: " << area.name << " [hourly] minimum generation of " - << min << " MW in timestep " << h + 1 << " of TS-" << tsIndexMin + 1 - << " is incompatible with the maximum generation of " << max - << " MW in timestep " << h + 1 << " of TS-" << tsIndexMax + 1 - << " MW."; - ret = false; - return; - } - } - }); - - return ret; -} - -bool HydroManagement::checkMinGeneration(uint year) const -{ - bool ret = true; - areas_.each( - [this, &ret, &year](const Data::Area& area) - { - bool useHeuristicTarget = area.hydro.useHeuristicTarget; - bool followLoadModulations = area.hydro.followLoadModulations; - bool reservoirManagement = area.hydro.reservoirManagement; - - if (!useHeuristicTarget) - { - return; - } - - if (!followLoadModulations) - { - ret = checkWeeklyMinGeneration(year, area) && ret; - return; - } - - if (reservoirManagement) - { - ret = checkYearlyMinGeneration(year, area) && ret; - } - else - { - ret = checkMonthlyMinGeneration(year, area) && ret; - } - }); - return ret; -} - -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( - [this, &year, &scratchmap, &mode](const Data::Area& area) + [this, &year, &scratchmap, &mode, &hydro_specific_map](Data::Area& area) { const auto& scratchpad = scratchmap.at(&area); const auto& rormatrix = area.hydro.series->ror; const auto* ror = rormatrix.getColumn(year); - auto& data = tmpDataByArea_[&area]; + auto& data = area.hydro.managementData[year]; + auto& hydro_specific = hydro_specific_map[&area]; const double* loadSeries = area.load.series.getColumn(year); const double* windSeries = area.wind.series.getColumn(year); const double* solarSeries = area.solar.series.getColumn(year); @@ -449,17 +192,20 @@ void HydroManagement::prepareNetDemand(uint year, Data::SimulationMode mode, assert(!std::isnan(netdemand) && "hydro management: NaN detected when calculating the net demande"); - data.DLN[dayYear] += netdemand; + hydro_specific.daily[dayYear].DLN += netdemand; } }); } -void HydroManagement::prepareEffectiveDemand() +void HydroManagement::prepareEffectiveDemand( + uint year, + HydroSpecificMap& hydro_specific_map) { areas_.each( - [this](Data::Area& area) + [this, &year, &hydro_specific_map](Data::Area& area) { - auto& data = tmpDataByArea_[&area]; + auto& data = area.hydro.managementData[year]; + auto& hydro_specific = hydro_specific_map[&area]; for (uint day = 0; day != 365; ++day) { @@ -470,18 +216,19 @@ void HydroManagement::prepareEffectiveDemand() double effectiveDemand = 0; // area.hydro.allocation is indexed by area index area.hydro.allocation.eachNonNull( - [this, &effectiveDemand, &day](unsigned areaIndex, double value) + [this, &effectiveDemand, &day, &hydro_specific_map](unsigned areaIndex, double value) { const auto* area = areas_.byIndex[areaIndex]; - effectiveDemand += tmpDataByArea_[area].DLN[day] * value; + effectiveDemand += hydro_specific_map[area].daily[day].DLN * value; }); assert(!std::isnan(effectiveDemand) && "nan value detected for effectiveDemand"); - data.DLE[day] += effectiveDemand; - data.MLE[realmonth] += effectiveDemand; + hydro_specific.daily[day].DLE += effectiveDemand; + hydro_specific.monthly[realmonth].MLE += effectiveDemand; - assert(not std::isnan(data.DLE[day]) && "nan value detected for DLE"); - assert(not std::isnan(data.MLE[realmonth]) && "nan value detected for DLE"); + assert(not std::isnan(hydro_specific.daily[day].DLE) && "nan value detected for DLE"); + assert(not std::isnan(hydro_specific.monthly[realmonth].MLE) + && "nan value detected for DLE"); } auto minimumYear = std::numeric_limits::infinity(); @@ -496,9 +243,9 @@ void HydroManagement::prepareEffectiveDemand() for (uint d = 0; d != daysPerMonth; ++d) { auto dYear = d + dayYear; - if (data.DLE[dYear] < minimumMonth) + if (hydro_specific.daily[dYear].DLE < minimumMonth) { - minimumMonth = data.DLE[dYear]; + minimumMonth = hydro_specific.daily[dYear].DLE; } } @@ -506,13 +253,13 @@ void HydroManagement::prepareEffectiveDemand() { for (uint d = 0; d != daysPerMonth; ++d) { - data.DLE[dayYear + d] -= minimumMonth - 1e-4; + hydro_specific.daily[dayYear + d].DLE -= minimumMonth - 1e-4; } } - if (data.MLE[realmonth] < minimumYear) + if (hydro_specific.monthly[realmonth].MLE < minimumYear) { - minimumYear = data.MLE[realmonth]; + minimumYear = hydro_specific.monthly[realmonth].MLE; } dayYear += daysPerMonth; @@ -522,34 +269,22 @@ void HydroManagement::prepareEffectiveDemand() { for (uint realmonth = 0; realmonth != 12; ++realmonth) { - data.MLE[realmonth] -= minimumYear - 1e-4; + hydro_specific.monthly[realmonth].MLE -= minimumYear - 1e-4; } } }); } -bool HydroManagement::checksOnGenerationPowerBounds(uint year) const -{ - return (checkMinGeneration(year) && checkGenerationPowerConsistency(year)) ? true : false; -} - void HydroManagement::makeVentilation(double* randomReservoirLevel, uint y, Antares::Data::Area::ScratchMap& scratchmap) { - prepareInflowsScaling(y); - minGenerationScaling(y); - if (!checksOnGenerationPowerBounds(y)) - { - throw FatalError("hydro management: invalid minimum generation"); - } - - changeInflowsToAccommodateFinalLevels(y); - prepareNetDemand(y, parameters_.mode, scratchmap); - prepareEffectiveDemand(); + HydroSpecificMap hydro_specific_map; + prepareNetDemand(y, parameters_.mode, scratchmap, hydro_specific_map); + prepareEffectiveDemand(y, hydro_specific_map); - prepareMonthlyOptimalGenerations(randomReservoirLevel, y); - prepareDailyOptimalGenerations(y, scratchmap); + prepareMonthlyOptimalGenerations(randomReservoirLevel, y, hydro_specific_map); + prepareDailyOptimalGenerations(y, scratchmap, hydro_specific_map); } } // namespace Antares diff --git a/src/solver/hydro/management/monthly.cpp b/src/solver/hydro/management/monthly.cpp index e12bf2f5a9..6bacef211a 100644 --- a/src/solver/hydro/management/monthly.cpp +++ b/src/solver/hydro/management/monthly.cpp @@ -82,7 +82,10 @@ static void CheckHydroAllocationProblem(Data::Area& area, } } -double HydroManagement::prepareMonthlyTargetGenerations(Data::Area& area, TmpDataByArea& data) +double HydroManagement::prepareMonthlyTargetGenerations( + Data::Area& area, + Antares::Data::AreaDependantHydroManagementData& data, + Antares::Data::TimeDependantHydroManagementData& hydro_specific) { double total = 0; @@ -95,7 +98,7 @@ double HydroManagement::prepareMonthlyTargetGenerations(Data::Area& area, TmpDat { for (uint realmonth = 0; realmonth != MONTHS_PER_YEAR; ++realmonth) { - data.MTG[realmonth] = data.inflows[realmonth]; + hydro_specific.monthly[realmonth].MTG = data.inflows[realmonth]; } return total; @@ -105,9 +108,9 @@ double HydroManagement::prepareMonthlyTargetGenerations(Data::Area& area, TmpDat for (uint realmonth = 0; realmonth != MONTHS_PER_YEAR; ++realmonth) { - if (data.MLE[realmonth] > monthlyMaxDemand) + if (hydro_specific.monthly[realmonth].MLE > monthlyMaxDemand) { - monthlyMaxDemand = data.MLE[realmonth]; + monthlyMaxDemand = hydro_specific.monthly[realmonth].MLE; } } @@ -116,8 +119,8 @@ double HydroManagement::prepareMonthlyTargetGenerations(Data::Area& area, TmpDat double coeff = 0.; for (uint realmonth = 0; realmonth != MONTHS_PER_YEAR; ++realmonth) { - assert(data.MLE[realmonth] / monthlyMaxDemand >= 0.); - coeff += std::pow(data.MLE[realmonth] / monthlyMaxDemand, + assert(hydro_specific.monthly[realmonth].MLE / monthlyMaxDemand >= 0.); + coeff += std::pow(hydro_specific.monthly[realmonth].MLE / monthlyMaxDemand, area.hydro.intermonthlyBreakdown); } @@ -128,10 +131,11 @@ double HydroManagement::prepareMonthlyTargetGenerations(Data::Area& area, TmpDat for (uint realmonth = 0; realmonth != MONTHS_PER_YEAR; ++realmonth) { - assert(data.MLE[realmonth] / monthlyMaxDemand >= 0.); - data.MTG[realmonth] = coeff - * std::pow(data.MLE[realmonth] / monthlyMaxDemand, - area.hydro.intermonthlyBreakdown); + assert(hydro_specific.monthly[realmonth].MLE / monthlyMaxDemand >= 0.); + hydro_specific.monthly[realmonth].MTG = coeff + * std::pow(hydro_specific.monthly[realmonth].MLE + / monthlyMaxDemand, + area.hydro.intermonthlyBreakdown); } } else @@ -140,20 +144,24 @@ double HydroManagement::prepareMonthlyTargetGenerations(Data::Area& area, TmpDat for (uint realmonth = 0; realmonth != MONTHS_PER_YEAR; ++realmonth) { - data.MTG[realmonth] = coeff; + hydro_specific.monthly[realmonth].MTG = coeff; } } return total; } -void HydroManagement::prepareMonthlyOptimalGenerations(double* random_reservoir_level, uint y) +void HydroManagement::prepareMonthlyOptimalGenerations( + double* random_reservoir_level, + uint y, + HydroSpecificMap& hydro_specific_map) { uint indexArea = 0; areas_.each( - [this, &random_reservoir_level, &y, &indexArea](Data::Area& area) + [this, &random_reservoir_level, &y, &indexArea, &hydro_specific_map](Data::Area& area) { - auto& data = tmpDataByArea_[&area]; + auto& data = area.hydro.managementData[y]; + auto& hydro_specific = hydro_specific_map[&area]; auto& minLvl = area.hydro.reservoirLevel[Data::PartHydro::minimum]; auto& maxLvl = area.hydro.reservoirLevel[Data::PartHydro::maximum]; @@ -173,7 +181,7 @@ void HydroManagement::prepareMonthlyOptimalGenerations(double* random_reservoir_ { auto problem = H2O_M_Instanciation(1); - double totalInflowsYear = prepareMonthlyTargetGenerations(area, data); + double totalInflowsYear = prepareMonthlyTargetGenerations(area, data, hydro_specific); assert(totalInflowsYear >= 0.); problem.CoutDepassementVolume = 1e2; @@ -189,7 +197,7 @@ void HydroManagement::prepareMonthlyOptimalGenerations(double* random_reservoir_ problem.TurbineMax[month] = totalInflowsYear; problem.TurbineMin[month] = data.mingens[realmonth]; - problem.TurbineCible[month] = data.MTG[realmonth]; + problem.TurbineCible[month] = hydro_specific.monthly[realmonth].MTG; problem.Apport[month] = data.inflows[realmonth]; problem.VolumeMin[month] = minLvl[firstDay]; problem.VolumeMax[month] = maxLvl[firstDay]; @@ -209,10 +217,11 @@ void HydroManagement::prepareMonthlyOptimalGenerations(double* random_reservoir_ { uint realmonth = (initReservoirLvlMonth + month) % MONTHS_PER_YEAR; - data.MOG[realmonth] = problem.Turbine[month] * area.hydro.reservoirCapacity; - data.MOL[realmonth] = problem.Volume[month]; + hydro_specific.monthly[realmonth].MOG = problem.Turbine[month] + * area.hydro.reservoirCapacity; + hydro_specific.monthly[realmonth].MOL = problem.Volume[month]; } - data.MOL[initReservoirLvlMonth] = lvi; + hydro_specific.monthly[initReservoirLvlMonth].MOL = lvi; solutionCost = problem.ProblemeHydraulique.CoutDeLaSolution; solutionCostNoised = problem.ProblemeHydraulique.CoutDeLaSolutionBruite; @@ -242,18 +251,22 @@ void HydroManagement::prepareMonthlyOptimalGenerations(double* random_reservoir_ for (uint realmonth = 0; realmonth != MONTHS_PER_YEAR; ++realmonth) { - data.MOG[realmonth] = data.inflows[realmonth]; - data.MOL[realmonth] = reservoirLevel[realmonth]; + hydro_specific.monthly[realmonth].MOG = data.inflows[realmonth]; + hydro_specific.monthly[realmonth].MOL = reservoirLevel[realmonth]; } } #ifndef NDEBUG for (uint realmonth = 0; realmonth != MONTHS_PER_YEAR; ++realmonth) { - assert(!std::isnan(data.MOG[realmonth]) && "nan value detected for MOG"); - assert(!std::isnan(data.MOL[realmonth]) && "nan value detected for MOL"); - assert(!std::isinf(data.MOG[realmonth]) && "infinite value detected for MOG"); - assert(!std::isinf(data.MOL[realmonth]) && "infinite value detected for MOL"); + assert(!std::isnan(hydro_specific.monthly[realmonth].MOG) + && "nan value detected for MOG"); + assert(!std::isnan(hydro_specific.monthly[realmonth].MOL) + && "nan value detected for MOL"); + assert(!std::isinf(hydro_specific.monthly[realmonth].MOG) + && "infinite value detected for MOG"); + assert(!std::isinf(hydro_specific.monthly[realmonth].MOL) + && "infinite value detected for MOL"); } #endif if (parameters_.hydroDebug) @@ -278,8 +291,9 @@ void HydroManagement::prepareMonthlyOptimalGenerations(double* random_reservoir_ writeSolutionCost("Solution cost (noised) : ", solutionCostNoised); buffer << "\n\n"; - buffer << '\t' << "\tInflows" << '\t' << "\tTarget Gen." << "\tTurbined" << "\tLevels" - << '\t' << "\tLvl min" << '\t' << "\tLvl max\n"; + buffer << '\t' << "\tInflows" << '\t' << "\tTarget Gen." + << "\tTurbined" + << "\tLevels" << '\t' << "\tLvl min" << '\t' << "\tLvl max\n"; for (uint month = 0; month != MONTHS_PER_YEAR; ++month) { uint realmonth = (initReservoirLvlMonth + month) % MONTHS_PER_YEAR; @@ -290,22 +304,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); - } - - indexArea++; - }); + buffer << monthName[0] << monthName[1] << monthName[2] << '\t'; + buffer << '\t'; + buffer << data.inflows[realmonth] << '\t'; + buffer << hydro_specific.monthly[realmonth].MTG << '\t'; + buffer << hydro_specific.monthly[realmonth].MOG / area.hydro.reservoirCapacity + << '\t'; + buffer << hydro_specific.monthly[realmonth].MOL << '\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 6a5355ea56..fbfeb17f6a 100644 --- a/src/solver/simulation/CMakeLists.txt +++ b/src/solver/simulation/CMakeLists.txt @@ -44,8 +44,6 @@ set(SRC_SIMULATION 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 include/antares/solver/simulation/ISimulationObserver.h diff --git a/src/solver/simulation/hydro-final-reservoir-level-functions.cpp b/src/solver/simulation/hydro-final-reservoir-level-functions.cpp deleted file mode 100644 index 32f2d4dfa2..0000000000 --- a/src/solver/simulation/hydro-final-reservoir-level-functions.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/* -** 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/solver.h b/src/solver/simulation/include/antares/solver/simulation/solver.h index cd2abfa366..6dc6f85eb8 100644 --- a/src/solver/simulation/include/antares/solver/simulation/solver.h +++ b/src/solver/simulation/include/antares/solver/simulation/solver.h @@ -127,7 +127,7 @@ class ISimulation: public Impl ** Storing these costs to compute std deviation later. */ void computeAnnualCostsStatistics(std::vector& state, - std::vector::iterator& set_it); + setOfParallelYears& batch); /*! ** \brief Iterate through all MC years diff --git a/src/solver/simulation/include/antares/solver/simulation/solver.hxx b/src/solver/simulation/include/antares/solver/simulation/solver.hxx index b9ae7f7678..6c0ab57525 100644 --- a/src/solver/simulation/include/antares/solver/simulation/solver.hxx +++ b/src/solver/simulation/include/antares/solver/simulation/solver.hxx @@ -32,14 +32,13 @@ #include #include "antares/concurrency/concurrency.h" #include "antares/solver/variable/print.h" -#include "antares/solver/hydro/management/management.h" // Added for use of randomReservoirLevel(...) -#include "antares/solver/simulation/apply-scenario.h" +#include "antares/solver/hydro/management/HydroInputsChecker.h" +#include "antares/solver/hydro/management/management.h" #include "antares/solver/simulation/opt_time_writer.h" #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 { @@ -306,8 +305,6 @@ void ISimulation::run() ImplementationType::setNbPerformedYearsInParallel(pNbMaxPerformedYearsInParallel); - TSGenerator::ResizeGeneratedTimeSeries(study.areas, study.parameters); - if (settings.tsGeneratorsOnly) { // Only the preprocessors can be used @@ -333,29 +330,6 @@ void ISimulation::run() // For beauty logs.info(); - // Sampled time-series Numbers - // We will resize all matrix related to the time-series numbers - // This operation can be done once since the number of years is constant - // for a single simulation - study.resizeAllTimeseriesNumbers(1 + study.runtime->rangeLimits.year[Data::rangeEnd]); - // Now, we will prepare the time-series numbers - if (not TimeSeriesNumbers::CheckNumberOfColumns(study.areas)) - { - throw FatalError( - "Inconsistent number of time-series detected. Please check your input data."); - } - - if (not TimeSeriesNumbers::Generate(study)) - { - throw FatalError("An unrecoverable error has occured. Can not continue."); - } - - if (study.parameters.useCustomScenario) - { - ApplyCustomScenario(study); - CheckFinalReservoirLevelsConfiguration(study); - } - // Launching the simulation for all years logs.info() << "MC-Years : [" << (study.runtime->rangeLimits.year[Data::rangeBegin] + 1) << " .. " << (1 + study.runtime->rangeLimits.year[Data::rangeEnd]) @@ -369,7 +343,6 @@ void ISimulation::run() ImplementationType::initializeState(state[numSpace], numSpace); } - logs.info() << " Starting the simulation"; uint finalYear = 1 + study.runtime->rangeLimits.year[Data::rangeEnd]; { pDurationCollector("mc_years") @@ -914,18 +887,15 @@ void ISimulation::computeRandomNumbers( template void ISimulation::computeAnnualCostsStatistics( std::vector& state, - std::vector::iterator& set_it) + setOfParallelYears& batch) { // Loop over years contained in the set - std::vector::iterator year_it; - for (year_it = set_it->yearsIndices.begin(); year_it != set_it->yearsIndices.end(); ++year_it) + for (auto y: batch.yearsIndices) { - // Get the index of the year - unsigned int y = *year_it; - if (set_it->isYearPerformed[y]) + if (batch.isYearPerformed[y]) { // Get space number associated to the performed year - uint numSpace = set_it->performedYearToSpace[y]; + uint numSpace = batch.performedYearToSpace[y]; const Variable::State& s = state[numSpace]; pAnnualStatistics.systemCost.addCost(s.annualSystemCost); pAnnualStatistics.criterionCost1.addCost(s.optimalSolutionCost1); @@ -989,66 +959,78 @@ void ISimulation::loopThroughYears(uint firstYear, // Number of threads to perform the jobs waiting in the queue pQueueService->maximumThreadCount(pNbMaxPerformedYearsInParallel); + HydroInputsChecker hydroInputsChecker(study); + + logs.info() << " Doing hydro validation"; - // Loop over sets of parallel years - std::vector::iterator set_it; - for (set_it = setsOfParallelYears.begin(); set_it != setsOfParallelYears.end(); ++set_it) + // Loop over sets of parallel years to check hydro inputs + for (const auto& batch: setsOfParallelYears) + { + if (batch.regenerateTS) + { + break; + } + for (auto year: batch.yearsIndices) + { + hydroInputsChecker.Execute(year); + } + } + + logs.info() << " Starting the simulation"; + + // Loop over sets of parallel years to run the simulation + for (auto& batch: setsOfParallelYears) { // 1 - We may want to regenerate the time-series this year. // This is the case when the preprocessors are enabled from the // interface and/or the refresh is enabled. - if (set_it->regenerateTS) + if (batch.regenerateTS) { - regenerateTimeSeries(set_it->yearForTSgeneration); + regenerateTimeSeries(batch.yearForTSgeneration); } - computeRandomNumbers(randomForParallelYears, - set_it->yearsIndices, - set_it->isYearPerformed, + batch.yearsIndices, + batch.isYearPerformed, randomHydroGenerator); - std::vector::iterator year_it; - bool yearPerformed = false; Concurrency::FutureSet results; - for (year_it = set_it->yearsIndices.begin(); year_it != set_it->yearsIndices.end(); - ++year_it) + for (auto y: batch.yearsIndices) { - // Get the index of the year - unsigned int y = *year_it; - - bool performCalculations = set_it->isYearPerformed[y]; + // for each year not handled earlier + hydroInputsChecker.Execute(y); + bool performCalculations = batch.isYearPerformed[y]; unsigned int numSpace = 999999; if (performCalculations) { yearPerformed = true; - numSpace = set_it->performedYearToSpace[y]; + numSpace = batch.performedYearToSpace[y]; } // If the year has not to be rerun, we skip the computation of the year. // Note that, when we enter for the first time in the "for" loop, all years of the set - // have to be rerun (meaning : they must be run once). if(!set_it->yearFailed[y]) + // have to be rerun (meaning : they must be run once). if(!batch.yearFailed[y]) // continue; auto task = std::make_shared>( - this, - y, - set_it->yearFailed, - set_it->isFirstPerformedYearOfASet, - pFirstSetParallelWithAPerformedYearWasRun, - numSpace, - randomForParallelYears, - performCalculations, - study, - state[numSpace], - pYearByYear, - pDurationCollector, + this, + y, + batch.yearFailed, + batch.isFirstPerformedYearOfASet, + pFirstSetParallelWithAPerformedYearWasRun, + numSpace, + randomForParallelYears, + performCalculations, + study, + state[numSpace], + pYearByYear, + pDurationCollector, pResultWriter, simulationObserver_.get()); results.add(Concurrency::AddTask(*pQueueService, task)); } // End loop over years of the current set of parallel years - logPerformedYearsInAset(*set_it); + logPerformedYearsInAset(batch); pQueueService->start(); @@ -1057,14 +1039,15 @@ void ISimulation::loopThroughYears(uint firstYear, results.join(); pResultWriter.flush(); - // At this point, the first set of parallel year(s) was run with at least one year performed + // At this point, the first set of parallel year(s) was run with at least one year + // performed if (!pFirstSetParallelWithAPerformedYearWasRun && yearPerformed) { pFirstSetParallelWithAPerformedYearWasRun = true; } // On regarde si au moins une année du lot n'a pas trouvé de solution - for (auto& [year, failed]: set_it->yearFailed) + for (auto& [year, failed]: batch.yearFailed) { // Si une année du lot d'années n'a pas trouvé de solution, on arrête tout if (failed) @@ -1076,17 +1059,17 @@ void ISimulation::loopThroughYears(uint firstYear, } // Computing the summary : adding the contribution of MC years // previously computed in parallel - ImplementationType::variables.computeSummary(set_it->spaceToPerformedYear, - set_it->nbPerformedYears); + ImplementationType::variables.computeSummary(batch.spaceToPerformedYear, + batch.nbPerformedYears); // Computing summary of spatial aggregations ImplementationType::variables.computeSpatialAggregatesSummary(ImplementationType::variables, - set_it->spaceToPerformedYear, - set_it->nbPerformedYears); + batch.spaceToPerformedYear, + batch.nbPerformedYears); // Computes statistics on annual (system and solution) costs, to be printed in output into // separate files - computeAnnualCostsStatistics(state, set_it); + computeAnnualCostsStatistics(state, batch); // Set to zero the random numbers of all parallel years randomForParallelYears.reset(); diff --git a/src/solver/ts-generator/availability.cpp b/src/solver/ts-generator/availability.cpp index 69f62a10a1..696725f846 100644 --- a/src/solver/ts-generator/availability.cpp +++ b/src/solver/ts-generator/availability.cpp @@ -28,6 +28,7 @@ #include #include #include +#include // For Antares::IO::fileSetContent #include "antares/study/simulation.h" #define SEP Yuni::IO::Separator @@ -36,21 +37,21 @@ constexpr double FAILURE_RATE_EQ_1 = 0.999; namespace Antares::TSGenerator { -AvailabilityTSGeneratorData::AvailabilityTSGeneratorData(Data::ThermalCluster* source): - unitCount(source->unitCount), - nominalCapacity(source->nominalCapacity), - forcedVolatility(source->forcedVolatility), - plannedVolatility(source->plannedVolatility), - forcedLaw(source->forcedLaw), - plannedLaw(source->plannedLaw), - prepro(source->prepro), - series(source->series.timeSeries), - modulationCapacity(source->modulation[Data::thermalModulationCapacity]), - name(source->name()) +AvailabilityTSGeneratorData::AvailabilityTSGeneratorData(Data::ThermalCluster* cluster): + unitCount(cluster->unitCount), + nominalCapacity(cluster->nominalCapacity), + forcedVolatility(cluster->forcedVolatility), + plannedVolatility(cluster->plannedVolatility), + forcedLaw(cluster->forcedLaw), + plannedLaw(cluster->plannedLaw), + prepro(cluster->prepro), + series(cluster->series.timeSeries), + modulationCapacity(cluster->modulation[Data::thermalModulationCapacity]), + name(cluster->name()) { } -AvailabilityTSGeneratorData::AvailabilityTSGeneratorData(Data::LinkTsGeneration& source, +AvailabilityTSGeneratorData::AvailabilityTSGeneratorData(LinkTSgenerationParams& source, Data::TimeSeries& capacity, Matrix<>& modulation, const std::string& areaDestName): @@ -73,8 +74,9 @@ class GeneratorTempData final { public: explicit GeneratorTempData(Data::Study&, unsigned, MersenneTwister&); + explicit GeneratorTempData(bool, unsigned, MersenneTwister&); - void generateTS(const Data::Area& area, AvailabilityTSGeneratorData& cluster) const; + void generateTS(AvailabilityTSGeneratorData&) const; private: bool derated; @@ -106,6 +108,13 @@ GeneratorTempData::GeneratorTempData(Data::Study& study, unsigned nbOfSeriesToGe { } +GeneratorTempData::GeneratorTempData(bool derated, unsigned int nbOfSeriesToGen, MersenneTwister& rndGenerator): + derated(derated), + nbOfSeriesToGen_(nbOfSeriesToGen), + rndgenerator(rndGenerator) +{ +} + template void GeneratorTempData::prepareIndispoFromLaw(Data::StatisticalLaw law, double volatility, @@ -184,28 +193,18 @@ int GeneratorTempData::durationGenerator(Data::StatisticalLaw law, return 0; } -void GeneratorTempData::generateTS(const Data::Area& area, - AvailabilityTSGeneratorData& cluster) const +void GeneratorTempData::generateTS(AvailabilityTSGeneratorData& tsGenerationData) const { - if (!cluster.prepro) - { - logs.error() - << "Cluster: " << area.name << '/' << cluster.name - << ": The timeseries will not be regenerated. All data related to the ts-generator for " - << "'thermal' have been released."; - return; - } + assert(tsGenerationData.prepro); - assert(cluster.prepro); - - if (0 == cluster.unitCount || 0 == cluster.nominalCapacity) + if (0 == tsGenerationData.unitCount || 0 == tsGenerationData.nominalCapacity) { return; } - const auto& preproData = *(cluster.prepro); + const auto& preproData = *(tsGenerationData.prepro); - int AUN = cluster.unitCount; + int AUN = tsGenerationData.unitCount; auto& FOD = preproData.data[Data::PreproAvailability::foDuration]; @@ -219,13 +218,13 @@ void GeneratorTempData::generateTS(const Data::Area& area, auto& NPOmax = preproData.data[Data::PreproAvailability::npoMax]; - double f_volatility = cluster.forcedVolatility; + double f_volatility = tsGenerationData.forcedVolatility; - double p_volatility = cluster.plannedVolatility; + double p_volatility = tsGenerationData.plannedVolatility; - auto f_law = cluster.forcedLaw; + auto f_law = tsGenerationData.forcedLaw; - auto p_law = cluster.plannedLaw; + auto p_law = tsGenerationData.plannedLaw; std::vector> FPOW(DAYS_PER_YEAR); std::vector> PPOW(DAYS_PER_YEAR); @@ -247,8 +246,8 @@ void GeneratorTempData::generateTS(const Data::Area& area, for (uint d = 0; d < DAYS_PER_YEAR; ++d) { - FPOW[d].resize(cluster.unitCount + 1); - PPOW[d].resize(cluster.unitCount + 1); + FPOW[d].resize(tsGenerationData.unitCount + 1); + PPOW[d].resize(tsGenerationData.unitCount + 1); PODOfTheDay = (int)POD[d]; FODOfTheDay = (int)FOD[d]; @@ -281,7 +280,7 @@ void GeneratorTempData::generateTS(const Data::Area& area, pp[d] = lp[d] / b; } - for (uint k = 0; k != cluster.unitCount + 1; ++k) + for (uint k = 0; k != tsGenerationData.unitCount + 1; ++k) { FPOW[d][k] = pow(a, (double)k); PPOW[d][k] = pow(b, (double)k); @@ -313,7 +312,7 @@ void GeneratorTempData::generateTS(const Data::Area& area, double cumul = 0; double last = 0; - auto& modulation = cluster.modulationCapacity; + auto& modulation = tsGenerationData.modulationCapacity; double* dstSeries = nullptr; const uint tsCount = nbOfSeriesToGen_ + 2; @@ -323,7 +322,7 @@ void GeneratorTempData::generateTS(const Data::Area& area, if (tsIndex > 1) { - dstSeries = cluster.series[tsIndex - 2]; + dstSeries = tsGenerationData.series[tsIndex - 2]; } for (uint dayInTheYear = 0; dayInTheYear < DAYS_PER_YEAR; ++dayInTheYear) @@ -498,7 +497,7 @@ void GeneratorTempData::generateTS(const Data::Area& area, } } - if (cluster.unitCount == 1) + if (tsGenerationData.unitCount == 1) { if (POC == 1 && FOC == 1) { @@ -568,7 +567,7 @@ void GeneratorTempData::generateTS(const Data::Area& area, } NOW = (NOW + 1) % Log_size; - AVP[dayInTheYear] = AUN * cluster.nominalCapacity; + AVP[dayInTheYear] = AUN * tsGenerationData.nominalCapacity; if (tsIndex > 1) { @@ -584,7 +583,7 @@ void GeneratorTempData::generateTS(const Data::Area& area, if (derated) { - cluster.series.averageTimeseries(); + tsGenerationData.series.averageTimeseries(); } } } // namespace @@ -609,23 +608,6 @@ 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, @@ -641,6 +623,20 @@ void writeResultsToDisk(const Data::Study& study, writer.addEntryFromBuffer(savePath, buffer); } +void writeResultsToDisk(const Matrix<>& series, + const std::filesystem::path savePath) +{ + std::string buffer; + series.saveToBuffer(buffer, 0); + + std::filesystem::path parentDir = savePath.parent_path(); + if (! std::filesystem::exists(parentDir)) + { + std::filesystem::create_directories(parentDir); + } + Antares::IO::fileSetContent(savePath.string(), buffer); +} + bool generateThermalTimeSeries(Data::Study& study, const std::vector& clusters, Solver::IResultWriter& writer, @@ -655,13 +651,12 @@ bool generateThermalTimeSeries(Data::Study& study, study.parameters.nbTimeSeriesThermal, study.runtime->random[Data::seedTsGenThermal]); - // TODO VP: parallel for (auto* cluster: clusters) { - AvailabilityTSGeneratorData tsConfigData(cluster); - generator.generateTS(*cluster->parentArea, tsConfigData); + AvailabilityTSGeneratorData tsGenerationData(cluster); + generator.generateTS(tsGenerationData); - if (archive) // compatibilty with in memory + if (archive) // For compatibilty with in memory thermal TS generation { std::string filePath = savePath + SEP + cluster->parentArea->id + SEP + cluster->id() + ".txt"; @@ -672,48 +667,49 @@ bool generateThermalTimeSeries(Data::Study& study, return true; } -bool generateLinkTimeSeries(Data::Study& study, - const listOfLinks& links, - Solver::IResultWriter& writer, +// gp : we should try to add const identifiers before args here +bool generateLinkTimeSeries(std::vector& links, + StudyParamsForLinkTS& generalParams, 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]); + logs.info() << "Generation of links time-series"; - for (const auto& link: links) + auto generator = GeneratorTempData(generalParams.derated, + generalParams.nbLinkTStoGenerate, + generalParams.random); + for (auto& link: links) { - Data::TimeSeries ts(link->timeseriesNumbers); - ts.resize(study.parameters.nbLinkTStoGenerate, HOURS_PER_YEAR); - - auto& tsGenStruct = link->tsGeneration; - - if (!tsGenStruct.valid) + if (! link.hasValidData) { - logs.error() << "Missing data for link " << link->from->id << "/" << link->with->id; + logs.error() << "Missing data for link " << link.namesPair.first << "/" << link.namesPair.second; return false; } + if (link.forceNoGeneration) + continue; // Skipping the link + + Data::TimeSeriesNumbers fakeTSnumbers; // gp : to quickly get rid of + Data::TimeSeries ts(fakeTSnumbers); + ts.resize(generalParams.nbLinkTStoGenerate, HOURS_PER_YEAR); + // DIRECT - AvailabilityTSGeneratorData tsConfigDataDirect(tsGenStruct, ts, tsGenStruct.modulationCapacityDirect, link->with->name); + AvailabilityTSGeneratorData tsConfigDataDirect(link, ts, link.modulationCapacityDirect, link.namesPair.second); - generator.generateTS(*link->from, tsConfigDataDirect); + generator.generateTS(tsConfigDataDirect); - std::string filePath = savePath + SEP + link->from->id + SEP + link->with->id.c_str() + std::string filePath = savePath + SEP + link.namesPair.first + SEP + link.namesPair.second + "_direct.txt"; - writeResultsToDisk(study, writer, ts.timeSeries, filePath); + writeResultsToDisk(ts.timeSeries, filePath); // INDIRECT - AvailabilityTSGeneratorData tsConfigDataIndirect(tsGenStruct, ts, tsGenStruct.modulationCapacityIndirect, link->with->name); + AvailabilityTSGeneratorData tsConfigDataIndirect(link, ts, link.modulationCapacityIndirect, link.namesPair.second); - generator.generateTS(*link->from, tsConfigDataIndirect); + generator.generateTS(tsConfigDataIndirect); - filePath = savePath + SEP + link->from->id + SEP + link->with->id.c_str() + filePath = savePath + SEP + link.namesPair.first + SEP + link.namesPair.second + "_indirect.txt"; - writeResultsToDisk(study, writer, ts.timeSeries, filePath); + writeResultsToDisk(ts.timeSeries, filePath); } return true; 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 1c32e67bdd..8cddc50679 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 @@ -33,13 +33,50 @@ #include "xcast/xcast.h" +using LinkPair = std::pair; +using LinkPairs = std::vector; + namespace Antares::TSGenerator { + +struct StudyParamsForLinkTS +{ + unsigned int nbLinkTStoGenerate = 1; + bool derated = false; + // gp : we will have a problem with that if seed-tsgen-links not set in + // gp : generaldata.ini. In that case, our default value is wrong. + MersenneTwister random; +}; + +struct LinkTSgenerationParams +{ + LinkPair namesPair; + + unsigned unitCount = 0; + double nominalCapacity = 0; + + double forcedVolatility = 0.; + double plannedVolatility = 0.; + + Data::StatisticalLaw forcedLaw = Data::LawUniform; + Data::StatisticalLaw plannedLaw = Data::LawUniform; + + std::unique_ptr prepro; + + Matrix<> modulationCapacityDirect; + Matrix<> modulationCapacityIndirect; + + bool forceNoGeneration = false; + bool hasValidData = true; +}; + + class AvailabilityTSGeneratorData { public: explicit AvailabilityTSGeneratorData(Data::ThermalCluster*); - AvailabilityTSGeneratorData(Data::LinkTsGeneration&, + + AvailabilityTSGeneratorData(LinkTSgenerationParams&, Data::TimeSeries&, Matrix<>& modulation, const std::string& name); @@ -77,15 +114,13 @@ bool generateThermalTimeSeries(Data::Study& study, Solver::IResultWriter& writer, const std::string& savePath); -bool generateLinkTimeSeries(Data::Study& study, - const listOfLinks& links, - Solver::IResultWriter& writer, +bool generateLinkTimeSeries(std::vector& links, + StudyParamsForLinkTS&, 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 ac36a826ea..4d93f56f8a 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 @@ -114,28 +114,6 @@ class PreproAvailability YString id; 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/tests/end-to-end/simple_study/simple-study.cpp b/src/tests/end-to-end/simple_study/simple-study.cpp index 7992165af2..1def8eded6 100644 --- a/src/tests/end-to-end/simple_study/simple-study.cpp +++ b/src/tests/end-to-end/simple_study/simple-study.cpp @@ -107,7 +107,6 @@ BOOST_AUTO_TEST_SUITE(ONE_AREA__ONE_THERMAL_CLUSTER) BOOST_FIXTURE_TEST_CASE(thermal_cluster_fullfills_area_demand, StudyFixture) { setNumberMCyears(1); - simulation->create(); simulation->run(); @@ -283,13 +282,13 @@ BOOST_FIXTURE_TEST_CASE(error_on_wrong_hydro_data, StudyFixture) { StudyBuilder builder; builder.simulationBetweenDays(0, 7); - builder.setNumberMCyears(1); Area& area = *builder.addAreaToStudy("A"); PartHydro& hydro = area.hydro; TimeSeriesConfigurer(hydro.series->storage.timeSeries) .setColumnCount(1) .fillColumnWith(0, -1.0); // Negative inflow will cause a consistency error with mingen + builder.setNumberMCyears(1); auto simulation = builder.simulation; simulation->create(); BOOST_CHECK_THROW(simulation->run(), Antares::FatalError); diff --git a/src/tests/inmemory-study/CMakeLists.txt b/src/tests/inmemory-study/CMakeLists.txt index 36c757f717..bb7cd26e13 100644 --- a/src/tests/inmemory-study/CMakeLists.txt +++ b/src/tests/inmemory-study/CMakeLists.txt @@ -11,6 +11,7 @@ target_link_libraries(in-memory-study PUBLIC ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} antares-solver-simulation + Antares::application PRIVATE Antares::infoCollection ) diff --git a/src/tests/inmemory-study/in-memory-study.cpp b/src/tests/inmemory-study/in-memory-study.cpp index 77b984fb45..4b4e65c100 100644 --- a/src/tests/inmemory-study/in-memory-study.cpp +++ b/src/tests/inmemory-study/in-memory-study.cpp @@ -19,8 +19,9 @@ * along with Antares_Simulator. If not, see . */ #define WIN32_LEAN_AND_MEAN -#include "in-memory-study.h" +#include "antares/application/ScenarioBuilderOwner.h" +#include "in-memory-study.h" void initializeStudy(Study* study) { @@ -193,6 +194,8 @@ void SimulationHandler::create() durationCollector_, resultWriter_, observer_); + Antares::Solver::ScenarioBuilderOwner(study_).callScenarioBuilder(); + SIM_AllocationTableaux(study_); } diff --git a/src/tests/src/solver/simulation/CMakeLists.txt b/src/tests/src/solver/simulation/CMakeLists.txt index 3ba8f09755..294005454b 100644 --- a/src/tests/src/solver/simulation/CMakeLists.txt +++ b/src/tests/src/solver/simulation/CMakeLists.txt @@ -1,5 +1,6 @@ # Useful variables definitions set(src_solver_simulation "${CMAKE_SOURCE_DIR}/solver/simulation") +set(src_solver_hydro "${CMAKE_SOURCE_DIR}/solver/hydro") set(src_libs_antares_study "${CMAKE_SOURCE_DIR}/libs/antares/study") set(SRC_TS_NUMBERS @@ -99,6 +100,7 @@ target_include_directories(test-hydro_final PRIVATE "${src_solver_simulation}" "${src_libs_antares_study}" + "${src_solver_hydro}" ) target_link_libraries(test-hydro_final PRIVATE 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 index d4a6c3512b..70e4b62357 100644 --- 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 @@ -6,10 +6,11 @@ #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 +#include "include/antares/solver/hydro/management/hydro-final-reservoir-level-functions.h" +#include "include/antares/study/parts/hydro/finalLevelValidator.h" + using namespace Antares::Solver; using namespace Antares::Data; @@ -257,7 +258,16 @@ BOOST_AUTO_TEST_CASE(final_level_unreachable_because_of_too_few_inflows___check_ BOOST_AUTO_TEST_CASE(check_all_areas_final_levels_when_config_is_ok___all_checks_succeed) { - CheckFinalReservoirLevelsConfiguration(*study); + for (uint year : {0, 1}) + { + CheckFinalReservoirLevelsConfiguration(study->areas, + study->parameters, + study->scenarioInitialHydroLevels, + study->scenarioFinalHydroLevels, + year); + } + // CheckFinalReservoirLevelsConfiguration(*study, 0); + // CheckFinalReservoirLevelsConfiguration(*study, 1); // Checks on Area 1 modifier BOOST_CHECK_EQUAL(area_1->hydro.deltaBetweenFinalAndInitialLevels[0].has_value(), true); @@ -272,4 +282,4 @@ BOOST_AUTO_TEST_CASE(check_all_areas_final_levels_when_config_is_ok___all_checks BOOST_CHECK_EQUAL(area_2->hydro.deltaBetweenFinalAndInitialLevels[1].value(), 4.3 - 2.4); } -BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/tools/ts-generator/main.cpp b/src/tools/ts-generator/main.cpp index 8f23f60619..60ad9dd720 100644 --- a/src/tools/ts-generator/main.cpp +++ b/src/tools/ts-generator/main.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -36,6 +37,7 @@ #include using namespace Antares; +using namespace Antares::TSGenerator; namespace fs = std::filesystem; @@ -111,28 +113,311 @@ std::vector getClustersToGen(Data::AreaList& areas, return clusters; } -TSGenerator::listOfLinks getLinksToGen(Data::AreaList& areas, const std::string& linksToGen) +// ===== New code for TS generation links ==================================== + +std::vector extractTargetAreas(fs::path sourceLinkDir) +{ + std::vector to_return; + fs::path pathToIni = sourceLinkDir / "properties.ini"; + IniFile ini; + ini.open(pathToIni); // gp : we should handle reading issues + for (auto* s = ini.firstSection; s; s = s->next) + { + std::string targetAreaName = transformNameIntoID(s->name); + to_return.push_back(targetAreaName); + } + return to_return; +} + +LinkPairs extractLinkNamesFromStudy(fs::path studyDir) +{ + LinkPairs to_return; + fs::path linksDir = studyDir / "input" / "links"; + for (auto const& item : fs::directory_iterator{linksDir}) + { + if (item.is_directory()) + { + std::string sourceAreaName = item.path().filename().generic_string(); + auto targetAreas = extractTargetAreas(item); + for (auto& targetAreaName : targetAreas) + { + auto linkPair = std::make_pair(sourceAreaName, targetAreaName); + to_return.push_back(linkPair); + } + } + } + return to_return; +} + +bool pairs_match(const LinkPair& p1, const LinkPair& p2) { - TSGenerator::listOfLinks links; - const auto ids = splitStringIntoPairs(linksToGen, ';', '.'); + return (p1.first == p2.first && p1.second == p2.second) + || (p1.first == p2.second && p1.second == p2.first); +} - for (const auto& [areaFromID, areaWithID]: ids) +const LinkPair* getMatchingPairInCollection(const LinkPair& pair, const LinkPairs& collection) +{ + for(const auto& p : collection) { - logs.info() << "Searching for link: " << areaFromID << "/" << areaWithID; + if (pairs_match(pair, p)) + return &p; + } + return nullptr; +} - auto* link = areas.findLink(areaFromID, areaWithID); - if (!link) +LinkPairs extractLinkNamesFromCmdLine(const LinkPairs& allLinks, + const std::string linksFromCmdLine) +{ + LinkPairs to_return; + LinkPairs pairsFromCmdLine = splitStringIntoPairs(linksFromCmdLine, ';', '.'); + for (auto& p : pairsFromCmdLine) + { + if (const auto* found_pair = getMatchingPairInCollection(p, allLinks); found_pair) + { + to_return.push_back(*found_pair); + } + else { - logs.warning() << "Link not found: " << areaFromID << "/" << areaWithID; + logs.error() << "Link '" << p.first << "." << p.second << "' not found"; + } + } + return to_return; +} + +bool readLinkGeneralProperty(StudyParamsForLinkTS& params, + const Yuni::String& key, + const Yuni::String& value) +{ + if (key == "derated") + { + return value.to(params.derated); + } + if (key == "nbtimeserieslinks") + { + return value.to(params.nbLinkTStoGenerate); + } + if (key == "seed-tsgen-links") + { + unsigned int seed {0}; + if (! value.to(seed)) + return false; + params.random.reset(seed); + return true; + } + return true; // gp : should we return true here ? +} + +StudyParamsForLinkTS readGeneralParamsForLinksTS(fs::path studyDir) +{ + StudyParamsForLinkTS to_return; + fs::path pathToGeneraldata = studyDir / "settings" / "generaldata.ini"; + IniFile ini; + ini.open(pathToGeneraldata); // gp : we should handle reading issues + for (auto* section = ini.firstSection; section; section = section->next) + { + // Skipping sections useless in the current context + Yuni::String sectionName = section->name; + if (sectionName != "general" && sectionName != "seeds - Mersenne Twister") continue; + + for (const IniFile::Property* p = section->firstProperty; p; p = p->next) + { + if (! readLinkGeneralProperty(to_return, p->key, p->value)) + { + logs.warning() << ini.filename() << ": reading value of '" + << p->key << "' went wrong"; + } } + } + return to_return; +} - links.emplace_back(link); +std::vector CreateLinkList(const LinkPairs& linksFromCmdLine) +{ + std::vector to_return; + to_return.reserve(linksFromCmdLine.size()); + for (const auto& link_pair : linksFromCmdLine) + { + LinkTSgenerationParams params; + params.namesPair = link_pair; + to_return.push_back(std::move(params)); } + return to_return; +} - return links; +LinkTSgenerationParams* findLinkInList(const LinkPair& link_to_find, + std::vector& linkList) +{ + for(auto& link : linkList) + { + if (link.namesPair == link_to_find) + return &link; + } + return nullptr; } +bool readLinkIniProperty(LinkTSgenerationParams* link, + const Yuni::String& key, + const Yuni::String& value) +{ + if (key == "unitcount") + { + return value.to(link->unitCount); + } + + if (key == "nominalcapacity") + { + return value.to(link->nominalCapacity); + } + + if (key == "law.planned") + { + return value.to(link->plannedLaw); + } + + if (key == "law.forced") + { + return value.to(link->forcedLaw); + } + + if (key == "volatility.planned") + { + return value.to(link->plannedVolatility); + } + + if (key == "volatility.forced") + { + return value.to(link->forcedVolatility); + } + + if (key == "force-no-generation") + { + return value.to(link->forceNoGeneration); + } + return true; +} + +void readLinkIniProperties(LinkTSgenerationParams* link, + IniFile::Section* section) +{ + for (const IniFile::Property* p = section->firstProperty; p; p = p->next) + { + if (! readLinkIniProperty(link, p->key, p->value)) + { + std::string linkName = link->namesPair.first + "." + link->namesPair.second; + logs.warning() << "Link '" << linkName << "' : reading value of '" + << p->key << "' went wrong"; + link->hasValidData = false; + } + } +} + +void readSourceAreaIniFile(fs::path pathToIni, + std::string sourceAreaName, + std::vector& linkList) +{ + IniFile ini; + ini.open(pathToIni); // gp : we should handle reading issues + for (auto* section = ini.firstSection; section; section = section->next) + { + std::string targetAreaName = transformNameIntoID(section->name); + const LinkPair processedLink = std::make_pair(sourceAreaName, targetAreaName); + if (auto* foundLink = findLinkInList(processedLink, linkList); foundLink) + { + readLinkIniProperties(foundLink, section); + } + } +} + +void readIniProperties(std::vector& linkList, fs::path toLinksDir) +{ + for(auto& link : linkList) + { + std::string sourceAreaName = link.namesPair.first; + fs::path pathToIni = toLinksDir / sourceAreaName / "properties.ini"; + readSourceAreaIniFile(pathToIni, sourceAreaName, linkList); + } +} + +bool readLinkPreproTimeSeries(LinkTSgenerationParams& link, + fs::path sourceAreaDir) +{ + bool to_return = true; + const auto preproId = link.namesPair.first + "/" + link.namesPair.second; + link.prepro = std::make_unique(preproId, link.unitCount); + + auto preproFileRoot = sourceAreaDir / "prepro" / link.namesPair.second; + + auto preproFile = preproFileRoot; + preproFile += ".txt"; + if (fs::exists(preproFile)) + { + to_return = link.prepro->data.loadFromCSVFile( + preproFile.string(), + Data::PreproAvailability::preproAvailabilityMax, + DAYS_PER_YEAR) + && link.prepro->validate() + && to_return; + } + + auto modulationFileDirect = preproFileRoot; + modulationFileDirect += "_mod_direct.txt"; + if (fs::exists(modulationFileDirect)) + { + to_return = link.modulationCapacityDirect.loadFromCSVFile( + modulationFileDirect.string(), + 1, + HOURS_PER_YEAR) + && to_return; + } + + auto modulationFileIndirect = preproFileRoot; + modulationFileIndirect += "_mod_indirect.txt"; + if (fs::exists(modulationFileIndirect)) + { + to_return = link.modulationCapacityIndirect.loadFromCSVFile( + modulationFileIndirect.string(), + 1, + HOURS_PER_YEAR) + && to_return; + } + // Makes possible a skip of TS generation when time comes + link.hasValidData = link.hasValidData && to_return; + return to_return; +} + +void readPreproTimeSeries(std::vector& linkList, + fs::path toLinksDir) +{ + for(auto& link : linkList) + { + std::string sourceAreaName = link.namesPair.first; + fs::path sourceAreaDir = toLinksDir / sourceAreaName; + if (! readLinkPreproTimeSeries(link, sourceAreaDir)) + { + logs.warning() << "Could not load all prepro data for link '" + << link.namesPair.first << "." << link.namesPair.second << "'"; + } + } +} + +void readLinksSpecificTSparameters(std::vector& linkList, + fs::path studyFolder) +{ + fs::path toLinksDir = studyFolder / "input" / "links"; + readIniProperties(linkList, toLinksDir); + readPreproTimeSeries(linkList, toLinksDir); +} + +std::string DateAndTime() +{ + YString to_return; + unsigned int now = Yuni::DateTime::Now(); + Yuni::DateTime::TimestampToString(to_return, "%Y%m%d-%H%M", now); + return to_return.to(); +} +// ============================================================================ + int main(int argc, char* argv[]) { logs.applicationName("ts-generator"); @@ -168,7 +453,6 @@ int main(int argc, char* argv[]) auto study = std::make_shared(true); Data::StudyLoadOptions studyOptions; studyOptions.prepareOutput = true; - studyOptions.linksLoadTSGen = true; if (!study->loadFromFolder(settings.studyFolder, studyOptions)) { @@ -197,9 +481,8 @@ int main(int argc, char* argv[]) durationCollector); const auto thermalSavePath = fs::path("ts-generator") / "thermal"; - const auto linksSavePath = fs::path("ts-generator") / "links"; - // THERMAL + // ============ THERMAL : Getting data for generating time-series ========= std::vector clusters; if (settings.allThermal) { @@ -215,28 +498,31 @@ int main(int argc, char* argv[]) logs.debug() << c->id(); } - // LINKS - TSGenerator::listOfLinks links; + // ============ LINKS : Getting data for generating LINKS time-series ===== + auto allLinksPairs = extractLinkNamesFromStudy(settings.studyFolder); + auto linksFromCmdLine = extractLinkNamesFromCmdLine(allLinksPairs, + settings.linksListToGen); if (settings.allLinks) - { - links = TSGenerator::getAllLinksToGen(study->areas); - } - else if (!settings.linksListToGen.empty()) - { - links = getLinksToGen(study->areas, settings.linksListToGen); - } + linksFromCmdLine = allLinksPairs; - for (auto& l: links) - { - logs.debug() << l->getName(); - } + StudyParamsForLinkTS generalParams = readGeneralParamsForLinksTS(settings.studyFolder); + + std::vector linkList = CreateLinkList(linksFromCmdLine); + readLinksSpecificTSparameters(linkList, settings.studyFolder); + + auto saveLinksTSpath = fs::path(settings.studyFolder) / "output" / DateAndTime(); + saveLinksTSpath /= "ts-generator"; + saveLinksTSpath /= "links"; + + // ============ TS Generation ============================================= bool ret = TSGenerator::generateThermalTimeSeries(*study, clusters, *resultWriter, thermalSavePath.string()); - ret = TSGenerator::generateLinkTimeSeries(*study, links, *resultWriter, linksSavePath.string()) + + ret = TSGenerator::generateLinkTimeSeries(linkList, generalParams, saveLinksTSpath.string()) && ret; return !ret; // return 0 for success -} +} \ No newline at end of file