diff --git a/src/.clang-format b/.clang-format similarity index 100% rename from src/.clang-format rename to .clang-format diff --git a/docs/user-guide/solver/static-modeler/04-parameters.md b/docs/user-guide/solver/static-modeler/04-parameters.md index aad7e09794..a50f982135 100644 --- a/docs/user-guide/solver/static-modeler/04-parameters.md +++ b/docs/user-guide/solver/static-modeler/04-parameters.md @@ -433,6 +433,18 @@ _**This section is under construction**_ > _**Note:**_ You can find more information on this parameter [here](../03-appendix.md#details-on-the-include-exportmps-parameter). +--- +#### include-export-solutions +- **Expected value:** `true` or `false` +- **Required:** no +- **Default value:** `false` +- **Usage:** set to `true` to activate writing the raw optimization results, that is + - For each variable, optimal values (saved in output/output-name/optimal-values-y-w--optim-nb-z.txt) and reduced costs (saved in output/output-name/reduced-costs-y-w--optim-nb-z.txt) + - each constraint, the marginal cost is saved in output/output-name/marinal-costs-y-w--optim-nb-z.txt +where y is the year number (starting from 1), w is the week number (starting from 1) and z is the optimization index (1 or 2). + +This is an advanced option intended to help developers and advanced users better understand their simulation results. + --- #### include-split-exported-mps [//]: # (TODO: document this parameter, seems to belong to another category) diff --git a/src/format-code.sh b/src/format-code.sh index 752080ca58..aaededa940 100755 --- a/src/format-code.sh +++ b/src/format-code.sh @@ -20,5 +20,5 @@ fi if ! [ -x "$(command -v clang-format)" ]; then echo 'Warning: clang-format is not installed. Skipping' >&2 else - echo "$SOURCE_FILES" | xargs clang-format -i --verbose + echo "$SOURCE_FILES" | xargs clang-format -style=file:../.clang-format -i --verbose fi 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/parameters.h b/src/libs/antares/study/include/antares/study/parameters.h index 38228bc23b..664d574b32 100644 --- a/src/libs/antares/study/include/antares/study/parameters.h +++ b/src/libs/antares/study/include/antares/study/parameters.h @@ -397,6 +397,7 @@ class Parameters final //! Enum to define unfeasible problem behavior \see UnfeasibleProblemBehavior UnfeasibleProblemBehavior unfeasibleProblemBehavior; + bool exportSolutions; } include; struct Compatibility 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/parameters.cpp b/src/libs/antares/study/parameters.cpp index 54f5b6db74..60a3781bd2 100644 --- a/src/libs/antares/study/parameters.cpp +++ b/src/libs/antares/study/parameters.cpp @@ -368,6 +368,7 @@ void Parameters::reset() include.exportMPS = mpsExportStatus::NO_EXPORT; include.exportStructure = false; + include.exportSolutions = false; namedProblems = false; include.unfeasibleProblemBehavior = UnfeasibleProblemBehavior::ERROR_MPS; @@ -721,6 +722,11 @@ static bool SGDIntLoadFamily_Optimization(Parameters& d, return true; } + if (key == "include-export-solutions") + { + return value.to(d.include.exportSolutions); + } + if (key == "include-exportstructure") { return value.to(d.include.exportStructure); @@ -1769,6 +1775,10 @@ void Parameters::prepareForSimulation(const StudyLoadOptions& options) { logs.info() << " :: ignoring hurdle costs"; } + if (!include.exportSolutions) + { + logs.info() << " :: ignoring solution export"; + } logs.info() << " :: solver " << options.optOptions.ortoolsSolver << " is used for problem resolution"; @@ -1888,7 +1898,9 @@ void Parameters::saveToINI(IniFile& ini) const section->add("include-primaryreserve", include.reserve.primary); section->add("include-exportmps", mpsExportStatusToString(include.exportMPS)); + section->add("include-exportstructure", include.exportStructure); + section->add("include-export-solutions", include.exportSolutions); // Unfeasible problem behavior section->add("include-unfeasible-problem-behavior", diff --git a/src/libs/antares/study/parameters/adq-patch-params.cpp b/src/libs/antares/study/parameters/adq-patch-params.cpp index 800f2d19f8..ba075cc3a8 100644 --- a/src/libs/antares/study/parameters/adq-patch-params.cpp +++ b/src/libs/antares/study/parameters/adq-patch-params.cpp @@ -150,6 +150,7 @@ void AdqPatchParams::addExcludedVariables(std::vector& out) const out.emplace_back("LOLP CSR"); out.emplace_back("MAX MRG CSR"); out.emplace_back("OV. COST CSR"); + out.emplace_back("MRG. PRICE CSR"); } } 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/modelParser/CMakeLists.txt b/src/solver/modelParser/CMakeLists.txt index 8e5ac13726..55eae9b706 100644 --- a/src/solver/modelParser/CMakeLists.txt +++ b/src/solver/modelParser/CMakeLists.txt @@ -17,7 +17,7 @@ target_include_directories(modelParser # Link dependencies (if any) target_link_libraries(modelParser PRIVATE - yaml-cpp + yaml-cpp::yaml-cpp ) install(DIRECTORY include/antares diff --git a/src/solver/modeler/CMakeLists.txt b/src/solver/modeler/CMakeLists.txt index cb31b11fd4..da059fbe1f 100644 --- a/src/solver/modeler/CMakeLists.txt +++ b/src/solver/modeler/CMakeLists.txt @@ -22,6 +22,10 @@ target_link_libraries(modeler-lib INTERFACE Antares::loadModelerFiles Antares::modelerParameters + Antares::optim-model-filler + Antares::modeler_api + # TODO FIXME don't depend on implementations + Antares::modeler-ortools-impl ) target_link_libraries(antares-modeler diff --git a/src/solver/modeler/api/include/antares/solver/modeler/api/linearProblem.h b/src/solver/modeler/api/include/antares/solver/modeler/api/linearProblem.h index 6885d81ac1..dffc7f874c 100644 --- a/src/solver/modeler/api/include/antares/solver/modeler/api/linearProblem.h +++ b/src/solver/modeler/api/include/antares/solver/modeler/api/linearProblem.h @@ -72,6 +72,8 @@ class ILinearProblem /// Solve the problem, returns a IMipSolution virtual IMipSolution* solve(bool verboseSolver) = 0; + virtual void WriteLP(const std::string& filename) = 0; + // Definition of infinity virtual double infinity() const = 0; }; diff --git a/src/solver/modeler/api/include/antares/solver/modeler/api/mipSolution.h b/src/solver/modeler/api/include/antares/solver/modeler/api/mipSolution.h index 0018718de2..4a8b7237dc 100644 --- a/src/solver/modeler/api/include/antares/solver/modeler/api/mipSolution.h +++ b/src/solver/modeler/api/include/antares/solver/modeler/api/mipSolution.h @@ -21,6 +21,8 @@ #pragma once +#include +#include #include #include "mipVariable.h" @@ -51,6 +53,7 @@ class IMipSolution virtual double getObjectiveValue() const = 0; virtual double getOptimalValue(const IMipVariable* var) const = 0; virtual std::vector getOptimalValues(const std::vector& vars) const = 0; + virtual const std::map& getOptimalValues() const = 0; }; } // namespace Antares::Solver::Modeler::Api diff --git a/src/solver/modeler/main.cpp b/src/solver/modeler/main.cpp index a4d071660b..559c1f980a 100644 --- a/src/solver/modeler/main.cpp +++ b/src/solver/modeler/main.cpp @@ -19,15 +19,22 @@ * along with Antares_Simulator. If not, see . */ +#include + #include +#include #include +#include #include +#include using namespace Antares; using namespace Antares::Solver; int main(int argc, const char** argv) { + logs.applicationName("modeler"); + if (argc < 1) { logs.error() << "No study path provided, exiting."; @@ -51,6 +58,57 @@ int main(int argc, const char** argv) logs.info() << "Libraries loaded"; const auto system = LoadFiles::loadSystem(studyPath, libraries); logs.info() << "System loaded"; + + // Fillers, etc. + std::vector fillers; + // TODO memory + for (auto& [_, component]: system.Components()) + { + fillers.push_back(new Antares::Optimization::ComponentFiller(component)); + } + + Antares::Solver::Modeler::Api::LinearProblemData LP_Data; + Antares::Solver::Modeler::Api::FillContext ctx = {0, 0}; + // We force the usage of MIP solvers to check that integer variables are properly handled + // TODO determine the nature of the problem based on system.Components() + const bool isMip = true; + Antares::Solver::Modeler::OrtoolsImpl::OrtoolsLinearProblem pb(isMip, parameters.solver); + Antares::Solver::Modeler::Api::LinearProblemBuilder linear_problem_builder(fillers); + linear_problem_builder.build(pb, LP_Data, ctx); + for (auto& filler: fillers) + { + delete filler; + } + + logs.info() << "Number of variables: " << pb.variableCount(); + logs.info() << "Number of constraints: " << pb.constraintCount(); + + if (!parameters.noOutput) + { + logs.info() << "Writing problem.lp..."; + auto mps_path = std::filesystem::current_path() / "problem.lp"; + pb.WriteLP(mps_path.string()); + } + + logs.info() << "Launching resolution..."; + auto* solution = pb.solve(parameters.solverLogs); + switch (solution->getStatus()) + { + case Antares::Solver::Modeler::Api::MipStatus::OPTIMAL: + case Antares::Solver::Modeler::Api::MipStatus::FEASIBLE: + if (!parameters.noOutput) + { + logs.info() << "Writing variables..."; + std::ofstream sol_out(std::filesystem::current_path() / "solution.csv"); + for (const auto& [name, value]: solution->getOptimalValues()) + { + sol_out << name << " " << value << std::endl; + } + } + break; + default: + logs.error() << "Problem during linear optimization"; + } } catch (const LoadFiles::ErrorLoadingYaml&) { diff --git a/src/solver/modeler/ortoolsImpl/include/antares/solver/modeler/ortoolsImpl/linearProblem.h b/src/solver/modeler/ortoolsImpl/include/antares/solver/modeler/ortoolsImpl/linearProblem.h index 3d69ee98e9..f4a2fed61e 100644 --- a/src/solver/modeler/ortoolsImpl/include/antares/solver/modeler/ortoolsImpl/linearProblem.h +++ b/src/solver/modeler/ortoolsImpl/include/antares/solver/modeler/ortoolsImpl/linearProblem.h @@ -65,6 +65,7 @@ class OrtoolsLinearProblem: public Api::ILinearProblem bool isMaximization() const override; OrtoolsMipSolution* solve(bool verboseSolver) override; + void WriteLP(const std::string& filename) override; double infinity() const override; diff --git a/src/solver/modeler/ortoolsImpl/include/antares/solver/modeler/ortoolsImpl/mipSolution.h b/src/solver/modeler/ortoolsImpl/include/antares/solver/modeler/ortoolsImpl/mipSolution.h index 0cf452c11b..f536329e36 100644 --- a/src/solver/modeler/ortoolsImpl/include/antares/solver/modeler/ortoolsImpl/mipSolution.h +++ b/src/solver/modeler/ortoolsImpl/include/antares/solver/modeler/ortoolsImpl/mipSolution.h @@ -44,6 +44,7 @@ class OrtoolsMipSolution final: public Api::IMipSolution double getOptimalValue(const Api::IMipVariable* var) const override; std::vector getOptimalValues( const std::vector& vars) const override; + const std::map& getOptimalValues() const override; private: operations_research::MPSolver::ResultStatus status_; diff --git a/src/solver/modeler/ortoolsImpl/linearProblem.cpp b/src/solver/modeler/ortoolsImpl/linearProblem.cpp index feba47dda8..20c302c9fd 100644 --- a/src/solver/modeler/ortoolsImpl/linearProblem.cpp +++ b/src/solver/modeler/ortoolsImpl/linearProblem.cpp @@ -20,6 +20,7 @@ */ #include +#include #include #include @@ -165,6 +166,14 @@ bool OrtoolsLinearProblem::isMaximization() const return objective_->maximization(); } +void OrtoolsLinearProblem::WriteLP(const std::string& filename) +{ + std::string out; + mpSolver_->ExportModelAsLpFormat(false, &out); + std::ofstream of(filename); + of << out; +} + MPSolver* OrtoolsLinearProblem::MpSolver() const { return mpSolver_; diff --git a/src/solver/modeler/ortoolsImpl/mipSolution.cpp b/src/solver/modeler/ortoolsImpl/mipSolution.cpp index 8239f9893a..b10386a1a4 100644 --- a/src/solver/modeler/ortoolsImpl/mipSolution.cpp +++ b/src/solver/modeler/ortoolsImpl/mipSolution.cpp @@ -93,4 +93,9 @@ std::vector OrtoolsMipSolution::getOptimalValues( return solution; } +const std::map& OrtoolsMipSolution::getOptimalValues() const +{ + return solution_; +} + } // namespace Antares::Solver::Modeler::OrtoolsImpl diff --git a/src/solver/modeler/parameters/CMakeLists.txt b/src/solver/modeler/parameters/CMakeLists.txt index 7ab35d5002..308a15f148 100644 --- a/src/solver/modeler/parameters/CMakeLists.txt +++ b/src/solver/modeler/parameters/CMakeLists.txt @@ -8,7 +8,7 @@ add_library(Antares::modelerParameters ALIAS modelerParameters) target_link_libraries(modelerParameters PRIVATE - yaml-cpp + yaml-cpp::yaml-cpp Antares::io) target_include_directories(modelerParameters diff --git a/src/solver/optimisation/adequacy_patch_csr/adq_patch_post_process_list.cpp b/src/solver/optimisation/adequacy_patch_csr/adq_patch_post_process_list.cpp index f9f8aa389b..fb21977bcd 100644 --- a/src/solver/optimisation/adequacy_patch_csr/adq_patch_post_process_list.cpp +++ b/src/solver/optimisation/adequacy_patch_csr/adq_patch_post_process_list.cpp @@ -48,10 +48,10 @@ AdqPatchPostProcessList::AdqPatchPostProcessList(const AdqPatchParams& adqPatchP problemeHebdo_, areas, numSpace_)); - post_process_list.push_back( - std::make_unique(problemeHebdo_, areas, numSpace)); post_process_list.push_back( std::make_unique(problemeHebdo_, areas, numSpace)); + post_process_list.push_back( + std::make_unique(problemeHebdo_, areas, numSpace)); post_process_list.push_back( std::make_unique(problemeHebdo_, areas, true, false)); post_process_list.push_back( 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/optimisation/opt_optimisation_lineaire.cpp b/src/solver/optimisation/opt_optimisation_lineaire.cpp index 99669ad1ea..ecb0231f11 100644 --- a/src/solver/optimisation/opt_optimisation_lineaire.cpp +++ b/src/solver/optimisation/opt_optimisation_lineaire.cpp @@ -62,6 +62,38 @@ void OPT_EcrireResultatFonctionObjectiveAuFormatTXT( writer.addEntryFromBuffer(filename, buffer); } +void OPT_WriteSolution(const PROBLEME_ANTARES_A_RESOUDRE& pb, + const OptPeriodStringGenerator& optPeriodStringGenerator, + int optimizationNumber, + Solver::IResultWriter& writer) +{ + Yuni::Clob buffer; + auto filename = createSolutionFilename(optPeriodStringGenerator, optimizationNumber); + for (int var = 0; var < pb.NombreDeVariables; var++) + { + buffer.appendFormat("%s\t%11.10e\n", pb.NomDesVariables[var].c_str(), pb.X[var]); + } + writer.addEntryFromBuffer(filename, buffer); + buffer.clear(); + + filename = createMarginalCostFilename(optPeriodStringGenerator, optimizationNumber); + for (unsigned int cont = 0; cont < pb.NombreDeContraintes; ++cont) + { + buffer.appendFormat("%s\t%11.10e\n", + pb.NomDesContraintes[cont].c_str(), + pb.CoutsMarginauxDesContraintes[cont]); + } + writer.addEntryFromBuffer(filename, buffer); + buffer.clear(); + + filename = createReducedCostFilename(optPeriodStringGenerator, optimizationNumber); + for (unsigned int var = 0; var < pb.NombreDeVariables; ++var) + { + buffer.appendFormat("%s\t%11.10e\n", pb.NomDesVariables[var].c_str(), pb.CoutsReduits[var]); + } + writer.addEntryFromBuffer(filename, buffer); +} + namespace { void notifyProblemHebdo(const PROBLEME_HEBDO* problemeHebdo, @@ -141,6 +173,13 @@ bool runWeeklyOptimization(const OptimizationOptions& options, optimizationNumber, writer); } + if (problemeHebdo->exportSolutions) + { + OPT_WriteSolution(*problemeHebdo->ProblemeAResoudre, + *optPeriodStringGenerator, + optimizationNumber, + writer); + } } return true; } diff --git a/src/solver/optimisation/post_process_commands.cpp b/src/solver/optimisation/post_process_commands.cpp index 3ddc06ddde..83446402a9 100644 --- a/src/solver/optimisation/post_process_commands.cpp +++ b/src/solver/optimisation/post_process_commands.cpp @@ -143,6 +143,18 @@ void UpdateMrgPriceAfterCSRcmd::execute(const optRuntimeData&) const bool isHourTriggeredByCsr = problemeHebdo_->adequacyPatchRuntimeData ->wasCSRTriggeredAtAreaHour(Area, hour); + // IF UNSP. ENR CSR == 0, MRG. PRICE CSR = MRG. PRICE + // ELSE, MRG. PRICE CSR = “Unsupplied Energy Cost” + if (hourlyResults.ValeursHorairesDeDefaillancePositiveCSR[hour] > 0.5 && areaInside) + { + hourlyResults.CoutsMarginauxHorairesCSR[hour] = -unsuppliedEnergyCost; + } + else + { + hourlyResults.CoutsMarginauxHorairesCSR[hour] = hourlyResults + .CoutsMarginauxHoraires[hour]; + } + if (isHourTriggeredByCsr && hourlyResults.ValeursHorairesDeDefaillancePositive[hour] > 0.5 && areaInside) { diff --git a/src/solver/simulation/common-hydro-remix.cpp b/src/solver/simulation/common-hydro-remix.cpp index e370a39a03..13f8e5d49d 100644 --- a/src/solver/simulation/common-hydro-remix.cpp +++ b/src/solver/simulation/common-hydro-remix.cpp @@ -24,7 +24,6 @@ #include #include -#include #include #include #include "antares/solver/simulation/common-eco-adq.h" @@ -35,19 +34,23 @@ namespace Antares::Solver::Simulation { + +const unsigned int HOURS_IN_WEEK = 168; +const unsigned int HOURS_IN_DAY = 24; + template static bool Remix(const Data::AreaList& areas, PROBLEME_HEBDO& problem, uint numSpace, uint hourInYear) { - double HE[168]; + double HE[HOURS_IN_WEEK]; - double DE[168]; + double DE[HOURS_IN_WEEK]; - bool remix[168]; + bool remix[HOURS_IN_WEEK]; - double G[168]; + double G[HOURS_IN_WEEK]; bool status = true; @@ -69,7 +72,7 @@ static bool Remix(const Data::AreaList& areas, uint endHour = step; uint offset = 0; - for (; offset < 168; offset += step, endHour += step) + for (; offset < HOURS_IN_WEEK; offset += step, endHour += step) { { double WD = 0.; @@ -238,8 +241,8 @@ std::vector extractLoadForCurrentWeek(const Data::Area& area, const unsigned int year, const unsigned int firstHourOfWeek) { - std::vector load_to_return(168, 0.); - for (int h = 0; h < 168; h++) + std::vector load_to_return(HOURS_IN_WEEK, 0.); + for (int h = 0; h < HOURS_IN_WEEK; h++) { load_to_return[h] = area.load.series.getColumn(year)[h + firstHourOfWeek]; } @@ -251,8 +254,8 @@ std::vector extractHydroPmin(const Data::Area& area, const unsigned int firstHourOfWeek) { // area->hydro.series->mingen.timeSeries - std::vector hydroPmin(168, 0.); - for (int h = 0; h < 168; h++) + std::vector hydroPmin(HOURS_IN_WEEK, 0.); + for (int h = 0; h < HOURS_IN_WEEK; h++) { hydroPmin[h] = area.hydro.series->mingen.getColumn(year)[h + firstHourOfWeek]; } @@ -287,7 +290,7 @@ static void RunAccurateShavePeaks(const Data::AreaList& areas, const auto& spillage = weeklyResults.ValeursHorairesDeDefaillanceNegative; const auto& dtgMrgArray = area.scratchpad[numSpace].dispatchableGenerationMargin; - const std::vector dtgMrg(dtgMrgArray, dtgMrgArray + 168); + const std::vector dtgMrg(dtgMrgArray, dtgMrgArray + HOURS_IN_WEEK); auto [H, U, L] = shavePeaksByRemixingHydro(DispatchGen, hydroGen, @@ -321,10 +324,10 @@ void RemixHydroForAllAreas(const Data::AreaList& areas, switch (simplexOptimizationRange) { case Data::sorWeek: - result = Remix<168>(areas, problem, numSpace, hourInYear); + result = Remix(areas, problem, numSpace, hourInYear); break; case Data::sorDay: - result = Remix<24>(areas, problem, numSpace, hourInYear); + result = Remix(areas, problem, numSpace, hourInYear); break; case Data::sorUnknown: logs.fatal() << "invalid simplex optimization range"; 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..dc7375c1f1 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; }; @@ -436,6 +436,7 @@ struct RESULTATS_HORAIRES std::vector debordementsHoraires; std::vector CoutsMarginauxHoraires; + std::vector CoutsMarginauxHorairesCSR; std::vector ProductionThermique; // index is pdtHebdo std::vector<::ShortTermStorage::RESULTS> ShortTermStorage; @@ -532,6 +533,7 @@ struct PROBLEME_HEBDO bool exportMPSOnError = false; bool ExportStructure = false; bool NamedProblems = false; + bool exportSolutions = false; uint32_t HeureDansLAnnee = 0; bool LeProblemeADejaEteInstancie = false; diff --git a/src/solver/simulation/shave-peaks-by-remix-hydro.cpp b/src/solver/simulation/shave-peaks-by-remix-hydro.cpp index 5ad4823add..99a8f2aa55 100644 --- a/src/solver/simulation/shave-peaks-by-remix-hydro.cpp +++ b/src/solver/simulation/shave-peaks-by-remix-hydro.cpp @@ -18,7 +18,7 @@ int find_min_index(const std::vector& TotalGen, { double min_val = top; int min_hour = -1; - for (int h = 0; h < TotalGen.size(); ++h) + for (unsigned int h = 0; h < TotalGen.size(); ++h) { if (OutUnsupE[h] > 0 && OutHydroGen[h] < HydroPmax[h] && !triedBottom[h] && enabledHours[h]) { @@ -42,7 +42,7 @@ int find_max_index(const std::vector& TotalGen, { double max_val = 0; int max_hour = -1; - for (int h = 0; h < TotalGen.size(); ++h) + for (unsigned int h = 0; h < TotalGen.size(); ++h) { if (OutHydroGen[h] > HydroPmin[h] && TotalGen[h] >= ref_value + eps && !triedPeak[h] && enabledHours[h]) @@ -59,9 +59,9 @@ int find_max_index(const std::vector& TotalGen, static bool operator<=(const std::vector& a, const std::vector& b) { - std::vector a_minus_b; - std::ranges::transform(a, b, std::back_inserter(a_minus_b), std::minus()); - return std::ranges::all_of(a_minus_b, [](const double& e) { return e <= 0.; }); + return a.size() == b.size() + && std::ranges::all_of(std::views::iota(size_t{0}, a.size()), + [&](size_t i) { return a[i] <= b[i]; }); } static bool operator<=(const std::vector& v, const double c) diff --git a/src/solver/simulation/sim_alloc_probleme_hebdo.cpp b/src/solver/simulation/sim_alloc_probleme_hebdo.cpp index dd7480671c..d6386dc891 100644 --- a/src/solver/simulation/sim_alloc_probleme_hebdo.cpp +++ b/src/solver/simulation/sim_alloc_probleme_hebdo.cpp @@ -398,6 +398,7 @@ void SIM_AllocateAreas(PROBLEME_HEBDO& problem, problem.ResultatsHoraires[k].TurbinageHoraire.assign(NombreDePasDeTemps, 0.); problem.ResultatsHoraires[k].PompageHoraire.assign(NombreDePasDeTemps, 0.); problem.ResultatsHoraires[k].CoutsMarginauxHoraires.assign(NombreDePasDeTemps, 0.); + problem.ResultatsHoraires[k].CoutsMarginauxHorairesCSR.assign(NombreDePasDeTemps, 0.); problem.ResultatsHoraires[k].niveauxHoraires.assign(NombreDePasDeTemps, 0.); problem.ResultatsHoraires[k].valeurH2oHoraire.assign(NombreDePasDeTemps, 0.); problem.ResultatsHoraires[k].debordementsHoraires.assign(NombreDePasDeTemps, 0.); diff --git a/src/solver/simulation/sim_calcul_economique.cpp b/src/solver/simulation/sim_calcul_economique.cpp index 786b1b5aa6..bfd33cc9f2 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 @@ -114,6 +122,7 @@ void SIM_InitialisationProblemeHebdo(Data::Study& study, problem.NombreDeContraintesCouplantes = activeConstraints.size(); problem.ExportMPS = study.parameters.include.exportMPS; + problem.exportSolutions = study.parameters.include.exportSolutions; problem.ExportStructure = study.parameters.include.exportStructure; problem.NamedProblems = study.parameters.namedProblems; problem.exportMPSOnError = Data::exportMPS(parameters.include.unfeasibleProblemBehavior); diff --git a/src/solver/systemParser/CMakeLists.txt b/src/solver/systemParser/CMakeLists.txt index 37e09a616a..e0c16fea65 100644 --- a/src/solver/systemParser/CMakeLists.txt +++ b/src/solver/systemParser/CMakeLists.txt @@ -22,7 +22,7 @@ target_link_libraries(systemParser PUBLIC Antares::antares-study-system-model PRIVATE - yaml-cpp + yaml-cpp::yaml-cpp ) install(DIRECTORY include/antares diff --git a/src/solver/utils/filename.cpp b/src/solver/utils/filename.cpp index cc71083350..2393e40c13 100644 --- a/src/solver/utils/filename.cpp +++ b/src/solver/utils/filename.cpp @@ -65,3 +65,21 @@ std::string createMPSfilename(const OptPeriodStringGenerator& optPeriodStringGen { return createOptimizationFilename("problem", optPeriodStringGenerator, optNumber, "mps"); } + +std::string createSolutionFilename(const OptPeriodStringGenerator& optPeriodStringGenerator, + const unsigned int optNumber) +{ + return createOptimizationFilename("optimal-values", optPeriodStringGenerator, optNumber, "txt"); +} + +std::string createMarginalCostFilename(const OptPeriodStringGenerator& optPeriodStringGenerator, + const unsigned int optNumber) +{ + return createOptimizationFilename("marginal-costs", optPeriodStringGenerator, optNumber, "txt"); +} + +std::string createReducedCostFilename(const OptPeriodStringGenerator& optPeriodStringGenerator, + const unsigned int optNumber) +{ + return createOptimizationFilename("reduced-costs", optPeriodStringGenerator, optNumber, "txt"); +} diff --git a/src/solver/utils/include/antares/solver/utils/filename.h b/src/solver/utils/include/antares/solver/utils/filename.h index 472d8f8477..7828fcc7a3 100644 --- a/src/solver/utils/include/antares/solver/utils/filename.h +++ b/src/solver/utils/include/antares/solver/utils/filename.h @@ -32,5 +32,15 @@ std::shared_ptr createOptPeriodAsString(bool isOptimiz std::string createCriterionFilename(const OptPeriodStringGenerator& optPeriodStringGenerator, const unsigned int optNumber); + std::string createMPSfilename(const OptPeriodStringGenerator& optPeriodStringGenerator, const unsigned int optNumber); + +std::string createSolutionFilename(const OptPeriodStringGenerator& optPeriodStringGenerator, + const unsigned int optNumber); + +std::string createMarginalCostFilename(const OptPeriodStringGenerator& optPeriodStringGenerator, + const unsigned int optNumber); + +std::string createReducedCostFilename(const OptPeriodStringGenerator& optPeriodStringGenerator, + const unsigned int optNumber); diff --git a/src/solver/variable/CMakeLists.txt b/src/solver/variable/CMakeLists.txt index ed7c017f5b..49105937c3 100644 --- a/src/solver/variable/CMakeLists.txt +++ b/src/solver/variable/CMakeLists.txt @@ -100,6 +100,7 @@ set(SRC_VARIABLE_ECONOMY include/antares/solver/variable/economy/STStorageCashFlowByCluster.h include/antares/solver/variable/economy/unsupliedEnergy.h include/antares/solver/variable/economy/unsupliedEnergyCsr.h + include/antares/solver/variable/economy/priceCSR.h include/antares/solver/variable/economy/domesticUnsuppliedEnergy.h include/antares/solver/variable/economy/localMatchingRuleViolations.h include/antares/solver/variable/economy/dtgMarginAfterCsr.h diff --git a/src/solver/variable/include/antares/solver/variable/economy/all.h b/src/solver/variable/include/antares/solver/variable/economy/all.h index 17207c22db..bd2e5c4cb2 100644 --- a/src/solver/variable/include/antares/solver/variable/economy/all.h +++ b/src/solver/variable/include/antares/solver/variable/economy/all.h @@ -60,6 +60,7 @@ #include "overallCost.h" #include "overallCostCsr.h" #include "overflow.h" +#include "priceCSR.h" #include "pumping.h" #include "renewableGeneration.h" #include "reservoirlevel.h" @@ -95,62 +96,64 @@ namespace Antares::Solver::Variable::Economy /*! ** \brief All variables for a single area (economy) */ -typedef // Prices - OverallCost // Overall Cost (Op. Cost + Unsupplied Eng.) - >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +typedef // Prices + OverallCost // Overall Cost (Op. Cost + Unsupplied Eng.) + >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> VariablesPerArea; /*! diff --git a/src/solver/variable/include/antares/solver/variable/economy/priceCSR.h b/src/solver/variable/include/antares/solver/variable/economy/priceCSR.h new file mode 100644 index 0000000000..a3eac2966a --- /dev/null +++ b/src/solver/variable/include/antares/solver/variable/economy/priceCSR.h @@ -0,0 +1,273 @@ +/* +** Copyright 2007-2023 RTE +** Authors: Antares_Simulator Team +** +** This file is part of Antares_Simulator. +** +** Antares_Simulator is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** There are special exceptions to the terms and conditions of the +** license as they are applied to this software. View the full text of +** the exceptions in file COPYING.txt in the directory of this software +** distribution +** +** Antares_Simulator is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with Antares_Simulator. If not, see . +** +** SPDX-License-Identifier: licenceRef-GPL3_WITH_RTE-Exceptions +*/ +#pragma once + +#include "yuni/yuni.h" + +#include "../variable.h" + +namespace Antares::Solver::Variable::Economy +{ +struct VCardPriceCSR +{ + //! Caption + static std::string Caption() + { + return "MRG. PRICE CSR"; + } + + //! Unit + static std::string Unit() + { + return "Euro"; + } + + //! The short description of the variable + static std::string Description() + { + return "Marginal Price CSR, throughout all MC years"; + } + + //! The expecte results + typedef Results>>>> + ResultsType; + + //! The VCard to look for for calculating spatial aggregates + typedef VCardPrice VCardForSpatialAggregate; + + enum + { + //! Data Level + categoryDataLevel = Category::DataLevel::area, + //! File level (provided by the type of the results) + categoryFileLevel = ResultsType::categoryFile + & (Category::FileLevel::id | Category::FileLevel::va), + //! Precision (views) + precision = Category::all, + //! Indentation (GUI) + nodeDepthForGUI = +0, + //! Decimal precision + decimal = 2, + //! Number of columns used by the variable (One ResultsType per column) + columnCount = 1, + //! The Spatial aggregation + spatialAggregate = Category::spatialAggregateAverage, + spatialAggregateMode = Category::spatialAggregateEachYear, + spatialAggregatePostProcessing = Category::spatialAggregatePostProcessingPrice, + //! Intermediate values + hasIntermediateValues = 1, + //! Can this variable be non applicable (0 : no, 1 : yes) + isPossiblyNonApplicable = 0, + }; + + typedef IntermediateValues IntermediateValuesBaseType; + typedef IntermediateValues* IntermediateValuesType; + + typedef IntermediateValuesBaseType* IntermediateValuesTypeForSpatialAg; + +}; // class VCard + +/*! +** \brief Marginal Price +*/ +template +class PriceCSR: public Variable::IVariable, NextT, VCardPriceCSR> +{ +public: + //! Type of the next static variable + typedef NextT NextType; + //! VCard + typedef VCardPriceCSR VCardType; + //! Ancestor + typedef Variable::IVariable, NextT, VCardType> AncestorType; + + //! List of expected results + typedef typename VCardType::ResultsType ResultsType; + + typedef VariableAccessor VariableAccessorType; + + enum + { + //! How many items have we got + count = 1 + NextT::count, + }; + + template + struct Statistics + { + enum + { + count = ((VCardType::categoryDataLevel & CDataLevel + && VCardType::categoryFileLevel & CFile) + ? (NextType::template Statistics::count + + VCardType::columnCount * ResultsType::count) + : NextType::template Statistics::count), + }; + }; + +public: + ~PriceCSR() + { + delete[] pValuesForTheCurrentYear; + } + + void initializeFromStudy(Data::Study& study) + { + pNbYearsParallel = study.maxNbYearsInParallel; + + // Average thoughout all years + InitializeResultsFromStudy(AncestorType::pResults, study); + + // Intermediate values + pValuesForTheCurrentYear = new VCardType::IntermediateValuesBaseType[pNbYearsParallel]; + for (unsigned int numSpace = 0; numSpace < pNbYearsParallel; numSpace++) + { + pValuesForTheCurrentYear[numSpace].initializeFromStudy(study); + } + + // Next + NextType::initializeFromStudy(study); + } + + template + static void InitializeResultsFromStudy(R& results, Data::Study& study) + { + VariableAccessorType::InitializeAndReset(results, study); + } + + void initializeFromArea(Data::Study* study, Data::Area* area) + { + // Next + NextType::initializeFromArea(study, area); + } + + void initializeFromLink(Data::Study* study, Data::AreaLink* link) + { + // Next + NextType::initializeFromAreaLink(study, link); + } + + void simulationBegin() + { + // Next + NextType::simulationBegin(); + } + + void simulationEnd() + { + NextType::simulationEnd(); + } + + void yearBegin(uint year, unsigned int numSpace) + { + // Reset the values for the current year + pValuesForTheCurrentYear[numSpace].reset(); + // Next variable + NextType::yearBegin(year, numSpace); + } + + void yearEndBuild(State& state, unsigned int year) + { + // Next variable + NextType::yearEndBuild(state, year); + } + + void yearEnd(uint year, unsigned int numSpace) + { + // Compute all statistics for the current year (daily,weekly,monthly) + pValuesForTheCurrentYear[numSpace].computeAveragesForCurrentYearFromHourlyResults(); + + // Next variable + NextType::yearEnd(year, numSpace); + } + + void computeSummary(std::map& numSpaceToYear, + unsigned int nbYearsForCurrentSummary) + { + for (unsigned int numSpace = 0; numSpace < nbYearsForCurrentSummary; ++numSpace) + { + // Merge all those values with the global results + AncestorType::pResults.merge(numSpaceToYear[numSpace] /*year*/, + pValuesForTheCurrentYear[numSpace]); + } + + // Next variable + NextType::computeSummary(numSpaceToYear, nbYearsForCurrentSummary); + } + + void hourBegin(uint hourInTheYear) + { + // Next variable + NextType::hourBegin(hourInTheYear); + } + + void hourForEachArea(State& state, unsigned int numSpace) + { + pValuesForTheCurrentYear[numSpace][state.hourInTheYear] = -state.hourlyResults + ->CoutsMarginauxHorairesCSR + [state.hourInTheWeek]; + // Next variable + NextType::hourForEachArea(state, numSpace); + } + + Antares::Memory::Stored::ConstReturnType retrieveRawHourlyValuesForCurrentYear( + uint, + unsigned int numSpace) const + { + return pValuesForTheCurrentYear[numSpace].hour; + } + + void localBuildAnnualSurveyReport(SurveyResults& results, + int fileLevel, + int precision, + unsigned int numSpace) const + { + // Initializing external pointer on current variable non applicable status + results.isCurrentVarNA = AncestorType::isNonApplicable; + + if (AncestorType::isPrinted[0]) + { + // Write the data for the current year + results.variableCaption = VCardType::Caption(); + results.variableUnit = VCardType::Unit(); + pValuesForTheCurrentYear[numSpace] + .template buildAnnualSurveyReport(results, fileLevel, precision); + } + } + +private: + //! Intermediate values for each year + typename VCardType::IntermediateValuesType pValuesForTheCurrentYear; + unsigned int pNbYearsParallel; + +}; // class PriceCSR + +} // namespace Antares::Solver::Variable::Economy 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() diff --git a/src/tests/src/libs/antares/yaml-parser/CMakeLists.txt b/src/tests/src/libs/antares/yaml-parser/CMakeLists.txt index dbb1a0535a..d6da0bec19 100644 --- a/src/tests/src/libs/antares/yaml-parser/CMakeLists.txt +++ b/src/tests/src/libs/antares/yaml-parser/CMakeLists.txt @@ -2,4 +2,4 @@ include(${CMAKE_SOURCE_DIR}/tests/macros.cmake) add_boost_test(yaml-parser-test SRC test_yaml_parser.cpp - LIBS yaml-cpp) + LIBS yaml-cpp::yaml-cpp)