From c80109f4cf470029cd261718fb11bcd4722bb094 Mon Sep 17 00:00:00 2001 From: guilpier-code <62292552+guilpier-code@users.noreply.github.com> Date: Wed, 10 Jul 2024 15:14:45 +0200 Subject: [PATCH] Link TS generation : splitting into multiple files (#2171) This PR aims at moving some code about link TS generation, from **main** program to new source files. It follows PR #2155 To be done : - [x] move headers (*.h) to the right place - [x] update this branch with its base branch (@flomnes made a change on base branch) We may also take advantage of this PR to : - [ ] Thermal TS generation : - [ ] avoid loading the **whole study** when **no** thermal TS generation is required (this is currently the case) - [ ] move implementation details about thermal TS generation from **main** program to new source files - [ ] Separate loading / extracting data from study and generate the TS --------- Co-authored-by: Florian OMNES <26088210+flomnes@users.noreply.github.com> Co-authored-by: Florian OMNES --- .../utils/include/antares/utils/utils.h | 6 +- src/libs/antares/utils/utils.cpp | 12 + src/solver/application/application.cpp | 19 +- .../antares/solver/simulation/solver.hxx | 19 +- src/solver/ts-generator/availability.cpp | 164 +++--- .../antares/solver/ts-generator/generator.h | 16 +- src/tools/ts-generator/CMakeLists.txt | 14 +- .../tools/ts-generator/linksTSgenerator.h | 32 ++ .../tools/ts-generator/tsGenerationOptions.h | 31 ++ src/tools/ts-generator/linksTSgenerator.cpp | 347 +++++++++++++ src/tools/ts-generator/main.cpp | 485 ++---------------- .../ts-generator/tsGenerationOptions.cpp | 77 +++ 12 files changed, 634 insertions(+), 588 deletions(-) create mode 100644 src/tools/ts-generator/include/antares/tools/ts-generator/linksTSgenerator.h create mode 100644 src/tools/ts-generator/include/antares/tools/ts-generator/tsGenerationOptions.h create mode 100644 src/tools/ts-generator/linksTSgenerator.cpp create mode 100644 src/tools/ts-generator/tsGenerationOptions.cpp diff --git a/src/libs/antares/utils/include/antares/utils/utils.h b/src/libs/antares/utils/include/antares/utils/utils.h index 1043e7ca4e..a2f50a2e54 100644 --- a/src/libs/antares/utils/include/antares/utils/utils.h +++ b/src/libs/antares/utils/include/antares/utils/utils.h @@ -36,9 +36,10 @@ namespace Antares */ template void TransformNameIntoID(const AnyString& name, StringT& out); - std::string transformNameIntoID(const std::string& name); +std::string FormattedTime(const std::string& format); + /*! ** \brief Beautify a name, for renaming an area for example */ @@ -51,11 +52,8 @@ std::vector> splitStringIntoPairs(const std: namespace Utils { - bool isZero(double d); - double round(double d, unsigned precision); - } // namespace Utils } // namespace Antares diff --git a/src/libs/antares/utils/utils.cpp b/src/libs/antares/utils/utils.cpp index 31992d6020..e188b2b13a 100644 --- a/src/libs/antares/utils/utils.cpp +++ b/src/libs/antares/utils/utils.cpp @@ -96,6 +96,18 @@ void BeautifyName(std::string& out, const std::string& oldname) out = yuniOut.c_str(); } +std::string FormattedTime(const std::string& format) +{ + using namespace std::chrono; + auto time = system_clock::to_time_t(system_clock::now()); + std::tm local_time = *std::localtime(&time); + + char time_buffer[256]; + std::strftime(time_buffer, sizeof(time_buffer), format.c_str(), &local_time); + + return std::string(time_buffer); +} + std::vector> splitStringIntoPairs(const std::string& s, char delimiter1, char delimiter2) diff --git a/src/solver/application/application.cpp b/src/solver/application/application.cpp index a55873ea09..c65e849a15 100644 --- a/src/solver/application/application.cpp +++ b/src/solver/application/application.cpp @@ -435,20 +435,6 @@ void Application::runSimulationInAdequacyMode() observer); } -static std::string timeToString() -{ - using namespace std::chrono; - auto time = system_clock::to_time_t(system_clock::now()); - std::tm local_time = *std::localtime(&time); - - char time_buffer[256]; - std::strftime(time_buffer, sizeof(time_buffer), "%Y%m%d-%H%M%S", &local_time); - - std::string currentTime = time_buffer; - - return currentTime; -} - void Application::resetLogFilename() const { fs::path logfile = fs::path(pSettings.studyFolder.c_str()) / "logs"; @@ -459,8 +445,9 @@ void Application::resetLogFilename() const + ". Aborting now."); } - logfile /= "solver-"; // append the filename - logfile += timeToString() + ".log"; // complete filename with timestamp and extension + logfile /= "solver-"; // append the filename + logfile += FormattedTime("%Y%m%d-%H%M%S") + + ".log"; // complete filename with timestamp and extension // Assigning the log filename logs.logfile(logfile.string()); diff --git a/src/solver/simulation/include/antares/solver/simulation/solver.hxx b/src/solver/simulation/include/antares/solver/simulation/solver.hxx index 2c0ae3bb1a..1aa04cb38f 100644 --- a/src/solver/simulation/include/antares/solver/simulation/solver.hxx +++ b/src/solver/simulation/include/antares/solver/simulation/solver.hxx @@ -454,15 +454,24 @@ void ISimulation::regenerateTimeSeries(uint year) if (refreshTSonCurrentYear) { auto clusters = getAllClustersToGen(study.areas, pData.haveToRefreshTSThermal); -#define SEP Yuni::IO::Separator - const std::string savePath = std::string("ts-generator") + SEP + "thermal" + SEP + "mc-" - + std::to_string(year); -#undef SEP - generateThermalTimeSeries(study, clusters, pResultWriter, savePath); + generateThermalTimeSeries(study, + clusters, + study.runtime->random[Data::seedTsGenThermal]); + + bool archive = study.parameters.timeSeriesToArchive & Data::timeSeriesThermal; + bool doWeWrite = archive && !study.parameters.noOutput; + if (doWeWrite) + { + fs::path savePath = fs::path(study.folderOutput.to()) / "ts-generator" + / "thermal" / "mc-" / std::to_string(year); + writeThermalTimeSeries(clusters, savePath); + } // apply the spinning if we generated some in memory clusters for (auto* cluster: clusters) + { cluster->calculationOfSpinning(); + } } }; } diff --git a/src/solver/ts-generator/availability.cpp b/src/solver/ts-generator/availability.cpp index efb5991093..a94fd99e46 100644 --- a/src/solver/ts-generator/availability.cpp +++ b/src/solver/ts-generator/availability.cpp @@ -19,8 +19,6 @@ ** along with Antares_Simulator. If not, see . */ -#include -#include #include #include // For Antares::IO::fileSetContent @@ -28,10 +26,6 @@ #include #include #include -#include -#include "antares/study/simulation.h" - -#define SEP Yuni::IO::Separator constexpr double FAILURE_RATE_EQ_1 = 0.999; @@ -45,14 +39,12 @@ AvailabilityTSGeneratorData::AvailabilityTSGeneratorData(Data::ThermalCluster* c forcedLaw(cluster->forcedLaw), plannedLaw(cluster->plannedLaw), prepro(cluster->prepro), - series(cluster->series.timeSeries), modulationCapacity(cluster->modulation[Data::thermalModulationCapacity]), name(cluster->name()) { } AvailabilityTSGeneratorData::AvailabilityTSGeneratorData(LinkTSgenerationParams& source, - Data::TimeSeries& capacity, Matrix<>& modulation, const std::string& areaDestName): unitCount(source.unitCount), @@ -62,7 +54,6 @@ AvailabilityTSGeneratorData::AvailabilityTSGeneratorData(LinkTSgenerationParams& forcedLaw(source.forcedLaw), plannedLaw(source.plannedLaw), prepro(source.prepro.get()), - series(capacity.timeSeries), modulationCapacity(modulation[0]), name(areaDestName) { @@ -70,20 +61,19 @@ AvailabilityTSGeneratorData::AvailabilityTSGeneratorData(LinkTSgenerationParams& namespace { -class GeneratorTempData final +class AvailabilityTSgenerator final { public: - explicit GeneratorTempData(Data::Study&, unsigned, MersenneTwister&); - explicit GeneratorTempData(bool, unsigned, MersenneTwister&); + explicit AvailabilityTSgenerator(bool, unsigned, MersenneTwister&); - void generateTS(AvailabilityTSGeneratorData&) const; + Matrix run(AvailabilityTSGeneratorData&) const; private: bool derated; uint nbOfSeriesToGen_; - MersenneTwister& rndgenerator; + MersenneTwister& randomGenerator_; static constexpr int Log_size = 4000; @@ -101,30 +91,21 @@ class GeneratorTempData final const T& duration) const; }; -GeneratorTempData::GeneratorTempData(Data::Study& study, - unsigned nbOfSeriesToGen, - MersenneTwister& rndGenerator): - derated(study.parameters.derated), - nbOfSeriesToGen_(nbOfSeriesToGen), - rndgenerator(rndGenerator) -{ -} - -GeneratorTempData::GeneratorTempData(bool derated, - unsigned int nbOfSeriesToGen, - MersenneTwister& rndGenerator): +AvailabilityTSgenerator::AvailabilityTSgenerator(bool derated, + unsigned int nbOfSeriesToGen, + MersenneTwister& randomGenerator): derated(derated), nbOfSeriesToGen_(nbOfSeriesToGen), - rndgenerator(rndGenerator) + randomGenerator_(randomGenerator) { } template -void GeneratorTempData::prepareIndispoFromLaw(Data::StatisticalLaw law, - double volatility, - std::array& A, - std::array& B, - const T& duration) const +void AvailabilityTSgenerator::prepareIndispoFromLaw(Data::StatisticalLaw law, + double volatility, + std::array& A, + std::array& B, + const T& duration) const { switch (law) { @@ -165,18 +146,18 @@ void GeneratorTempData::prepareIndispoFromLaw(Data::StatisticalLaw law, } } -int GeneratorTempData::durationGenerator(Data::StatisticalLaw law, - int expec, - double volat, - double a, - double b) const +int AvailabilityTSgenerator::durationGenerator(Data::StatisticalLaw law, + int expec, + double volat, + double a, + double b) const { if (volat == 0 || expec == 1) { return expec; } - double rndnumber = rndgenerator.next(); + double rndnumber = randomGenerator_.next(); switch (law) { @@ -197,13 +178,16 @@ int GeneratorTempData::durationGenerator(Data::StatisticalLaw law, return 0; } -void GeneratorTempData::generateTS(AvailabilityTSGeneratorData& tsGenerationData) const +Matrix AvailabilityTSgenerator::run(AvailabilityTSGeneratorData& tsGenerationData) const { assert(tsGenerationData.prepro); + Matrix to_return; + to_return.reset(nbOfSeriesToGen_, 24 * DAYS_PER_YEAR); + if (0 == tsGenerationData.unitCount || 0 == tsGenerationData.nominalCapacity) { - return; + return to_return; } const auto& preproData = *(tsGenerationData.prepro); @@ -317,18 +301,11 @@ void GeneratorTempData::generateTS(AvailabilityTSGeneratorData& tsGenerationData double last = 0; auto& modulation = tsGenerationData.modulationCapacity; - double* dstSeries = nullptr; const uint tsCount = nbOfSeriesToGen_ + 2; for (uint tsIndex = 0; tsIndex != tsCount; ++tsIndex) { uint hour = 0; - - if (tsIndex > 1) - { - dstSeries = tsGenerationData.series[tsIndex - 2]; - } - for (uint dayInTheYear = 0; dayInTheYear < DAYS_PER_YEAR; ++dayInTheYear) { assert(dayInTheYear < 366); @@ -388,7 +365,7 @@ void GeneratorTempData::generateTS(AvailabilityTSGeneratorData& tsGenerationData if (lf[dayInTheYear] > 0. && lf[dayInTheYear] <= FAILURE_RATE_EQ_1) { - A = rndgenerator.next(); + A = randomGenerator_.next(); last = FPOW[dayInTheYear][AUN]; if (A > last) @@ -424,7 +401,7 @@ void GeneratorTempData::generateTS(AvailabilityTSGeneratorData& tsGenerationData } last = PPOW[dayInTheYear][AUN_app]; - A = rndgenerator.next(); + A = randomGenerator_.next(); if (A > last) { @@ -578,7 +555,7 @@ void GeneratorTempData::generateTS(AvailabilityTSGeneratorData& tsGenerationData double AVPDayInTheYear = AVP[dayInTheYear]; for (uint h = 0; h != 24; ++h) { - dstSeries[hour] = std::round(AVPDayInTheYear * modulation[hour]); + to_return[tsIndex - 2][hour] = std::round(AVPDayInTheYear * modulation[hour]); ++hour; } } @@ -587,8 +564,9 @@ void GeneratorTempData::generateTS(AvailabilityTSGeneratorData& tsGenerationData if (derated) { - tsGenerationData.series.averageTimeseries(); + to_return.averageTimeseries(); } + return to_return; } } // namespace @@ -612,22 +590,7 @@ std::vector getAllClustersToGen(const Data::AreaList& are return clusters; } -static void writeResultsToDisk(const Data::Study& study, - Solver::IResultWriter& writer, - const Matrix<>& series, - const std::string& savePath) -{ - if (study.parameters.noOutput) - { - return; - } - - std::string buffer; - series.saveToBuffer(buffer, 0); - writer.addEntryFromBuffer(savePath, buffer); -} - -static void writeResultsToDisk(const Matrix<>& series, const std::filesystem::path& savePath) +void writeTStoDisk(const Matrix<>& series, const std::filesystem::path savePath) { std::string buffer; series.saveToBuffer(buffer, 0); @@ -642,45 +605,48 @@ static void writeResultsToDisk(const Matrix<>& series, const std::filesystem::pa bool generateThermalTimeSeries(Data::Study& study, const std::vector& clusters, - Solver::IResultWriter& writer, - const std::string& savePath) + MersenneTwister& thermalRandom) { logs.info(); logs.info() << "Generating the thermal time-series"; - bool archive = study.parameters.timeSeriesToArchive & Data::timeSeriesThermal; - - auto generator = GeneratorTempData(study, - study.parameters.nbTimeSeriesThermal, - study.runtime->random[Data::seedTsGenThermal]); + auto generator = AvailabilityTSgenerator(study.parameters.derated, + study.parameters.nbTimeSeriesThermal, + thermalRandom); for (auto* cluster: clusters) { AvailabilityTSGeneratorData tsGenerationData(cluster); - generator.generateTS(tsGenerationData); - - if (archive) // For compatibilty with in memory thermal TS generation - { - std::string filePath = savePath + SEP + cluster->parentArea->id + SEP + cluster->id() - + ".txt"; - writeResultsToDisk(study, writer, cluster->series.timeSeries, filePath); - } + cluster->series.timeSeries = generator.run(tsGenerationData); } return true; } +void writeThermalTimeSeries(const std::vector& clusters, + const fs::path& savePath) +{ + for (auto* cluster: clusters) + { + auto areaName = cluster->parentArea->id.to(); + auto clusterName = cluster->id(); + auto filePath = savePath / areaName / clusterName += ".txt"; + + writeTStoDisk(cluster->series.timeSeries, filePath); + } +} + // gp : we should try to add const identifiers before args here bool generateLinkTimeSeries(std::vector& links, StudyParamsForLinkTS& generalParams, - const std::string& savePath) + const fs::path& savePath) { logs.info(); logs.info() << "Generation of links time-series"; - auto generator = GeneratorTempData(generalParams.derated, - generalParams.nbLinkTStoGenerate, - generalParams.random); + auto generator = AvailabilityTSgenerator(generalParams.derated, + generalParams.nbLinkTStoGenerate, + generalParams.random); for (auto& link: links) { if (!link.hasValidData) @@ -695,33 +661,23 @@ bool generateLinkTimeSeries(std::vector& links, 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 + // === DIRECT ======================= AvailabilityTSGeneratorData tsConfigDataDirect(link, - ts, link.modulationCapacityDirect, link.namesPair.second); + auto generated_ts = generator.run(tsConfigDataDirect); - generator.generateTS(tsConfigDataDirect); + auto filePath = savePath / link.namesPair.first / link.namesPair.second += "_direct.txt"; + writeTStoDisk(generated_ts, filePath); - std::string filePath = savePath + SEP + link.namesPair.first + SEP + link.namesPair.second - + "_direct.txt"; - writeResultsToDisk(ts.timeSeries, filePath); - - // INDIRECT + // === INDIRECT ======================= AvailabilityTSGeneratorData tsConfigDataIndirect(link, - ts, link.modulationCapacityIndirect, link.namesPair.second); + generated_ts = generator.run(tsConfigDataIndirect); - generator.generateTS(tsConfigDataIndirect); - - filePath = savePath + SEP + link.namesPair.first + SEP + link.namesPair.second - + "_indirect.txt"; - writeResultsToDisk(ts.timeSeries, filePath); + filePath = savePath / link.namesPair.first / link.namesPair.second += "_indirect.txt"; + writeTStoDisk(generated_ts, 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 bfb2901bfa..5ffea85100 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 @@ -24,15 +24,14 @@ #include #include -#include #include #include #include #include -#include #include "xcast/xcast.h" +namespace fs = std::filesystem; using LinkPair = std::pair; using LinkPairs = std::vector; @@ -52,7 +51,7 @@ struct LinkTSgenerationParams { LinkPair namesPair; - unsigned unitCount = 0; + unsigned unitCount = 1; double nominalCapacity = 0; double forcedVolatility = 0.; @@ -76,7 +75,6 @@ class AvailabilityTSGeneratorData explicit AvailabilityTSGeneratorData(Data::ThermalCluster*); AvailabilityTSGeneratorData(LinkTSgenerationParams&, - Data::TimeSeries&, Matrix<>& modulation, const std::string& name); @@ -91,8 +89,6 @@ class AvailabilityTSGeneratorData Data::PreproAvailability* prepro; - Matrix<>& series; - Matrix<>::ColumnType& modulationCapacity; const std::string& name; @@ -110,12 +106,14 @@ bool GenerateTimeSeries(Data::Study& study, uint year, IResultWriter& writer); bool generateThermalTimeSeries(Data::Study& study, const std::vector& clusters, - Solver::IResultWriter& writer, - const std::string& savePath); + MersenneTwister& thermalRandom); + +void writeThermalTimeSeries(const std::vector& clusters, + const fs::path& savePath); bool generateLinkTimeSeries(std::vector& links, StudyParamsForLinkTS&, - const std::string& savePath); + const fs::path& savePath); std::vector getAllClustersToGen(const Data::AreaList& areas, bool globalThermalTSgeneration); diff --git a/src/tools/ts-generator/CMakeLists.txt b/src/tools/ts-generator/CMakeLists.txt index cea38ae0cd..f037e6f1dd 100644 --- a/src/tools/ts-generator/CMakeLists.txt +++ b/src/tools/ts-generator/CMakeLists.txt @@ -1,5 +1,9 @@ set(SRCS main.cpp + include/antares/tools/ts-generator/linksTSgenerator.h + include/antares/tools/ts-generator/tsGenerationOptions.h + tsGenerationOptions.cpp + linksTSgenerator.cpp ) set(execname "antares-ts-generator") @@ -13,15 +17,15 @@ INSTALL(EXPORT ${execname} target_link_libraries(${execname} PRIVATE - Antares::utils - antares-solver-ts-generator - Antares::study - Antares::checks + Antares::utils + antares-solver-ts-generator + Antares::study + Antares::checks ) target_include_directories(${execname} PRIVATE - "${CMAKE_CURRENT_SOURCE_DIR}/include" + "${CMAKE_CURRENT_SOURCE_DIR}/include" ) import_std_libs(${execname}) diff --git a/src/tools/ts-generator/include/antares/tools/ts-generator/linksTSgenerator.h b/src/tools/ts-generator/include/antares/tools/ts-generator/linksTSgenerator.h new file mode 100644 index 0000000000..1feaac5038 --- /dev/null +++ b/src/tools/ts-generator/include/antares/tools/ts-generator/linksTSgenerator.h @@ -0,0 +1,32 @@ + +#pragma once + +#include +#include "antares/tools/ts-generator/tsGenerationOptions.h" + +namespace fs = std::filesystem; + +namespace Antares::TSGenerator +{ + +class LinksTSgenerator +{ +public: + LinksTSgenerator(Settings&); + void extractData(); + bool generate(); + +private: + LinkPairs extractLinkNamesFromStudy(); + LinkPairs extractLinkNamesFromCmdLine(const LinkPairs&); + StudyParamsForLinkTS readGeneralParamsForLinksTS(); + void extractLinksSpecificTSparameters(); + + std::string linksFromCmdLineOptions_; + fs::path studyFolder_; + bool generateTSforAllLinks_ = false; + std::vector linkList_; + StudyParamsForLinkTS generalParams_; +}; + +} // namespace Antares::TSGenerator diff --git a/src/tools/ts-generator/include/antares/tools/ts-generator/tsGenerationOptions.h b/src/tools/ts-generator/include/antares/tools/ts-generator/tsGenerationOptions.h new file mode 100644 index 0000000000..1d83cce593 --- /dev/null +++ b/src/tools/ts-generator/include/antares/tools/ts-generator/tsGenerationOptions.h @@ -0,0 +1,31 @@ + +#pragma once + +#include + +#include + +namespace Antares::TSGenerator +{ +struct Settings +{ + std::string studyFolder; + + /// generate TS for all clusters if activated + bool allThermal = false; + /// generate TS for a list "area.cluster;area2.cluster2;" + std::string thermalListToGen = ""; + + /// generate TS for all links if activated + bool allLinks = false; + /// generate TS for a list "area.link;area2.link2;" + std::string linksListToGen; +}; + +bool parseOptions(int, char*[], Settings&); +std::unique_ptr createTsGeneratorParser(Settings&); + +bool checkOptions(Settings& options); +bool linkTSrequired(Settings& options); +bool thermalTSrequired(Settings& options); +} // namespace Antares::TSGenerator diff --git a/src/tools/ts-generator/linksTSgenerator.cpp b/src/tools/ts-generator/linksTSgenerator.cpp new file mode 100644 index 0000000000..7ad893194a --- /dev/null +++ b/src/tools/ts-generator/linksTSgenerator.cpp @@ -0,0 +1,347 @@ + +#include "antares/tools/ts-generator/linksTSgenerator.h" + +#include "antares/utils/utils.h" + +namespace Antares::TSGenerator +{ +// ================== +// Free functions +// ================== +std::vector extractTargetAreas(const 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) + { + to_return.push_back(transformNameIntoID(s->name)); + } + return to_return; +} + +bool pairs_match(const LinkPair& p1, const LinkPair& p2) +{ + return (p1.first == p2.first && p1.second == p2.second) + || (p1.first == p2.second && p1.second == p2.first); +} + +const LinkPair* getMatchingPairInCollection(const LinkPair& pair, const LinkPairs& collection) +{ + for (const auto& p: collection) + { + if (pairs_match(pair, p)) + { + return &p; + } + } + return nullptr; +} + +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; +} + +std::vector CreateLinkList(const LinkPairs& linksFromCmdLine) +{ + std::vector to_return; + std::for_each(linksFromCmdLine.begin(), + linksFromCmdLine.end(), + [&to_return](const auto& link_pair) + { + LinkTSgenerationParams link; + link.namesPair = link_pair; + to_return.push_back(std::move(link)); + }); + return to_return; +} + +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 problems here + 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); + } +} + +fs::path makePreproFile(const fs::path& preproFilePath, const std::string& changingEnd) +{ + auto to_return = preproFilePath; + to_return += changingEnd + ".txt"; + return to_return; +} + +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; + + // Testing files existence + auto preproFile = makePreproFile(preproFileRoot, ""); + auto modulationDirectFile = makePreproFile(preproFileRoot, "_mod_direct"); + auto modulationIndirectFile = makePreproFile(preproFileRoot, "_mod_indirect"); + std::vector paths{preproFile, modulationDirectFile, modulationIndirectFile}; + if (std::any_of(paths.begin(), paths.end(), [](auto& path) { return !fs::exists(path); })) + { + link.hasValidData = false; + return false; + } + + // Files loading + to_return = link.prepro->data.loadFromCSVFile(preproFile.string(), + Data::PreproAvailability::preproAvailabilityMax, + DAYS_PER_YEAR) + && link.prepro->validate() && to_return; + + to_return = link.modulationCapacityDirect.loadFromCSVFile(modulationDirectFile.string(), + 1, + HOURS_PER_YEAR) + && to_return; + + to_return = link.modulationCapacityIndirect.loadFromCSVFile(modulationIndirectFile.string(), + 1, + HOURS_PER_YEAR) + && to_return; + + 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/modulation data for link '" + << link.namesPair.first << "." << link.namesPair.second << "'"; + } + } +} + +// ================== +// Class methods +// ================== +LinksTSgenerator::LinksTSgenerator(Settings& settings): + studyFolder_(settings.studyFolder), + linksFromCmdLineOptions_(settings.linksListToGen), + generateTSforAllLinks_(settings.allLinks) +{ +} + +void LinksTSgenerator::extractData() +{ + auto allLinksPairs = extractLinkNamesFromStudy(); + + LinkPairs namesLinksToGenerate; + if (generateTSforAllLinks_) + { + namesLinksToGenerate = allLinksPairs; + } + else + { + namesLinksToGenerate = extractLinkNamesFromCmdLine(allLinksPairs); + } + + linkList_ = CreateLinkList(namesLinksToGenerate); + extractLinksSpecificTSparameters(); + + generalParams_ = readGeneralParamsForLinksTS(); +} + +LinkPairs LinksTSgenerator::extractLinkNamesFromStudy() +{ + LinkPairs to_return; + fs::path linksDir = studyFolder_ / "input" / "links"; + for (const auto& 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; +} + +LinkPairs LinksTSgenerator::extractLinkNamesFromCmdLine(const LinkPairs& allLinks) +{ + LinkPairs to_return; + LinkPairs pairsFromCmdLine = splitStringIntoPairs(linksFromCmdLineOptions_, ';', '.'); + for (auto& p: pairsFromCmdLine) + { + if (const auto* found_pair = getMatchingPairInCollection(p, allLinks); found_pair) + { + to_return.push_back(*found_pair); + } + else + { + logs.error() << "Link '" << p.first << "." << p.second << "' not found"; + } + } + return to_return; +} + +StudyParamsForLinkTS LinksTSgenerator::readGeneralParamsForLinksTS() +{ + StudyParamsForLinkTS to_return; + fs::path pathToGeneraldata = studyFolder_ / "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; +} + +void LinksTSgenerator::extractLinksSpecificTSparameters() +{ + fs::path toLinksDir = studyFolder_ / "input" / "links"; + readIniProperties(linkList_, toLinksDir); + readPreproTimeSeries(linkList_, toLinksDir); +} + +bool LinksTSgenerator::generate() +{ + auto saveTSpath = fs::path(studyFolder_) / "output" / FormattedTime("%Y%m%d-%H%M"); + saveTSpath /= "ts-generator"; + saveTSpath /= "links"; + + return generateLinkTimeSeries(linkList_, generalParams_, saveTSpath); +} + +} // namespace Antares::TSGenerator diff --git a/src/tools/ts-generator/main.cpp b/src/tools/ts-generator/main.cpp index b1fabf2ff7..d923d26a6c 100644 --- a/src/tools/ts-generator/main.cpp +++ b/src/tools/ts-generator/main.cpp @@ -22,74 +22,25 @@ #include #include -#include -#include - -#include -#include -#include #include #include -#include #include #include -#include -#include +#include "antares/tools/ts-generator/linksTSgenerator.h" +#include "antares/tools/ts-generator/tsGenerationOptions.h" +using namespace Antares::TSGenerator; -using namespace Antares; using namespace Antares::TSGenerator; namespace fs = std::filesystem; -struct Settings -{ - std::string studyFolder; - - /// generate TS for all clusters if activated - bool allThermal = false; - /// generate TS for a list "area.cluster;area2.cluster2;" - std::string thermalListToGen = ""; - - /// generate TS for all links if activated - bool allLinks = false; - /// generate TS for a list "area.link;area2.link2;" - std::string linksListToGen = ""; -}; - -std::unique_ptr createTsGeneratorParser(Settings& settings) -{ - auto parser = std::make_unique(); - parser->addParagraph("Antares Time Series generator\n"); - - parser->addFlag(settings.allThermal, - ' ', - "all-thermal", - "Generate TS for all thermal clusters"); - parser->addFlag(settings.thermalListToGen, - ' ', - "thermal", - "Generate TS for a list of area IDs and thermal clusters IDs, " - "\nusage: --thermal=\"areaID.clusterID;area2ID.clusterID\""); - - parser->addFlag(settings.allLinks, ' ', "all-links", "Generate TS capacities for all links"); - parser->addFlag(settings.linksListToGen, - ' ', - "links", - "Generate TS capacities for a list of 2 area IDs, " - "usage: --links=\"areaID.area2ID;area3ID.area1ID\""); - - parser->remainingArguments(settings.studyFolder); - - return parser; -} - std::vector getClustersToGen(Data::AreaList& areas, const std::string& clustersToGen) { std::vector clusters; - const auto ids = splitStringIntoPairs(clustersToGen, ';', '.'); + const auto pairsAreaCluster = splitStringIntoPairs(clustersToGen, ';', '.'); - for (const auto& [areaID, clusterID]: ids) + for (const auto& [areaID, clusterID]: pairsAreaCluster) { logs.info() << "Searching for area: " << areaID << " and cluster: " << clusterID; @@ -113,419 +64,63 @@ std::vector getClustersToGen(Data::AreaList& areas, return clusters; } -// ===== 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 (const auto& 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) -{ - return (p1.first == p2.first && p1.second == p2.second) - || (p1.first == p2.second && p1.second == p2.first); -} - -const LinkPair* getMatchingPairInCollection(const LinkPair& pair, const LinkPairs& collection) -{ - for (const auto& p: collection) - { - if (pairs_match(pair, p)) - { - return &p; - } - } - return nullptr; -} - -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.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; -} - -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; -} - -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) +int main(int argc, char* argv[]) { - 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); - } + logs.applicationName("ts-generator"); - if (key == "volatility.planned") + Settings settings; + if (!parseOptions(argc, argv, settings)) { - return value.to(link->plannedVolatility); + return 1; } - if (key == "volatility.forced") + if (!checkOptions(settings)) { - return value.to(link->forcedVolatility); + return 1; } - if (key == "force-no-generation") - { - return value.to(link->forceNoGeneration); - } - return true; -} + bool return_code{true}; -void readLinkIniProperties(LinkTSgenerationParams* link, IniFile::Section* section) -{ - for (const IniFile::Property* p = section->firstProperty; p; p = p->next) + if (thermalTSrequired(settings)) { - if (!readLinkIniProperty(link, p->key, p->value)) + // === Data for TS generation === + auto study = std::make_shared(true); + Data::StudyLoadOptions studyOptions; + if (!study->loadFromFolder(settings.studyFolder, studyOptions)) { - std::string linkName = link->namesPair.first + "." + link->namesPair.second; - logs.warning() << "Link '" << linkName << "' : reading value of '" << p->key - << "' went wrong"; - link->hasValidData = false; + logs.error() << "Invalid study given to the generator"; + return 1; } - } -} -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) + std::vector clusters; + if (settings.allThermal) { - readLinkIniProperties(foundLink, section); + clusters = getAllClustersToGen(study->areas, true); } - } -} - -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)) + else if (!settings.thermalListToGen.empty()) { - logs.warning() << "Could not load all prepro data for link '" << link.namesPair.first - << "." << link.namesPair.second << "'"; + clusters = getClustersToGen(study->areas, settings.thermalListToGen); } - } -} - -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(); -} -// ============================================================================ + // === TS generation === + MersenneTwister thermalRandom; + thermalRandom.reset(study->parameters.seed[Data::seedTsGenThermal]); + return_code = TSGenerator::generateThermalTimeSeries(*study, clusters, thermalRandom); -int main(int argc, char* argv[]) -{ - logs.applicationName("ts-generator"); - - Settings settings; - - auto parser = createTsGeneratorParser(settings); - switch (auto ret = parser->operator()(argc, argv); ret) - { - using namespace Yuni::GetOpt; - case ReturnCode::error: - logs.error() << "Unknown arguments, aborting"; - return parser->errors(); - case ReturnCode::help: - // End the program - return 0; - default: - break; - } - - if (settings.allThermal && !settings.thermalListToGen.empty()) - { - logs.error() << "Conflicting options, either choose all thermal clusters or a list"; - return 1; - } - - if (settings.allLinks && !settings.linksListToGen.empty()) - { - logs.error() << "Conflicting options, either choose all links or a list"; - return 1; - } - - auto study = std::make_shared(true); - Data::StudyLoadOptions studyOptions; - studyOptions.prepareOutput = true; - - if (!study->loadFromFolder(settings.studyFolder, studyOptions)) - { - logs.error() << "Invalid study given to the generator"; - return 1; - } - - study->initializeRuntimeInfos(); - // Force the writing of generated TS into output/YYYYMMDD-HHSSeco/ts-generator/thermal[/mc-0] - study->parameters.timeSeriesToArchive |= Antares::Data::timeSeriesThermal; - - try - { - Antares::Check::checkMinStablePower(true, study->areas); - } - catch (Error::InvalidParametersForThermalClusters& ex) - { - Antares::logs.error() << ex.what(); - } - - Benchmarking::DurationCollector durationCollector; - - auto resultWriter = Solver::resultWriterFactory(Data::ResultFormat::legacyFilesDirectories, - study->folderOutput, - nullptr, - durationCollector); - - const auto thermalSavePath = fs::path("ts-generator") / "thermal"; - - // ============ THERMAL : Getting data for generating time-series ========= - std::vector clusters; - if (settings.allThermal) - { - clusters = TSGenerator::getAllClustersToGen(study->areas, true); - } - else if (!settings.thermalListToGen.empty()) - { - clusters = getClustersToGen(study->areas, settings.thermalListToGen); - } - - for (auto& c: clusters) - { - logs.debug() << c->id(); + // === Writing generated TS on disk === + auto thermalSavePath = fs::path(settings.studyFolder) / "output" + / FormattedTime("%Y%m%d-%H%M"); + thermalSavePath /= "ts-generator"; + thermalSavePath /= "thermal"; + writeThermalTimeSeries(clusters, thermalSavePath); } - // ============ LINKS : Getting data for generating LINKS time-series ===== - auto allLinksPairs = extractLinkNamesFromStudy(settings.studyFolder); - auto linksFromCmdLine = extractLinkNamesFromCmdLine(allLinksPairs, settings.linksListToGen); - if (settings.allLinks) + if (linkTSrequired(settings)) { - linksFromCmdLine = allLinksPairs; + LinksTSgenerator linksTSgenerator(settings); + linksTSgenerator.extractData(); + return_code = linksTSgenerator.generate() && return_code; } - 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(linkList, generalParams, saveLinksTSpath.string()) - && ret; - - return !ret; // return 0 for success + return !return_code; // return 0 for success } diff --git a/src/tools/ts-generator/tsGenerationOptions.cpp b/src/tools/ts-generator/tsGenerationOptions.cpp new file mode 100644 index 0000000000..29fa64b6ad --- /dev/null +++ b/src/tools/ts-generator/tsGenerationOptions.cpp @@ -0,0 +1,77 @@ +#include "antares/tools/ts-generator/tsGenerationOptions.h" + +#include + +namespace Antares::TSGenerator +{ + +std::unique_ptr createTsGeneratorParser(Settings& settings) +{ + auto parser = std::make_unique(); + parser->addParagraph("Antares Time Series generator\n"); + + parser->addFlag(settings.allThermal, + ' ', + "all-thermal", + "Generate TS for all thermal clusters"); + parser->addFlag(settings.thermalListToGen, + ' ', + "thermal", + "Generate TS for a list of area IDs and thermal clusters IDs, " + "\nusage: --thermal=\"areaID.clusterID;area2ID.clusterID\""); + + parser->addFlag(settings.allLinks, ' ', "all-links", "Generate TS capacities for all links"); + parser->addFlag(settings.linksListToGen, + ' ', + "links", + "Generate TS capacities for a list of 2 area IDs, " + "usage: --links=\"areaID.area2ID;area3ID.area1ID\""); + + parser->remainingArguments(settings.studyFolder); + + return parser; +} + +bool parseOptions(int argc, char* argv[], Settings& options) +{ + auto parser = createTsGeneratorParser(options); + switch (auto ret = parser->operator()(argc, argv); ret) + { + using namespace Yuni::GetOpt; + case ReturnCode::error: + logs.error() << "Unknown arguments, aborting"; + return false; + case ReturnCode::help: + return false; + default: + break; + } + return true; +} + +bool checkOptions(Settings& options) +{ + if (options.allThermal && !options.thermalListToGen.empty()) + { + logs.error() << "Conflicting options, either choose all thermal clusters or a list"; + return false; + } + + if (options.allLinks && !options.linksListToGen.empty()) + { + logs.error() << "Conflicting options, either choose all links or a list"; + return false; + } + return true; +} + +bool linkTSrequired(Settings& options) +{ + return options.allLinks || !options.linksListToGen.empty(); +} + +bool thermalTSrequired(Settings& options) +{ + return options.allThermal || !options.thermalListToGen.empty(); +} +} // namespace Antares::TSGenerator