From a7670bb5db763bfaa87b89d152d35ef528315fc0 Mon Sep 17 00:00:00 2001 From: payetvin <113102157+payetvin@users.noreply.github.com> Date: Fri, 1 Mar 2024 17:54:08 +0100 Subject: [PATCH] Tool for time series generation (#1967) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jason Maréchal <45510813+JasonMarechal25@users.noreply.github.com> Co-authored-by: Florian OMNES <26088210+flomnes@users.noreply.github.com> Co-authored-by: Guillaume PIERRE Co-authored-by: OMNES Florian --- .github/workflows/centos7.yml | 2 +- .github/workflows/oracle8.yml | 2 +- .../include/antares/study/runtime/runtime.h | 2 + src/libs/antares/study/runtime/runtime.cpp | 15 ++ src/libs/antares/utils/CMakeLists.txt | 3 +- .../utils/include/antares/utils/utils.h | 7 + src/libs/antares/utils/utils.cpp | 28 ++++ src/solver/application/application.cpp | 19 --- .../include/antares/application/application.h | 2 - .../include/antares/solver/misc/options.h | 4 +- src/solver/misc/options.cpp | 2 +- .../antares/solver/simulation/solver.hxx | 15 +- .../antares/solver/ts-generator/generator.h | 7 +- src/solver/ts-generator/thermal.cpp | 35 ++-- .../src/solver/simulation/CMakeLists.txt | 1 + .../solver/simulation/tests-ts-numbers.cpp | 37 +++++ src/tools/CMakeLists.txt | 2 +- src/tools/ts-generator/CMakeLists.txt | 28 ++++ src/tools/ts-generator/main.cpp | 157 ++++++++++++++++++ 19 files changed, 318 insertions(+), 50 deletions(-) create mode 100644 src/tools/ts-generator/CMakeLists.txt create mode 100644 src/tools/ts-generator/main.cpp diff --git a/.github/workflows/centos7.yml b/.github/workflows/centos7.yml index 1b5eb07d17..a2f6452ed1 100644 --- a/.github/workflows/centos7.yml +++ b/.github/workflows/centos7.yml @@ -88,7 +88,7 @@ jobs: -DCMAKE_BUILD_TYPE=Release \ -DBUILD_TESTING=ON \ -DBUILD_not_system=OFF \ - -DBUILD_TOOLS=OFF \ + -DBUILD_TOOLS=ON \ -DBUILD_UI=OFF \ -DCMAKE_PREFIX_PATH=${{ env.ORTOOLSDIR }}/install \ diff --git a/.github/workflows/oracle8.yml b/.github/workflows/oracle8.yml index f31c2f4823..683eebaff5 100644 --- a/.github/workflows/oracle8.yml +++ b/.github/workflows/oracle8.yml @@ -83,7 +83,7 @@ jobs: cmake -B _build -S src \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_TESTING=ON \ - -DBUILD_TOOLS=OFF \ + -DBUILD_TOOLS=ON \ -DBUILD_UI=OFF \ -DCMAKE_PREFIX_PATH=${{ env.ORTOOLS_DIR }}/install diff --git a/src/libs/antares/study/include/antares/study/runtime/runtime.h b/src/libs/antares/study/include/antares/study/runtime/runtime.h index 702078919c..93f4946875 100644 --- a/src/libs/antares/study/include/antares/study/runtime/runtime.h +++ b/src/libs/antares/study/include/antares/study/runtime/runtime.h @@ -82,6 +82,8 @@ class StudyRuntimeInfos */ bool loadFromStudy(Study& study); + void initializeRandomNumberGenerators(const Parameters& parameters); + public: //! The number of years to process uint nbYears; diff --git a/src/libs/antares/study/runtime/runtime.cpp b/src/libs/antares/study/runtime/runtime.cpp index b4f5d5c710..86a92afcd5 100644 --- a/src/libs/antares/study/runtime/runtime.cpp +++ b/src/libs/antares/study/runtime/runtime.cpp @@ -258,6 +258,19 @@ void StudyRuntimeInfos::checkThermalTSGeneration(Study& study) }); } +void StudyRuntimeInfos::initializeRandomNumberGenerators(const Parameters& parameters) +{ + logs.info() << "Initializing random number generators..."; + for (uint i = 0; i != Data::seedMax; ++i) + { +#ifndef NDEBUG + logs.debug() << " random number generator: " << Data::SeedToCString((Data::SeedIndex)i) + << ", seed: " << parameters.seed[i]; +#endif + random[i].reset(parameters.seed[i]); + } +} + bool StudyRuntimeInfos::loadFromStudy(Study& study) { auto& gd = study.parameters; @@ -309,6 +322,8 @@ bool StudyRuntimeInfos::loadFromStudy(Study& study) if (not gd.geographicTrimming) disableAllFilters(study); + initializeRandomNumberGenerators(study.parameters); + logs.info(); logs.info() << "Summary"; logs.info() << " areas: " << study.areas.size(); diff --git a/src/libs/antares/utils/CMakeLists.txt b/src/libs/antares/utils/CMakeLists.txt index 9a0bc514cf..904ad43dfc 100644 --- a/src/libs/antares/utils/CMakeLists.txt +++ b/src/libs/antares/utils/CMakeLists.txt @@ -12,6 +12,7 @@ add_library(Antares::utils ALIAS ${PROJ}) target_link_libraries(${PROJ} PRIVATE yuni-static-core + Antares::logs ) target_include_directories(${PROJ} @@ -21,4 +22,4 @@ target_include_directories(${PROJ} install(DIRECTORY include/antares DESTINATION "include" -) \ No newline at end of file +) diff --git a/src/libs/antares/utils/include/antares/utils/utils.h b/src/libs/antares/utils/include/antares/utils/utils.h index 6f262c713c..c2be72c9c1 100644 --- a/src/libs/antares/utils/include/antares/utils/utils.h +++ b/src/libs/antares/utils/include/antares/utils/utils.h @@ -24,6 +24,9 @@ #include #include +#include +#include + namespace Antares { /*! @@ -40,6 +43,10 @@ void TransformNameIntoID(const AnyString& name, StringT& out); void BeautifyName(YString& out, AnyString oldname); void BeautifyName(std::string& out, const std::string& oldname); +std::vector> splitStringIntoPairs(const std::string& s, + char delimiter1, + char delimiter2); + } // namespace Antares #include "utils.hxx" diff --git a/src/libs/antares/utils/utils.cpp b/src/libs/antares/utils/utils.cpp index 00ec1adadf..138ca072c8 100644 --- a/src/libs/antares/utils/utils.cpp +++ b/src/libs/antares/utils/utils.cpp @@ -20,6 +20,10 @@ */ #include "antares/utils/utils.h" +#include + + +#include using namespace Yuni; @@ -79,4 +83,28 @@ void BeautifyName(std::string& out, const std::string& oldname) out = yuniOut.c_str(); } +std::vector> splitStringIntoPairs(const std::string& s, + char delimiter1, + char delimiter2) +{ + std::vector> pairs; + std::stringstream ss(s); + std::string token; + + while (std::getline(ss, token, delimiter1)) + { + size_t pos = token.find(delimiter2); + if (pos != std::string::npos) + { + std::string begin = token.substr(0, pos); + std::string end = token.substr(pos + 1); + pairs.push_back({begin, end}); + } + else + logs.warning() << "Error while parsing: " << token; + } + + return pairs; +} + } // namespace Antares diff --git a/src/solver/application/application.cpp b/src/solver/application/application.cpp index 8c1d442ff8..421d24188c 100644 --- a/src/solver/application/application.cpp +++ b/src/solver/application/application.cpp @@ -197,22 +197,6 @@ void Application::prepare(int argc, char* argv[]) logs.info() << " The progression is disabled"; } -void Application::initializeRandomNumberGenerators() const -{ - logs.info() << "Initializing random number generators..."; - const auto& parameters = pStudy->parameters; - auto& runtime = *pStudy->runtime; - - for (uint i = 0; i != Data::seedMax; ++i) - { -#ifndef NDEBUG - logs.debug() << " random number generator: " << Data::SeedToCString((Data::SeedIndex)i) - << ", seed: " << parameters.seed[i]; -#endif - runtime.random[i].reset(parameters.seed[i]); - } -} - void Application::onLogMessage(int level, const Yuni::String& /*message*/) { switch (level) @@ -432,9 +416,6 @@ void Application::readDataForTheStudy(Data::StudyLoadOptions& options) // alloc global vectors SIM_AllocationTableaux(study); - - // Random-numbers generators - initializeRandomNumberGenerators(); } void Application::writeComment(Data::Study& study) { diff --git a/src/solver/application/include/antares/application/application.h b/src/solver/application/include/antares/application/application.h index f46d7ad3f9..ec83662f75 100644 --- a/src/solver/application/include/antares/application/application.h +++ b/src/solver/application/include/antares/application/application.h @@ -85,8 +85,6 @@ class Application final : public Yuni::IEventObserver CreateParser(Settings& settings, settings.simulationName, 'n', "name", "Set the name of the new simulation to VALUE"); // --generators-only parser->addFlag( - settings.tsGeneratorsOnly, 'g', "generators-only", "Run the time-series generators only"); + settings.tsGeneratorsOnly, 'g', "generators-only", "Run the time-series generators only"); // --comment-file parser->add(settings.commentFile, diff --git a/src/solver/simulation/include/antares/solver/simulation/solver.hxx b/src/solver/simulation/include/antares/solver/simulation/solver.hxx index 7c475f0f9c..25fbc1e085 100644 --- a/src/solver/simulation/include/antares/solver/simulation/solver.hxx +++ b/src/solver/simulation/include/antares/solver/simulation/solver.hxx @@ -481,15 +481,20 @@ void ISimulation::regenerateTimeSeries(uint year) timer.stop(); pDurationCollector.addDuration("tsgen_hydro", timer.get_duration()); } + // Thermal const bool refreshTSonCurrentYear = (year % pData.refreshIntervalThermal == 0); + Benchmarking::Timer timer; + + if (refreshTSonCurrentYear) { - Benchmarking::Timer timer; - GenerateThermalTimeSeries( - study, year, pData.haveToRefreshTSThermal, refreshTSonCurrentYear, pResultWriter); - timer.stop(); - pDurationCollector.addDuration("tsgen_thermal", timer.get_duration()); + auto clusters = getAllClustersToGen(study.areas, pData.haveToRefreshTSThermal); + + GenerateThermalTimeSeries(study, clusters, year, pResultWriter); } + + timer.stop(); + pDurationCollector.addDuration("tsgen_thermal", timer.get_duration()); } template 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 07d19e8f85..d2192e93ef 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 @@ -41,11 +41,12 @@ template bool GenerateTimeSeries(Data::Study& study, uint year, IResultWriter& writer); bool GenerateThermalTimeSeries(Data::Study& study, + std::vector clusters, uint year, - bool globalThermalTSgeneration, - bool refresh, - IResultWriter& writer); + Solver::IResultWriter& writer); +std::vector getAllClustersToGen(Data::AreaList& areas, + bool globalThermalTSgeneration); /*! ** \brief Destroy all TS Generators */ diff --git a/src/solver/ts-generator/thermal.cpp b/src/solver/ts-generator/thermal.cpp index 988d631fda..b5b33921dd 100644 --- a/src/solver/ts-generator/thermal.cpp +++ b/src/solver/ts-generator/thermal.cpp @@ -19,6 +19,7 @@ ** along with Antares_Simulator. If not, see . */ +#include #include #include @@ -596,11 +597,24 @@ void GeneratorTempData::operator()(Data::Area& area, Data::ThermalCluster& clust } } // namespace +std::vector getAllClustersToGen(Data::AreaList& areas, + bool globalThermalTSgeneration) +{ + std::vector clusters; + + areas.each([&clusters, &globalThermalTSgeneration](Data::Area& area) { + for (auto cluster : area.thermal.list.all()) + if (cluster->doWeGenerateTS(globalThermalTSgeneration)) + clusters.push_back(cluster.get()); + }); + + return clusters; +} + bool GenerateThermalTimeSeries(Data::Study& study, + std::vector clusters, uint year, - bool globalThermalTSgeneration, - bool refreshTSonCurrentYear, - Antares::Solver::IResultWriter& writer) + Solver::IResultWriter& writer) { logs.info(); logs.info() << "Generating the thermal time-series"; @@ -610,16 +624,9 @@ bool GenerateThermalTimeSeries(Data::Study& study, generator->currentYear = year; - study.areas.each([&](Data::Area& area) { - for (auto cluster : area.thermal.list.all()) - { - if (cluster->doWeGenerateTS(globalThermalTSgeneration) && refreshTSonCurrentYear) - { - (*generator)(area, *cluster); - } - ++progression; - } - }); + // TODO VP: parallel + for (auto* cluster : clusters) + (*generator)(*cluster->parentArea, *cluster); delete generator; @@ -627,5 +634,3 @@ bool GenerateThermalTimeSeries(Data::Study& study, } } // namespace Antares::TSGenerator - - diff --git a/src/tests/src/solver/simulation/CMakeLists.txt b/src/tests/src/solver/simulation/CMakeLists.txt index 18b460411c..e056ac388d 100644 --- a/src/tests/src/solver/simulation/CMakeLists.txt +++ b/src/tests/src/solver/simulation/CMakeLists.txt @@ -19,6 +19,7 @@ target_include_directories(tests-ts-numbers ) target_link_libraries(tests-ts-numbers PRIVATE + Antares::utils Boost::unit_test_framework model_antares antares-solver-simulation diff --git a/src/tests/src/solver/simulation/tests-ts-numbers.cpp b/src/tests/src/solver/simulation/tests-ts-numbers.cpp index e40fcbc8d5..8652a16d26 100644 --- a/src/tests/src/solver/simulation/tests-ts-numbers.cpp +++ b/src/tests/src/solver/simulation/tests-ts-numbers.cpp @@ -27,6 +27,7 @@ #include #include "antares/solver/ts-generator/generator.h" +#include #include // std::adjacent_find @@ -751,3 +752,39 @@ BOOST_AUTO_TEST_CASE(check_all_drawn_ts_numbers_are_bounded_between_0_and_nb_of_ BOOST_CHECK(thermalTsNumber < thermalNumberOfTs); BOOST_CHECK_LT(binding_constraints_TS_number, binding_constraints_number_of_TS); } + +BOOST_AUTO_TEST_CASE(split_string_ts_cluster_gen) +{ + char delimiter1 = ';'; + char delimiter2 = '.'; + + using stringPair = std::pair; + std::vector v; + + // only one pair of area cluster + v = splitStringIntoPairs("abc.def", delimiter1, delimiter2); + BOOST_CHECK(v[0] == stringPair("abc", "def")); + + // two pairs + v = splitStringIntoPairs("abc.def;ghi.jkl", delimiter1, delimiter2); + BOOST_CHECK(v[0] == stringPair("abc", "def")); + BOOST_CHECK(v[1] == stringPair("ghi", "jkl")); + + // first pair isn't valid + v = splitStringIntoPairs("abcdef;ghi.jkl", delimiter1, delimiter2); + BOOST_CHECK(v[0] == stringPair("ghi", "jkl")); + + // second pair isn't valid + v = splitStringIntoPairs("abc.def;ghijkl", delimiter1, delimiter2); + BOOST_CHECK(v[0] == stringPair("abc", "def")); + + // no semi colon + v = splitStringIntoPairs("abc.def.ghi.jkl", delimiter1, delimiter2); + BOOST_CHECK(v[0] == stringPair("abc", "def.ghi.jkl")); + + // no separator + v.clear(); + v = splitStringIntoPairs("abcdef", delimiter1, delimiter2); + BOOST_CHECK(v.empty()); +} + diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index 90f2afcee6..e11d4c3230 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -5,5 +5,5 @@ add_subdirectory(cleaner) add_subdirectory(yby-aggregator) add_subdirectory(config) add_subdirectory(vacuum) - add_subdirectory(kirchhoff-cbuilder) +add_subdirectory(ts-generator) diff --git a/src/tools/ts-generator/CMakeLists.txt b/src/tools/ts-generator/CMakeLists.txt new file mode 100644 index 0000000000..cea38ae0cd --- /dev/null +++ b/src/tools/ts-generator/CMakeLists.txt @@ -0,0 +1,28 @@ +set(SRCS + main.cpp +) + +set(execname "antares-ts-generator") +add_executable(${execname} ${SRCS}) +install(TARGETS ${execname} EXPORT antares-ts-generator DESTINATION bin) + +INSTALL(EXPORT ${execname} + FILE antares-ts-generatorConfig.cmake + DESTINATION cmake +) + +target_link_libraries(${execname} + PRIVATE + Antares::utils + antares-solver-ts-generator + Antares::study + Antares::checks +) + +target_include_directories(${execname} + PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/include" +) + +import_std_libs(${execname}) +executable_strip(${execname}) diff --git a/src/tools/ts-generator/main.cpp b/src/tools/ts-generator/main.cpp new file mode 100644 index 0000000000..0317a8f026 --- /dev/null +++ b/src/tools/ts-generator/main.cpp @@ -0,0 +1,157 @@ +/* +** 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + + +using namespace Antares; + +struct TsGeneratorSettings +{ + 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 = ""; +}; + +std::unique_ptr createTsGeneratorParser(TsGeneratorSettings& 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, usage: --thermal=\"areaID.clusterID;area2ID.clusterID\""); + + parser->remainingArguments(settings.studyFolder); + + + return parser; +} + +std::vector getClustersToGen(Data::AreaList& areas, + const std::string& clustersToGen) +{ + std::vector clusters; + const auto ids = splitStringIntoPairs(clustersToGen, ';', '.'); + + for (const auto& [areaID, clusterID] : ids) + { + logs.info() << "Generating ts for area: " << areaID << " and cluster: " << clusterID; + + auto* area = areas.find(areaID); + if (!area) + { + logs.warning() << "Area not found: " << areaID; + continue; + } + + auto* cluster = area->thermal.list.findInAll(clusterID); + if (!cluster) + { + logs.warning() << "Cluster not found: " << clusterID; + continue; + } + + clusters.push_back(cluster); + } + + return clusters; +} + +int main(int argc, char *argv[]) +{ + TsGeneratorSettings 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; + } + + 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::NullDurationCollector nullDurationCollector; + + auto resultWriter = Solver::resultWriterFactory( + Data::ResultFormat::legacyFilesDirectories, study->folderOutput, nullptr, nullDurationCollector); + + std::vector clusters; + + if (settings.thermalListToGen.empty()) + clusters = TSGenerator::getAllClustersToGen(study->areas, true); + else + clusters = getClustersToGen(study->areas, settings.thermalListToGen); + + for (auto& c : clusters) + logs.debug() << c->id(); + + return TSGenerator::GenerateThermalTimeSeries(*study, clusters, 0, *resultWriter); +} +