From c6a693edeff70fb9b32c37f086c906f43dc03416 Mon Sep 17 00:00:00 2001 From: Abdoulbari Zaher <32519851+a-zakir@users.noreply.github.com> Date: Mon, 6 Jan 2025 17:17:57 +0100 Subject: [PATCH] Add optional additionnal constraints to short-term storage objects [ANT-1855] (#2546) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit add week-dependent rhs New syntax to declare multiple contraints that shares same rhs vector and type --------- Co-authored-by: payetvin <113102157+payetvin@users.noreply.github.com> Co-authored-by: Vincent Payet Co-authored-by: Florian Omnès --- src/libs/antares/study/CMakeLists.txt | 4 +- src/libs/antares/study/area/list.cpp | 10 +- ...alConstraint.h => additionalConstraints.h} | 24 +- .../study/parts/short-term-storage/cluster.h | 4 +- .../parts/short-term-storage/container.h | 3 +- .../study/parts/short-term-storage/series.h | 2 +- ...nstraint.cpp => additionalConstraints.cpp} | 29 +- .../parts/short-term-storage/container.cpp | 149 ++++- .../study/parts/short-term-storage/series.cpp | 14 +- .../ShortTermStorageCumulation.cpp | 95 ++- .../opt_decompte_variables_et_contraintes.cpp | 8 +- ...opt_gestion_second_membre_cas_lineaire.cpp | 26 +- .../sim_structure_probleme_economique.h | 2 +- .../simulation/sim_calcul_economique.cpp | 18 +- .../short-term-storage-input-output.cpp | 579 ++++++++++++++++++ 15 files changed, 856 insertions(+), 111 deletions(-) rename src/libs/antares/study/include/antares/study/parts/short-term-storage/{AdditionalConstraint.h => additionalConstraints.h} (80%) rename src/libs/antares/study/parts/short-term-storage/{AdditionalConstraint.cpp => additionalConstraints.cpp} (68%) diff --git a/src/libs/antares/study/CMakeLists.txt b/src/libs/antares/study/CMakeLists.txt index e7336d020c..c3d23d9d80 100644 --- a/src/libs/antares/study/CMakeLists.txt +++ b/src/libs/antares/study/CMakeLists.txt @@ -102,9 +102,9 @@ set(SRC_STUDY_PART_SHORT_TERM_STORAGE parts/short-term-storage/series.cpp include/antares/study/parts/short-term-storage/series.h include/antares/study/parts/short-term-storage/cluster.h - include/antares/study/parts/short-term-storage/AdditionalConstraint.h + include/antares/study/parts/short-term-storage/additionalConstraints.h parts/short-term-storage/cluster.cpp - parts/short-term-storage/AdditionalConstraint.cpp + parts/short-term-storage/additionalConstraints.cpp ) source_group("study\\part\\short-term-storage" FILES ${SRC_STUDY_PART_SHORT_TERM_SOTRAGE}) diff --git a/src/libs/antares/study/area/list.cpp b/src/libs/antares/study/area/list.cpp index 7af3a6a5a7..c91ecc1e38 100644 --- a/src/libs/antares/study/area/list.cpp +++ b/src/libs/antares/study/area/list.cpp @@ -1194,12 +1194,14 @@ bool AreaList::loadFromFolder(const StudyLoadOptions& options) if (fs::exists(stsFolder)) { - for (const auto& [id, area]: areas) + for (const auto& area: areas | std::views::values) { - fs::path folder = stsFolder / "clusters" / area->id.c_str(); + fs::path cluster_folder = stsFolder / "clusters" / area->id.c_str(); + ret = area->shortTermStorage.createSTStorageClustersFromIniFile(cluster_folder) + && ret; - ret = area->shortTermStorage.createSTStorageClustersFromIniFile(folder) && ret; - ret = area->shortTermStorage.LoadConstraintsFromIniFile(folder) && ret; + const auto constraints_folder = stsFolder / "constraints" / area->id.c_str(); + ret = area->shortTermStorage.loadAdditionalConstraints(constraints_folder) && ret; } } else diff --git a/src/libs/antares/study/include/antares/study/parts/short-term-storage/AdditionalConstraint.h b/src/libs/antares/study/include/antares/study/parts/short-term-storage/additionalConstraints.h similarity index 80% rename from src/libs/antares/study/include/antares/study/parts/short-term-storage/AdditionalConstraint.h rename to src/libs/antares/study/include/antares/study/parts/short-term-storage/additionalConstraints.h index e16b991a05..5804261157 100644 --- a/src/libs/antares/study/include/antares/study/parts/short-term-storage/AdditionalConstraint.h +++ b/src/libs/antares/study/include/antares/study/parts/short-term-storage/additionalConstraints.h @@ -22,20 +22,30 @@ #pragma once #include #include +#include namespace Antares::Data::ShortTermStorage { -struct AdditionalConstraint +class SingleAdditionalConstraint +{ +public: + std::set hours; + unsigned int globalIndex = 0; + unsigned int localIndex = 0; + bool isValidHoursRange() const; +}; + +struct AdditionalConstraints { std::string name; std::string cluster_id; std::string variable; std::string operatorType; - std::set hours; - double rhs; + bool enabled = true; + std::vector rhs; - unsigned int globalIndex = 0; + std::vector constraints; struct ValidateResult { @@ -43,11 +53,15 @@ struct AdditionalConstraint std::string error_msg; }; + // Number of enabled constraints + std::size_t enabledConstraints() const; + ValidateResult validate() const; private: bool isValidVariable() const; bool isValidOperatorType() const; - bool isValidHoursRange() const; + + bool isValidHours() const; }; } // namespace Antares::Data::ShortTermStorage diff --git a/src/libs/antares/study/include/antares/study/parts/short-term-storage/cluster.h b/src/libs/antares/study/include/antares/study/parts/short-term-storage/cluster.h index df74a350b0..b7c901d6d1 100644 --- a/src/libs/antares/study/include/antares/study/parts/short-term-storage/cluster.h +++ b/src/libs/antares/study/include/antares/study/parts/short-term-storage/cluster.h @@ -26,7 +26,7 @@ #include -#include "AdditionalConstraint.h" +#include "additionalConstraints.h" #include "properties.h" #include "series.h" @@ -51,6 +51,6 @@ class STStorageCluster std::shared_ptr series = std::make_shared(); mutable Properties properties; - std::vector additional_constraints; + std::vector additionalConstraints; }; } // namespace Antares::Data::ShortTermStorage diff --git a/src/libs/antares/study/include/antares/study/parts/short-term-storage/container.h b/src/libs/antares/study/include/antares/study/parts/short-term-storage/container.h index d4e0233b5c..b53abc48f0 100644 --- a/src/libs/antares/study/include/antares/study/parts/short-term-storage/container.h +++ b/src/libs/antares/study/include/antares/study/parts/short-term-storage/container.h @@ -23,7 +23,6 @@ #include #include -#include "AdditionalConstraint.h" #include "cluster.h" namespace Antares::Data::ShortTermStorage @@ -42,7 +41,7 @@ class STStorageInput /// Number of enabled ST storages, ignoring disabled ST storages std::size_t count() const; - bool LoadConstraintsFromIniFile(const std::filesystem::path& filePath); + bool loadAdditionalConstraints(const std::filesystem::path& filePath); /// erase disabled cluster from the vector uint removeDisabledClusters(); diff --git a/src/libs/antares/study/include/antares/study/parts/short-term-storage/series.h b/src/libs/antares/study/include/antares/study/parts/short-term-storage/series.h index 80f90b94f2..1f53a220fe 100644 --- a/src/libs/antares/study/include/antares/study/parts/short-term-storage/series.h +++ b/src/libs/antares/study/include/antares/study/parts/short-term-storage/series.h @@ -63,5 +63,5 @@ class Series bool loadFile(const std::filesystem::path& folder, std::vector& vect); bool writeVectorToFile(const std::string& path, const std::vector& vect); - +void fillIfEmpty(std::vector& v, double value); } // namespace Antares::Data::ShortTermStorage diff --git a/src/libs/antares/study/parts/short-term-storage/AdditionalConstraint.cpp b/src/libs/antares/study/parts/short-term-storage/additionalConstraints.cpp similarity index 68% rename from src/libs/antares/study/parts/short-term-storage/AdditionalConstraint.cpp rename to src/libs/antares/study/parts/short-term-storage/additionalConstraints.cpp index 2ca904041c..edc8d4d227 100644 --- a/src/libs/antares/study/parts/short-term-storage/AdditionalConstraint.cpp +++ b/src/libs/antares/study/parts/short-term-storage/additionalConstraints.cpp @@ -18,11 +18,14 @@ ** You should have received a copy of the Mozilla Public Licence 2.0 ** along with Antares_Simulator. If not, see . */ -#include "antares/study/parts/short-term-storage/AdditionalConstraint.h" + +#include "antares/study/parts/short-term-storage/additionalConstraints.h" + +#include namespace Antares::Data::ShortTermStorage { -AdditionalConstraint::ValidateResult AdditionalConstraint::validate() const +AdditionalConstraints::ValidateResult AdditionalConstraints::validate() const { if (cluster_id.empty()) { @@ -39,27 +42,39 @@ AdditionalConstraint::ValidateResult AdditionalConstraint::validate() const return {false, "Invalid operator type. Must be 'less', 'equal', or 'greater'."}; } - if (!isValidHoursRange()) + if (!isValidHours()) { - return {false, "Hours set contains invalid values. Must be between 1 and 168."}; + return {false, "Hours sets contains invalid values. Must be between 1 and 168."}; } return {true, ""}; } -bool AdditionalConstraint::isValidHoursRange() const +bool SingleAdditionalConstraint::isValidHoursRange() const { // `hours` is a sorted set; begin() gives the smallest and prev(end()) gives the largest. return !hours.empty() && *hours.begin() >= 1 && *std::prev(hours.end()) <= 168; } -bool AdditionalConstraint::isValidVariable() const +bool AdditionalConstraints::isValidHours() const +{ + return std::ranges::all_of(constraints, + [](const auto& constraint) + { return constraint.isValidHoursRange(); }); +} + +bool AdditionalConstraints::isValidVariable() const { return variable == "injection" || variable == "withdrawal" || variable == "netting"; } -bool AdditionalConstraint::isValidOperatorType() const +bool AdditionalConstraints::isValidOperatorType() const { return operatorType == "less" || operatorType == "equal" || operatorType == "greater"; } + +std::size_t AdditionalConstraints::enabledConstraints() const +{ + return enabled ? constraints.size() : 0; +} } // namespace Antares::Data::ShortTermStorage diff --git a/src/libs/antares/study/parts/short-term-storage/container.cpp b/src/libs/antares/study/parts/short-term-storage/container.cpp index 39a958c5c7..4c8e17ba3a 100644 --- a/src/libs/antares/study/parts/short-term-storage/container.cpp +++ b/src/libs/antares/study/parts/short-term-storage/container.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -75,10 +76,80 @@ bool STStorageInput::createSTStorageClustersFromIniFile(const fs::path& path) return true; } -bool STStorageInput::LoadConstraintsFromIniFile(const fs::path& parent_path) +static bool loadHours(std::string hoursStr, AdditionalConstraints& additionalConstraints) +{ + std::erase_if(hoursStr, ::isspace); + // Validate the entire string format + if (std::regex fullFormatRegex(R"(^(\[\d+(,\d+)*\])(,(\[\d+(,\d+)*\]))*$)"); + !std::regex_match(hoursStr, fullFormatRegex)) + { + logs.error() << "In constraint " << additionalConstraints.name + << ": Input string does not match the required format: " << hoursStr << '\n'; + return false; + } + // Split the `hours` field into multiple groups + std::regex groupRegex(R"(\[(.*?)\])"); + // Match each group enclosed in square brackets + auto groupsBegin = std::sregex_iterator(hoursStr.begin(), hoursStr.end(), groupRegex); + auto groupsEnd = std::sregex_iterator(); + unsigned int localIndex = 0; + for (auto it = groupsBegin; it != groupsEnd; ++it) + { + // Extract the contents of the square brackets + std::string group = (*it)[1].str(); + std::stringstream ss(group); + std::string hour; + std::set hourSet; + int hourVal; + while (std::getline(ss, hour, ',')) + { + try + { + hourVal = std::stoi(hour); + hourSet.insert(hourVal); + } + + catch (const std::invalid_argument& ex) + { + logs.error() << "In constraint " << additionalConstraints.name + << " Hours sets contains invalid values: " << hour + << "\n exception thrown: " << ex.what() << '\n'; + + return false; + } + catch (const std::out_of_range& ex) + { + logs.error() << "In constraint " << additionalConstraints.name + << " Hours sets contains out of range values: " << hour + << "\n exception thrown: " << ex.what() << '\n'; + return false; + } + } + if (!hourSet.empty()) + { + // Add this group to the `hours` vec + additionalConstraints.constraints.push_back( + {.hours = hourSet, .localIndex = localIndex}); + ++localIndex; + } + } + return true; +} + +static bool readRHS(AdditionalConstraints& additionalConstraints, const fs::path& rhsPath) +{ + const auto ret = loadFile(rhsPath, additionalConstraints.rhs); + if (ret) + { + fillIfEmpty(additionalConstraints.rhs, 0.0); + } + return ret; +} + +bool STStorageInput::loadAdditionalConstraints(const fs::path& parentPath) { IniFile ini; - const auto pathIni = parent_path / "additional-constraints.ini"; + const auto pathIni = parentPath / "additional-constraints.ini"; if (!ini.open(pathIni, false)) { logs.info() << "There is no: " << pathIni; @@ -87,8 +158,8 @@ bool STStorageInput::LoadConstraintsFromIniFile(const fs::path& parent_path) for (auto* section = ini.firstSection; section; section = section->next) { - AdditionalConstraint constraint; - constraint.name = section->name.c_str(); + AdditionalConstraints additionalConstraints; + additionalConstraints.name = section->name.c_str(); for (auto* property = section->firstProperty; property; property = property->next) { const std::string key = property->key; @@ -96,46 +167,51 @@ bool STStorageInput::LoadConstraintsFromIniFile(const fs::path& parent_path) if (key == "cluster") { - // TODO do i have to transform the name to id? TransformNameIntoID std::string clusterName; value.to(clusterName); - constraint.cluster_id = transformNameIntoID(clusterName); + additionalConstraints.cluster_id = transformNameIntoID(clusterName); } - else if (key == "variable") + else if (key == "enabled") { - value.to(constraint.variable); + value.to(additionalConstraints.enabled); } - else if (key == "operator") + else if (key == "variable") { - value.to(constraint.operatorType); + value.to(additionalConstraints.variable); } - else if (key == "hours") + else if (key == "operator") { - std::stringstream ss(value.c_str()); - std::string hour; - while (std::getline(ss, hour, ',')) - { - int hourVal = std::stoi(hour); - constraint.hours.insert(hourVal); - } + value.to(additionalConstraints.operatorType); } - else if (key == "rhs") + else if (key == "hours" && !loadHours(value.c_str(), additionalConstraints)) { - property->value.to(constraint.rhs); + return false; } } - if (auto ret = constraint.validate(); !ret.ok) + // We don't want load RHS and link the STS time if the constraint is disabled + if (!additionalConstraints.enabled) + { + return true; + } + + if (const auto rhsPath = parentPath / ("rhs_" + additionalConstraints.name + ".txt"); + !readRHS(additionalConstraints, rhsPath)) + { + logs.error() << "Error while reading rhs file: " << rhsPath; + return false; + } + + if (auto [ok, error_msg] = additionalConstraints.validate(); !ok) { logs.error() << "Invalid constraint in section: " << section->name; - logs.error() << ret.error_msg; + logs.error() << error_msg; return false; } - auto it = std::find_if(storagesByIndex.begin(), - storagesByIndex.end(), - [&constraint](const STStorageCluster& cluster) - { return cluster.id == constraint.cluster_id; }); + auto it = std::ranges::find_if(storagesByIndex, + [&additionalConstraints](const STStorageCluster& cluster) + { return cluster.id == additionalConstraints.cluster_id; }); if (it == storagesByIndex.end()) { logs.warning() << " from file " << pathIni; @@ -145,7 +221,7 @@ bool STStorageInput::LoadConstraintsFromIniFile(const fs::path& parent_path) } else { - it->additional_constraints.push_back(constraint); + it->additionalConstraints.push_back(additionalConstraints); } } @@ -194,11 +270,20 @@ bool STStorageInput::saveDataSeriesToFolder(const std::string& folder) const std::size_t STStorageInput::cumulativeConstraintCount() const { - return std::accumulate(storagesByIndex.begin(), - storagesByIndex.end(), - 0, - [](int acc, const auto& cluster) - { return acc + cluster.additional_constraints.size(); }); + return std::accumulate( + storagesByIndex.begin(), + storagesByIndex.end(), + 0, + [](size_t outer_constraint_count, const auto& cluster) + { + return outer_constraint_count + + std::accumulate( + cluster.additionalConstraints.begin(), + cluster.additionalConstraints.end(), + 0, + [](size_t inner_constraint_count, const auto& additionalConstraints) + { return inner_constraint_count + additionalConstraints.enabledConstraints(); }); + }); } std::size_t STStorageInput::count() const diff --git a/src/libs/antares/study/parts/short-term-storage/series.cpp b/src/libs/antares/study/parts/short-term-storage/series.cpp index 6c341c948d..a8694e967b 100644 --- a/src/libs/antares/study/parts/short-term-storage/series.cpp +++ b/src/libs/antares/study/parts/short-term-storage/series.cpp @@ -107,16 +107,16 @@ bool loadFile(const fs::path& path, std::vector& vect) return true; } -void Series::fillDefaultSeriesIfEmpty() +void fillIfEmpty(std::vector& v, double value) { - auto fillIfEmpty = [](std::vector& v, double value) + if (v.empty()) { - if (v.empty()) - { - v.resize(HOURS_PER_YEAR, value); - } - }; + v.resize(HOURS_PER_YEAR, value); + } +} +void Series::fillDefaultSeriesIfEmpty() +{ fillIfEmpty(maxInjectionModulation, 1.0); fillIfEmpty(maxWithdrawalModulation, 1.0); fillIfEmpty(inflows, 0.0); diff --git a/src/solver/optimisation/constraints/ShortTermStorageCumulation.cpp b/src/solver/optimisation/constraints/ShortTermStorageCumulation.cpp index f6e684db83..3354ce29ce 100644 --- a/src/solver/optimisation/constraints/ShortTermStorageCumulation.cpp +++ b/src/solver/optimisation/constraints/ShortTermStorageCumulation.cpp @@ -27,10 +27,8 @@ class CumulationConstraint { public: - virtual void build(ConstraintBuilder& builder, - unsigned int index, - const ::ShortTermStorage::PROPERTIES& input) const - = 0; + virtual void build(unsigned int index) const = 0; + virtual std::string name() const = 0; virtual ~CumulationConstraint() = default; }; @@ -38,9 +36,12 @@ class CumulationConstraint class WithdrawalCumulationConstraint: public CumulationConstraint { public: - void build(ConstraintBuilder& builder, - unsigned int index, - const ::ShortTermStorage::PROPERTIES&) const override + WithdrawalCumulationConstraint(ConstraintBuilder& builder): + builder(builder) + { + } + + void build(unsigned int index) const override { builder.ShortTermStorageWithdrawal(index, 1.0); } @@ -51,14 +52,19 @@ class WithdrawalCumulationConstraint: public CumulationConstraint } ~WithdrawalCumulationConstraint() override = default; + + ConstraintBuilder& builder; }; class InjectionCumulationConstraint: public CumulationConstraint { public: - void build(ConstraintBuilder& builder, - unsigned int index, - const ::ShortTermStorage::PROPERTIES&) const override + InjectionCumulationConstraint(ConstraintBuilder& builder): + builder(builder) + { + } + + void build(unsigned int index) const override { builder.ShortTermStorageInjection(index, 1.0); } @@ -69,17 +75,25 @@ class InjectionCumulationConstraint: public CumulationConstraint } ~InjectionCumulationConstraint() override = default; + + ConstraintBuilder& builder; }; class NettingCumulationConstraint: public CumulationConstraint { public: - void build(ConstraintBuilder& builder, - unsigned int index, - const ::ShortTermStorage::PROPERTIES& input) const override + NettingCumulationConstraint( + ConstraintBuilder& builder, + const ::ShortTermStorage::PROPERTIES& short_term_storage_properties): + builder(builder), + short_term_storage_properties(short_term_storage_properties) + { + } + + void build(unsigned int index) const override { - builder.ShortTermStorageInjection(index, input.injectionEfficiency) - .ShortTermStorageWithdrawal(index, -input.withdrawalEfficiency); + builder.ShortTermStorageInjection(index, short_term_storage_properties.injectionEfficiency) + .ShortTermStorageWithdrawal(index, -short_term_storage_properties.withdrawalEfficiency); } std::string name() const override @@ -88,21 +102,28 @@ class NettingCumulationConstraint: public CumulationConstraint } ~NettingCumulationConstraint() override = default; + + ConstraintBuilder& builder; + const ShortTermStorage::PROPERTIES& short_term_storage_properties; }; -std::unique_ptr cumulationConstraintFromVariable(const std::string& variable) +std::unique_ptr cumulationConstraintFactory( + const std::string& variable, + ConstraintBuilder& builder, + const ShortTermStorage::PROPERTIES& short_term_storage_properties) { if (variable == "withdrawal") { - return std::make_unique(); + return std::make_unique(builder); } else if (variable == "injection") { - return std::make_unique(); + return std::make_unique(builder); } else if (variable == "netting") { - return std::make_unique(); + return std::make_unique(builder, + short_term_storage_properties); } throw std::invalid_argument("Invalid cumulation constraint type"); } @@ -130,28 +151,34 @@ void ShortTermStorageCumulation::add(int pays) for (const auto& storage: data.ShortTermStorage[pays]) { - for (const auto& constraint: storage.additional_constraints) + for (const auto& additionalConstraints: storage.additionalConstraints) { // sum (var[h]) sign rhs, h in list provided by user where: // var = injection for InjectionCumulationConstraint // var = withdrawal for WithdrawalCumulationConstraint // var = injectionEfficiency * injection - withdrawalEfficiency * withdrawal for Netting - auto constraintHelper = cumulationConstraintFromVariable(constraint.variable); - namer.ShortTermStorageCumulation(constraintHelper->name(), - builder.data.nombreDeContraintes, - storage.name, - constraint.name); - const auto index = storage.clusterGlobalIndex; - data.CorrespondanceCntNativesCntOptimHebdomadaires - .ShortTermStorageCumulation[constraint.globalIndex] - = builder.data.nombreDeContraintes; - - for (const auto& hour: constraint.hours) + auto cumulationConstraint = cumulationConstraintFactory(additionalConstraints.variable, + builder, + storage); + for (const auto& [hours, globalIndex, localIndex]: additionalConstraints.constraints) { - builder.updateHourWithinWeek(hour - 1); - constraintHelper->build(builder, index, storage); + namer.ShortTermStorageCumulation(cumulationConstraint->name(), + builder.data.nombreDeContraintes, + storage.name, + additionalConstraints.name + "_" + + std::to_string(localIndex)); + const auto index = storage.clusterGlobalIndex; + data.CorrespondanceCntNativesCntOptimHebdomadaires + .ShortTermStorageCumulation[globalIndex] + = builder.data.nombreDeContraintes; + + for (const auto& hour: hours) + { + builder.updateHourWithinWeek(hour - 1); + cumulationConstraint->build(index); + } + builder.SetOperator(ConvertSense(additionalConstraints.operatorType)).build(); } - builder.SetOperator(ConvertSense(constraint.operatorType)).build(); } } } diff --git a/src/solver/optimisation/opt_decompte_variables_et_contraintes.cpp b/src/solver/optimisation/opt_decompte_variables_et_contraintes.cpp index 79c29c58ab..19f3f26ec9 100644 --- a/src/solver/optimisation/opt_decompte_variables_et_contraintes.cpp +++ b/src/solver/optimisation/opt_decompte_variables_et_contraintes.cpp @@ -245,9 +245,13 @@ int OPT_DecompteDesVariablesEtDesContraintesDuProblemeAOptimiser(PROBLEME_HEBDO* ProblemeAResoudre->NombreDeContraintes += 2 * nombreDePasDeTempsPourUneOptimisation; } - if (!storage.additional_constraints.empty()) + if (!storage.additionalConstraints.empty()) { - ProblemeAResoudre->NombreDeContraintes += storage.additional_constraints.size(); + for (const auto& additionalConstraints: storage.additionalConstraints) + { + ProblemeAResoudre->NombreDeContraintes += additionalConstraints + .enabledConstraints(); + } } } } diff --git a/src/solver/optimisation/opt_gestion_second_membre_cas_lineaire.cpp b/src/solver/optimisation/opt_gestion_second_membre_cas_lineaire.cpp index c04d416f00..d4065fd880 100644 --- a/src/solver/optimisation/opt_gestion_second_membre_cas_lineaire.cpp +++ b/src/solver/optimisation/opt_gestion_second_membre_cas_lineaire.cpp @@ -19,6 +19,8 @@ ** along with Antares_Simulator. If not, see . */ +#include + #include "antares/solver/simulation/sim_structure_probleme_economique.h" double OPT_SommeDesPminThermiques(const PROBLEME_HEBDO*, int, uint); @@ -47,17 +49,27 @@ static void shortTermStorageCumulationRHS( const std::vector<::ShortTermStorage::AREA_INPUT>& shortTermStorageInput, int numberOfAreas, std::vector& SecondMembre, - const CORRESPONDANCES_DES_CONTRAINTES_HEBDOMADAIRES& CorrespondancesDesContraintesHebdomadaires) + const CORRESPONDANCES_DES_CONTRAINTES_HEBDOMADAIRES& CorrespondancesDesContraintesHebdomadaires, + int weekFirstHour) { for (int areaIndex = 0; areaIndex < numberOfAreas; areaIndex++) { for (auto& storage: shortTermStorageInput[areaIndex]) { - for (const auto& constraint: storage.additional_constraints) + for (const auto& additionalConstraints: storage.additionalConstraints) { - int cnt = CorrespondancesDesContraintesHebdomadaires - .ShortTermStorageCumulation[constraint.globalIndex]; - SecondMembre[cnt] = constraint.rhs; + for (const auto& constraint: additionalConstraints.constraints) + { + const int cnt = CorrespondancesDesContraintesHebdomadaires + .ShortTermStorageCumulation[constraint.globalIndex]; + + SecondMembre[cnt] = std::accumulate( + constraint.hours.begin(), + constraint.hours.end(), + 0.0, + [weekFirstHour, &additionalConstraints](const double sum, const int hour) + { return sum + additionalConstraints.rhs[weekFirstHour + hour - 1]; }); + } } } } @@ -395,11 +407,11 @@ void OPT_InitialiserLeSecondMembreDuProblemeLineaire(PROBLEME_HEBDO* problemeHeb } } } - shortTermStorageCumulationRHS(problemeHebdo->ShortTermStorage, problemeHebdo->NombreDePays, ProblemeAResoudre->SecondMembre, - problemeHebdo->CorrespondanceCntNativesCntOptimHebdomadaires); + problemeHebdo->CorrespondanceCntNativesCntOptimHebdomadaires, + weekFirstHour); if (problemeHebdo->OptimisationAvecCoutsDeDemarrage) { OPT_InitialiserLeSecondMembreDuProblemeLineaireCoutsDeDemarrage(problemeHebdo, diff --git a/src/solver/simulation/include/antares/solver/simulation/sim_structure_probleme_economique.h b/src/solver/simulation/include/antares/solver/simulation/sim_structure_probleme_economique.h index 35051ba2d5..9356868766 100644 --- a/src/solver/simulation/include/antares/solver/simulation/sim_structure_probleme_economique.h +++ b/src/solver/simulation/include/antares/solver/simulation/sim_structure_probleme_economique.h @@ -183,7 +183,7 @@ struct PROPERTIES bool penalizeVariationInjection; std::shared_ptr series; - std::vector additional_constraints; + std::vector additionalConstraints; int clusterGlobalIndex; std::string name; }; diff --git a/src/solver/simulation/sim_calcul_economique.cpp b/src/solver/simulation/sim_calcul_economique.cpp index 786b1b5aa6..d9166226f1 100644 --- a/src/solver/simulation/sim_calcul_economique.cpp +++ b/src/solver/simulation/sim_calcul_economique.cpp @@ -39,7 +39,7 @@ static void importShortTermStorages( std::vector<::ShortTermStorage::AREA_INPUT>& ShortTermStorageOut) { int clusterGlobalIndex = 0; - int clusterCumulativeConstraintGlobalIndex = 0; + int constraintGlobalIndex = 0; for (uint areaIndex = 0; areaIndex != areas.size(); areaIndex++) { ShortTermStorageOut[areaIndex].resize(areas[areaIndex]->shortTermStorage.count()); @@ -60,12 +60,20 @@ static void importShortTermStorages( toInsert.penalizeVariationInjection = st.properties.penalizeVariationInjection; toInsert.penalizeVariationWithdrawal = st.properties.penalizeVariationWithdrawal; toInsert.name = st.properties.name; - toInsert.additional_constraints = st.additional_constraints; - for (auto& constraint: toInsert.additional_constraints) + for (const auto& constraint: st.additionalConstraints) { - constraint.globalIndex = clusterCumulativeConstraintGlobalIndex; - ++clusterCumulativeConstraintGlobalIndex; + if (constraint.enabled) + { + auto newConstraint = constraint; + for (auto& c: newConstraint.constraints) + { + c.globalIndex = constraintGlobalIndex; + ++constraintGlobalIndex; + } + toInsert.additionalConstraints.push_back(std::move(newConstraint)); + } } + toInsert.series = st.series; // TODO add missing properties, or use the same struct diff --git a/src/tests/src/libs/antares/study/short-term-storage-input/short-term-storage-input-output.cpp b/src/tests/src/libs/antares/study/short-term-storage-input/short-term-storage-input-output.cpp index 265135c7d4..d4340dce12 100644 --- a/src/tests/src/libs/antares/study/short-term-storage-input/short-term-storage-input-output.cpp +++ b/src/tests/src/libs/antares/study/short-term-storage-input/short-term-storage-input-output.cpp @@ -25,10 +25,13 @@ #include #include +#include #include #include +#include "antares/antares/constants.h" +#include "antares/study/parts/short-term-storage/additionalConstraints.h" #include "antares/study/parts/short-term-storage/container.h" using namespace std; @@ -451,3 +454,579 @@ BOOST_FIXTURE_TEST_CASE(check_series_save, Fixture) } BOOST_AUTO_TEST_SUITE_END() + +BOOST_AUTO_TEST_SUITE(AdditionalConstraintsTests) + +BOOST_AUTO_TEST_CASE(Validate_ClusterIdEmpty) +{ + ShortTermStorage::AdditionalConstraints constraints; + constraints.cluster_id = ""; // Cluster ID is empty + constraints.variable = "injection"; + constraints.operatorType = "less"; + + auto [ok, error_msg] = constraints.validate(); + BOOST_CHECK_EQUAL(ok, false); + BOOST_CHECK_EQUAL(error_msg, "Cluster ID is empty."); +} + +BOOST_AUTO_TEST_CASE(Validate_InvalidVariable) +{ + ShortTermStorage::AdditionalConstraints constraints; + constraints.cluster_id = "ClusterA"; + constraints.variable = "invalid"; // Invalid variable type + constraints.operatorType = "less"; + + auto [ok, error_msg] = constraints.validate(); + BOOST_CHECK_EQUAL(ok, false); + BOOST_CHECK_EQUAL(error_msg, + "Invalid variable type. Must be 'injection', 'withdrawal', or 'netting'."); +} + +BOOST_AUTO_TEST_CASE(Validate_InvalidOperatorType) +{ + ShortTermStorage::AdditionalConstraints constraints; + constraints.cluster_id = "ClusterA"; + constraints.variable = "injection"; + constraints.operatorType = "invalid"; // Invalid operator type + + auto [ok, error_msg] = constraints.validate(); + BOOST_CHECK_EQUAL(ok, false); + BOOST_CHECK_EQUAL(error_msg, "Invalid operator type. Must be 'less', 'equal', or 'greater'."); +} + +BOOST_AUTO_TEST_CASE(Validate_InvalidHours_Empty) +{ + ShortTermStorage::AdditionalConstraints constraints; + constraints.cluster_id = "ClusterA"; + constraints.variable = "injection"; + constraints.operatorType = "less"; + + // Case : Empty hours + ShortTermStorage::SingleAdditionalConstraint constraint; + constraint.hours = {}; // Invalid: empty + constraints.constraints.push_back(constraint); + + auto [ok, error_msg] = constraints.validate(); + BOOST_CHECK_EQUAL(ok, false); + BOOST_CHECK_EQUAL(error_msg, "Hours sets contains invalid values. Must be between 1 and 168."); +} + +BOOST_AUTO_TEST_CASE(Validate_InvalidHours_Out_of_range) +{ + ShortTermStorage::AdditionalConstraints constraints; + constraints.cluster_id = "ClusterA"; + constraints.variable = "injection"; + constraints.operatorType = "less"; + + // Case: Out of range + ShortTermStorage::SingleAdditionalConstraint constraint; + constraint.hours = {120, 169}; // Invalid: out of range + constraints.constraints.push_back(constraint); + + auto [ok, error_msg] = constraints.validate(); + BOOST_CHECK_EQUAL(ok, false); + BOOST_CHECK_EQUAL(error_msg, "Hours sets contains invalid values. Must be between 1 and 168."); +} + +BOOST_AUTO_TEST_CASE(Validate_InvalidHours_Below_minimum) +{ + ShortTermStorage::AdditionalConstraints constraints; + constraints.cluster_id = "ClusterA"; + constraints.variable = "injection"; + constraints.operatorType = "less"; + + // Case : Below minimum + ShortTermStorage::SingleAdditionalConstraint constraint; + constraint.hours = {0, 1}; // Invalid: below minimum + constraints.constraints.push_back(constraint); + + auto [ok, error_msg] = constraints.validate(); + BOOST_CHECK_EQUAL(ok, false); + BOOST_CHECK_EQUAL(error_msg, "Hours sets contains invalid values. Must be between 1 and 168."); +} + +BOOST_AUTO_TEST_CASE(Validate_ValidConstraints) +{ + ShortTermStorage::AdditionalConstraints constraints; + constraints.cluster_id = "ClusterA"; + constraints.variable = "injection"; + constraints.operatorType = "less"; + + ShortTermStorage::SingleAdditionalConstraint constraint1; + constraint1.hours = {1, 2, 3}; // Valid hours + + ShortTermStorage::SingleAdditionalConstraint constraint2; + constraint2.hours = {100, 150, 168}; // Valid hours + + constraints.constraints = {constraint1, constraint2}; + + auto [ok, error_msg] = constraints.validate(); + BOOST_CHECK_EQUAL(ok, true); + BOOST_CHECK(error_msg.empty()); +} + +BOOST_AUTO_TEST_CASE(loadAdditionalConstraints_ValidFile) +{ + std::filesystem::path testPath = getFolder() / "test_data"; + std::filesystem::create_directory(testPath); + + std::ofstream iniFile(testPath / "additional-constraints.ini"); + iniFile << "[constraint1]\n"; + iniFile << "cluster=cluster1\n"; + iniFile << "variable=injection\n"; + iniFile << "operator=less\n"; + iniFile << "hours=[1,2,3]\n"; + iniFile.close(); + + ShortTermStorage::STStorageInput storageInput; + ShortTermStorage::STStorageCluster cluster; + cluster.id = "cluster1"; + storageInput.storagesByIndex.push_back(cluster); + + bool result = storageInput.loadAdditionalConstraints(testPath); + + BOOST_CHECK_EQUAL(result, true); + BOOST_CHECK_EQUAL(storageInput.storagesByIndex[0].additionalConstraints.size(), 1); + BOOST_CHECK_EQUAL(storageInput.storagesByIndex[0].additionalConstraints[0].name, "constraint1"); + + std::filesystem::remove_all(testPath); +} + +BOOST_AUTO_TEST_CASE(loadAdditionalConstraints_InvalidHours) +{ + std::filesystem::path testPath = getFolder() / "test_data"; + std::filesystem::create_directory(testPath); + + std::ofstream iniFile(testPath / "additional-constraints.ini"); + iniFile << "[constraint1]\n"; + iniFile << "cluster=ClusterA\n"; + iniFile << "variable=injection\n"; + iniFile << "operator=less\n"; + iniFile << "hours=[0,1]\n"; // Invalid hours + iniFile.close(); + + ShortTermStorage::STStorageInput storageInput; + ShortTermStorage::STStorageCluster cluster; + cluster.id = "ClusterA"; + storageInput.storagesByIndex.push_back(cluster); + + bool result = storageInput.loadAdditionalConstraints(testPath); + BOOST_CHECK_EQUAL(result, false); + + std::filesystem::remove_all(testPath); +} + +BOOST_AUTO_TEST_CASE(loadAdditionalConstraints_MissingFile) +{ + ShortTermStorage::STStorageInput storageInput; + bool result = storageInput.loadAdditionalConstraints("nonexistent_path"); + BOOST_CHECK_EQUAL(result, true); +} + +BOOST_AUTO_TEST_CASE(loadAdditionalConstraints_InvalidConstraint) +{ + std::filesystem::path testPath = getFolder() / "test_data"; + std::filesystem::create_directory(testPath); + + std::ofstream iniFile(testPath / "additional-constraints.ini"); + iniFile << "[constraint1]\n"; + iniFile << "cluster=cluster1\n"; + iniFile << "variable=invalid\n"; // Invalid variable + iniFile << "operator=less\n"; + iniFile << "hours=[1,2,3]\n"; + iniFile.close(); + + ShortTermStorage::STStorageInput storageInput; + ShortTermStorage::STStorageCluster cluster; + cluster.id = "cluster1"; + storageInput.storagesByIndex.push_back(cluster); + + bool result = storageInput.loadAdditionalConstraints(testPath); + BOOST_CHECK_EQUAL(result, false); + + std::filesystem::remove_all(testPath); +} + +BOOST_AUTO_TEST_CASE(loadAdditionalConstraints_ValidRhs) +{ + std::filesystem::path testPath = getFolder() / "test_data"; + std::filesystem::create_directory(testPath); + + std::ofstream iniFile(testPath / "additional-constraints.ini"); + iniFile << "[constraint1]\n"; + iniFile << "cluster=cluster1\n"; + iniFile << "variable=injection\n"; + iniFile << "operator=less\n"; + iniFile << "hours=[1,2,3]\n"; + iniFile.close(); + + std::ofstream rhsFile(testPath / "rhs_constraint1.txt"); + for (int i = 0; i < HOURS_PER_YEAR; ++i) + { + rhsFile << i * 1.0 << "\n"; + } + rhsFile.close(); + + ShortTermStorage::STStorageInput storageInput; + ShortTermStorage::STStorageCluster cluster; + cluster.id = "cluster1"; + storageInput.storagesByIndex.push_back(cluster); + + bool result = storageInput.loadAdditionalConstraints(testPath); + + BOOST_CHECK_EQUAL(result, true); + BOOST_CHECK_EQUAL(storageInput.storagesByIndex[0].additionalConstraints[0].rhs.size(), + HOURS_PER_YEAR); + BOOST_CHECK_EQUAL(storageInput.storagesByIndex[0].additionalConstraints[0].rhs[0], 0.0); + BOOST_CHECK_EQUAL( + storageInput.storagesByIndex[0].additionalConstraints[0].rhs[HOURS_PER_YEAR - 1], + HOURS_PER_YEAR - 1); + + std::filesystem::remove_all(testPath); +} + +BOOST_AUTO_TEST_CASE(Load2ConstraintsFromIniFile) +{ + std::filesystem::path testPath = getFolder() / "test_data"; + std::filesystem::create_directory(testPath); + + std::ofstream iniFile(testPath / "additional-constraints.ini"); + iniFile << R"([constraint1] + cluster=cluster1 + variable=injection + operator=less + hours=[1,2,3] + [constraint2] + cluster=cluster1 + variable=withdrawal + operator=greater + hours=[5,33])"; + iniFile.close(); + + std::ofstream rhsFile(testPath / "rhs_constraint1.txt"); + for (int i = 0; i < HOURS_PER_YEAR; ++i) + { + rhsFile << i * 1.0 << "\n"; + } + rhsFile.close(); + + ShortTermStorage::STStorageInput storageInput; + ShortTermStorage::STStorageCluster cluster; + cluster.id = "cluster1"; + storageInput.storagesByIndex.push_back(cluster); + + bool result = storageInput.loadAdditionalConstraints(testPath); + + BOOST_CHECK_EQUAL(result, true); + BOOST_CHECK_EQUAL(storageInput.storagesByIndex[0].additionalConstraints.size(), 2); + + //------- constraint1 ---------- + const auto& constraint1 = storageInput.storagesByIndex[0].additionalConstraints[0]; + BOOST_CHECK_EQUAL(constraint1.name, "constraint1"); + BOOST_CHECK_EQUAL(constraint1.operatorType, "less"); + BOOST_CHECK_EQUAL(constraint1.variable, "injection"); + BOOST_CHECK_EQUAL(constraint1.cluster_id, cluster.id); + BOOST_CHECK_EQUAL(constraint1.rhs.size(), HOURS_PER_YEAR); + BOOST_CHECK_EQUAL(constraint1.rhs[0], 0.0); + BOOST_CHECK_EQUAL(constraint1.rhs[HOURS_PER_YEAR - 1], HOURS_PER_YEAR - 1); + + //------- constraint2 ---------- + + const auto& constraint2 = storageInput.storagesByIndex[0].additionalConstraints[1]; + BOOST_CHECK_EQUAL(constraint2.name, "constraint2"); + BOOST_CHECK_EQUAL(constraint2.operatorType, "greater"); + BOOST_CHECK_EQUAL(constraint2.variable, "withdrawal"); + BOOST_CHECK_EQUAL(constraint2.cluster_id, cluster.id); + + BOOST_CHECK_EQUAL(constraint2.rhs.size(), HOURS_PER_YEAR); + BOOST_CHECK_EQUAL(constraint2.rhs[0], 0.0); + BOOST_CHECK_EQUAL(constraint2.rhs[HOURS_PER_YEAR - 1], 0.0); + + std::filesystem::remove_all(testPath); +} + +BOOST_AUTO_TEST_CASE(loadAdditionalConstraints_MissingRhsFile) +{ + std::filesystem::path testPath = getFolder() / "test_data"; + std::filesystem::create_directory(testPath); + + std::ofstream iniFile(testPath / "additional-constraints.ini"); + iniFile << "[constraint1]\n"; + iniFile << "cluster=cluster1\n"; + iniFile << "variable=injection\n"; + iniFile << "operator=less\n"; + iniFile << "hours=[1,2,3]\n"; + iniFile.close(); + + ShortTermStorage::STStorageInput storageInput; + ShortTermStorage::STStorageCluster cluster; + cluster.id = "cluster1"; + storageInput.storagesByIndex.push_back(cluster); + + bool result = storageInput.loadAdditionalConstraints(testPath); + + BOOST_CHECK_EQUAL(result, true); + BOOST_CHECK_EQUAL(storageInput.storagesByIndex[0].additionalConstraints[0].rhs.size(), + HOURS_PER_YEAR); + BOOST_CHECK_EQUAL(storageInput.storagesByIndex[0].additionalConstraints[0].rhs[0], 0.0); + + std::filesystem::remove_all(testPath); +} + +BOOST_AUTO_TEST_CASE(loadAdditionalConstraints_MalformedRhsFile) +{ + std::filesystem::path testPath = getFolder() / "test_data"; + std::filesystem::create_directory(testPath); + + std::ofstream iniFile(testPath / "additional-constraints.ini"); + iniFile << "[constraint1]\n"; + iniFile << "cluster=cluster1\n"; + iniFile << "variable=injection\n"; + iniFile << "operator=less\n"; + iniFile << "hours=[1,2,3]\n"; + iniFile.close(); + + std::ofstream rhsFile(testPath / "rhs_constraint1.txt"); + rhsFile << "1.0\n2.0\ninvalid\n4.0\n"; // Malformed line + rhsFile.close(); + + ShortTermStorage::STStorageInput storageInput; + ShortTermStorage::STStorageCluster cluster; + cluster.id = "cluster1"; + storageInput.storagesByIndex.push_back(cluster); + + bool result = storageInput.loadAdditionalConstraints(testPath); + BOOST_CHECK_EQUAL(result, false); + /*"Error while reading rhs file: " << "rhs_" << additionalConstraints.name + << + ".txt";*/ + std::filesystem::remove_all(testPath); +} + +BOOST_AUTO_TEST_CASE(loadAdditionalConstraints_IncompleteRhsFile) +{ + std::filesystem::path testPath = getFolder() / "test_data"; + std::filesystem::create_directory(testPath); + + std::ofstream iniFile(testPath / "additional-constraints.ini"); + iniFile << "[constraint1]\n"; + iniFile << "cluster=cluster1\n"; + iniFile << "variable=injection\n"; + iniFile << "operator=less\n"; + iniFile << "hours=[1,2,3]\n"; + iniFile.close(); + + std::ofstream rhsFile(testPath / "rhs_constraint1.txt"); + for (int i = 0; i < 10; ++i) + { + rhsFile << i * 1.0 << "\n"; + } + rhsFile.close(); + + ShortTermStorage::STStorageInput storageInput; + ShortTermStorage::STStorageCluster cluster; + cluster.id = "cluster1"; + storageInput.storagesByIndex.push_back(cluster); + + bool result = storageInput.loadAdditionalConstraints(testPath); + BOOST_CHECK_EQUAL(result, false); + + std::filesystem::remove_all(testPath); +} + +// Test data for parameterization +namespace bdata = boost::unit_test::data; + +BOOST_DATA_TEST_CASE(Validate_AllVariableOperatorCombinations, + bdata::make({"injection", "withdrawal", "netting"}) + ^ bdata::make({"less", "equal", "greater"}), + variable, + op) +{ + ShortTermStorage::AdditionalConstraints constraints; + constraints.cluster_id = "ClusterA"; + constraints.variable = variable; + constraints.operatorType = op; + + // Create constraints with valid hours + constraints.constraints.push_back(ShortTermStorage::SingleAdditionalConstraint{{1, 2, 3}}); + constraints.constraints.push_back(ShortTermStorage::SingleAdditionalConstraint{{50, 100, 150}}); + constraints.constraints.push_back( + ShortTermStorage::SingleAdditionalConstraint{{120, 121, 122}}); + + // Validate the constraints + auto [ok, error_msg] = constraints.validate(); + BOOST_CHECK_EQUAL(ok, true); + BOOST_CHECK(error_msg.empty()); +} + +BOOST_DATA_TEST_CASE(Validate_AllVariableOperatorCombinationsFromFile, + bdata::make({"injection", "withdrawal", "netting"}) + * bdata::make({"less", "equal", "greater"}), + variable, + op) +{ + // Define the path for the test data + std::filesystem::path testPath = std::filesystem::temp_directory_path() / "test_data"; + std::filesystem::create_directory(testPath); + + // Write the `.ini` file for this test case + std::ofstream iniFile(testPath / "additional-constraints.ini"); + iniFile << "[constraint1]\n"; + iniFile << "cluster=clustera\n"; + iniFile << "variable=" << variable << "\n"; + iniFile << "operator=" << op << "\n"; + iniFile << "enabled=true\n"; + iniFile << "hours=[1,2,3]\n"; + iniFile.close(); + + // Write the `rhs_constraint1.txt` file + std::ofstream rhsFile(testPath / "rhs_constraint1.txt"); + for (int i = 0; i < HOURS_PER_YEAR; ++i) + { + rhsFile << i * 1.0 << "\n"; + } + rhsFile.close(); + + // Setup storage input and cluster + ShortTermStorage::STStorageInput storageInput; + ShortTermStorage::STStorageCluster cluster; + cluster.id = "clustera"; + storageInput.storagesByIndex.push_back(cluster); + + // Load constraints from the `.ini` file + bool result = storageInput.loadAdditionalConstraints(testPath); + BOOST_CHECK_EQUAL(storageInput.cumulativeConstraintCount(), 1); + + // Assertions + BOOST_CHECK_EQUAL(result, true); + // Validate loaded constraints + auto& built_cluster = storageInput.storagesByIndex[0]; + BOOST_REQUIRE_EQUAL(built_cluster.additionalConstraints.size(), 1); + + const auto& loadedConstraint = built_cluster.additionalConstraints[0]; + + // Check variable, operator type, and rhs values + BOOST_CHECK_EQUAL(loadedConstraint.variable, variable); + BOOST_CHECK_EQUAL(loadedConstraint.operatorType, op); + BOOST_REQUIRE_EQUAL(loadedConstraint.rhs.size(), HOURS_PER_YEAR); + + int i = 0; + do + { + BOOST_CHECK_CLOSE(loadedConstraint.rhs[i], i * 1.0, 0.001); + // Check rhs values within a tolerance + + i += HOURS_PER_YEAR / 5; + } while (i < HOURS_PER_YEAR); +} + +BOOST_AUTO_TEST_CASE(Load_disabled) +{ + // Define the path for the test data + std::filesystem::path testPath = std::filesystem::temp_directory_path() / "test_data"; + std::filesystem::create_directory(testPath); + + // Write the `.ini` file for this test case + std::ofstream iniFile(testPath / "additional-constraints.ini"); + iniFile << "[constraint1]\n"; + iniFile << "cluster=clustera\n"; + iniFile << "variable=injection\n"; + iniFile << "operator=less\n"; + iniFile << "enabled=false\n"; + iniFile << "hours=[1,2,3]\n"; + iniFile.close(); + + // Setup storage input and cluster + ShortTermStorage::STStorageInput storageInput; + ShortTermStorage::STStorageCluster cluster; + cluster.id = "clustera"; + storageInput.storagesByIndex.push_back(cluster); + + // Load constraints from the `.ini` file + bool result = storageInput.loadAdditionalConstraints(testPath); + BOOST_CHECK_EQUAL(storageInput.cumulativeConstraintCount(), 0); + + // Assertions + BOOST_CHECK_EQUAL(result, true); + // Validate loaded constraints + auto& built_cluster = storageInput.storagesByIndex[0]; + BOOST_REQUIRE_EQUAL(built_cluster.additionalConstraints.size(), 0); +} + +BOOST_DATA_TEST_CASE(loadAdditionalConstraints_InvalidHoursFormat, + bdata::make({"", + "[]", + "[ ]", + "[\t]", + "[\r]", + "[\f]", + "[\v]", + "[1, nol]", + "[; 3,2,1]", + "[1, 12345678901]", + "[1, 12345", + "1]", + "[1,]", + "[1,,2]", + "[a]", + "[1, 2], , [3]"}), + hours) +{ + std::filesystem::path testPath = getFolder() / "test_data"; + std::filesystem::create_directory(testPath); + + std::ofstream iniFile(testPath / "additional-constraints.ini"); + iniFile << "[constraint1]\n"; + iniFile << "cluster=cluster1\n"; + iniFile << "variable=injection\n"; + iniFile << "operator=less\n"; + iniFile << "hours=" << hours << "\n"; // Invalid formats + iniFile.close(); + + ShortTermStorage::STStorageInput storageInput; + ShortTermStorage::STStorageCluster cluster; + cluster.id = "cluster1"; + storageInput.storagesByIndex.push_back(cluster); + + bool result = storageInput.loadAdditionalConstraints(testPath); + BOOST_CHECK_EQUAL(result, false); + + std::filesystem::remove_all(testPath); +} + +BOOST_DATA_TEST_CASE( + loadAdditionalConstraints_ValidHoursFormats, + bdata::make( + {"[1],[1],[3,2,1]", + "[\r1,\t2]", + "[\v1\f,\t2],\f\v\t[4]", + "[\f\v1]\t\t", + "\t\v\t[1 ], [ 1, 2,3] ", + " [4,5 ]", + "[1 2 3 , 11 3]"}), + hours) +{ + std::filesystem::path testPath = getFolder() / "test_data"; + std::filesystem::create_directory(testPath); + + std::ofstream iniFile(testPath / "additional-constraints.ini"); + iniFile << "[constraint1]\n"; + iniFile << "cluster=cluster1\n"; + iniFile << "variable=injection\n"; + iniFile << "operator=less\n"; + iniFile << "hours=" << hours << "\n"; // Valid formats + iniFile.close(); + + ShortTermStorage::STStorageInput storageInput; + ShortTermStorage::STStorageCluster cluster; + cluster.id = "cluster1"; + storageInput.storagesByIndex.push_back(cluster); + + bool result = storageInput.loadAdditionalConstraints(testPath); + BOOST_CHECK_EQUAL(result, true); + + std::filesystem::remove_all(testPath); +} + +BOOST_AUTO_TEST_SUITE_END()