diff --git a/src/solver/infeasible-problem-analysis/CMakeLists.txt b/src/solver/infeasible-problem-analysis/CMakeLists.txt index 424235facc..72b93ad153 100644 --- a/src/solver/infeasible-problem-analysis/CMakeLists.txt +++ b/src/solver/infeasible-problem-analysis/CMakeLists.txt @@ -10,8 +10,8 @@ set(SRC_INFEASIBLE_PROBLEM_ANALYSIS include/antares/solver/infeasible-problem-analysis/unfeasible-pb-analyzer.h include/antares/solver/infeasible-problem-analysis/report.h report.cpp - include/antares/solver/infeasible-problem-analysis/constraint.h - constraint.cpp + include/antares/solver/infeasible-problem-analysis/watched-constraints.h + watched-constraints.cpp ) add_library(infeasible_problem_analysis ${SRC_INFEASIBLE_PROBLEM_ANALYSIS}) diff --git a/src/solver/infeasible-problem-analysis/constraint-slack-analysis.cpp b/src/solver/infeasible-problem-analysis/constraint-slack-analysis.cpp index 785d4670f8..099ac70db6 100644 --- a/src/solver/infeasible-problem-analysis/constraint-slack-analysis.cpp +++ b/src/solver/infeasible-problem-analysis/constraint-slack-analysis.cpp @@ -31,12 +31,23 @@ using namespace operations_research; +namespace +{ +bool compareSlackSolutions(const MPVariable* a, const MPVariable* b) +{ + return a->solution_value() > b->solution_value(); +} + +constexpr unsigned int nbMaxSlackVarsToKeep = 10; +} // namespace + namespace Antares::Optimization { void ConstraintSlackAnalysis::run(MPSolver* problem) { - addSlackVariables(problem); + selectConstraintsToWatch(problem); + addSlackVariablesToConstraints(problem); if (slackVariables_.empty()) { logs.error() << title() << " : no constraints have been selected"; @@ -53,9 +64,21 @@ void ConstraintSlackAnalysis::run(MPSolver* problem) } hasDetectedInfeasibilityCause_ = true; + + sortSlackVariablesByValue(); + trimSlackVariables(); +} + +void ConstraintSlackAnalysis::selectConstraintsToWatch(MPSolver* problem) +{ + ConstraintsFactory factory; + std::regex rgx = factory.constraintsFilter(); + std::ranges::copy_if(problem->constraints(), + std::back_inserter(constraintsToWatch_), + [&rgx](auto* c) { return std::regex_search(c->name(), rgx); }); } -void ConstraintSlackAnalysis::addSlackVariables(MPSolver* problem) +void ConstraintSlackAnalysis::addSlackVariablesToConstraints(MPSolver* problem) { /* Optimization: We assess that less than 1 every 3 constraint will match @@ -64,29 +87,21 @@ void ConstraintSlackAnalysis::addSlackVariables(MPSolver* problem) */ const unsigned int selectedConstraintsInverseRatio = 3; slackVariables_.reserve(problem->NumConstraints() / selectedConstraintsInverseRatio); - std::regex rgx(constraint_name_pattern); const double infinity = MPSolver::infinity(); - for (MPConstraint* constraint: problem->constraints()) + for (MPConstraint* c: constraintsToWatch_) { - if (std::regex_search(constraint->name(), rgx)) + if (c->lb() > -infinity) { - if (constraint->lb() != -infinity) - { - const MPVariable* slack = problem->MakeNumVar(0, - infinity, - constraint->name() + "::low"); - constraint->SetCoefficient(slack, 1.); - slackVariables_.push_back(slack); - } - - if (constraint->ub() != infinity) - { - const MPVariable* slack = problem->MakeNumVar(0, - infinity, - constraint->name() + "::up"); - constraint->SetCoefficient(slack, -1.); - slackVariables_.push_back(slack); - } + const MPVariable* slack = problem->MakeNumVar(0, infinity, c->name() + "::low"); + c->SetCoefficient(slack, 1.); + slackVariables_.push_back(slack); + } + + if (c->ub() < infinity) + { + const MPVariable* slack = problem->MakeNumVar(0, infinity, c->name() + "::up"); + c->SetCoefficient(slack, -1.); + slackVariables_.push_back(slack); } } } @@ -104,10 +119,22 @@ void ConstraintSlackAnalysis::buildObjective(MPSolver* problem) const objective->SetMinimization(); } +void ConstraintSlackAnalysis::sortSlackVariablesByValue() +{ + std::sort(std::begin(slackVariables_), std::end(slackVariables_), ::compareSlackSolutions); +} + +void ConstraintSlackAnalysis::trimSlackVariables() +{ + unsigned int nbSlackVars = slackVariables_.size(); + slackVariables_.resize(std::min(nbMaxSlackVarsToKeep, nbSlackVars)); +} + void ConstraintSlackAnalysis::printReport() const { InfeasibleProblemReport report(slackVariables_); - report.prettyPrint(); + report.logSuspiciousConstraints(); + report.logInfeasibilityCauses(); } } // namespace Antares::Optimization diff --git a/src/solver/infeasible-problem-analysis/constraint.cpp b/src/solver/infeasible-problem-analysis/constraint.cpp deleted file mode 100644 index 2c4fd6d085..0000000000 --- a/src/solver/infeasible-problem-analysis/constraint.cpp +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright 2007-2024, RTE (https://www.rte-france.com) - * See AUTHORS.txt - * SPDX-License-Identifier: MPL-2.0 - * This file is part of Antares-Simulator, - * Adequacy and Performance assessment for interconnected energy networks. - * - * Antares_Simulator is free software: you can redistribute it and/or modify - * it under the terms of the Mozilla Public Licence 2.0 as published by - * the Mozilla Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * Antares_Simulator is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Mozilla Public Licence 2.0 for more details. - * - * You should have received a copy of the Mozilla Public Licence 2.0 - * along with Antares_Simulator. If not, see . - */ -#include "antares/solver/infeasible-problem-analysis/constraint.h" - -#include -#include -#include -#include - -#include -#include -#include - -namespace -{ -const std::string kUnknown = ""; -} - -namespace Antares::Optimization -{ -Constraint::Constraint(const std::string& name, const double slackValue): - name_(name), - slackValue_(slackValue) -{ -} - -void Constraint::extractComponentsFromName() -{ - boost::algorithm::split_regex(nameComponents_, name_, boost::regex("::")); -} - -double Constraint::getSlackValue() const -{ - return slackValue_; -} - -class StringIsNotWellFormated: public std::runtime_error -{ -public: - StringIsNotWellFormated(const std::string& error_message): - std::runtime_error(error_message) - { - } -}; - -std::string StringBetweenAngleBrackets(const std::string& constraintName) -{ - std::vector split_name; - boost::split(split_name, constraintName, boost::is_any_of("<>")); - - std::string err_msg = "Error: "; - if (split_name.size() < 3) - { - err_msg += "constraint name '" + constraintName + "' misses '<' and/or '>' bracket"; - throw StringIsNotWellFormated(err_msg); - } - if (split_name[1].empty()) - { - err_msg += "constraint name '" + constraintName + "' must be of format '**'"; - throw StringIsNotWellFormated(err_msg); - } - return split_name[1]; -} - -std::string Constraint::areaName() const -{ - return StringBetweenAngleBrackets(nameComponents_.at(1)); -} - -std::string Constraint::timeStep() const -{ - return StringBetweenAngleBrackets(nameComponents_.at(nameComponents_.size() - 2)); -} - -ConstraintType Constraint::type() const -{ - assert(nameComponents_.size() > 1); - if (nameComponents_.at(1) == "hourly") - { - return ConstraintType::binding_constraint_hourly; - } - if (nameComponents_.at(1) == "daily") - { - return ConstraintType::binding_constraint_daily; - } - if (nameComponents_.at(1) == "weekly") - { - return ConstraintType::binding_constraint_weekly; - } - if (nameComponents_.at(0) == "FictiveLoads") - { - return ConstraintType::fictitious_load; - } - if (nameComponents_.at(0) == "AreaHydroLevel") - { - return ConstraintType::hydro_reservoir_level; - } - if (nameComponents_.at(0) == "HydroPower") - { - return ConstraintType::hydro_production_weekly; - } - if (nameComponents_.at(0) == "Level") - { - return ConstraintType::short_term_storage_level; - } - return ConstraintType::none; -} - -std::string Constraint::shortName() const -{ - return nameComponents_.at(0); -} - -std::string Constraint::STSname() const -{ - return StringBetweenAngleBrackets(nameComponents_.at(2)); -} - -std::string Constraint::prettyPrint() const -{ - switch (type()) - { - case ConstraintType::binding_constraint_hourly: - return "Hourly binding constraint '" + shortName() + "' at hour " + timeStep(); - case ConstraintType::binding_constraint_daily: - return "Daily binding constraint '" + shortName() + "' at day " + timeStep(); - case ConstraintType::binding_constraint_weekly: - return "Weekly binding constraint '" + shortName(); - case ConstraintType::fictitious_load: - return "Last resort shedding status at area '" + areaName() + "' at hour " + timeStep(); - case ConstraintType::hydro_reservoir_level: - return "Hydro reservoir constraint at area '" + areaName() + "' at hour " + timeStep(); - case ConstraintType::hydro_production_weekly: - return "Hydro weekly production at area '" + areaName() + "'"; - case ConstraintType::short_term_storage_level: - return "Short-term-storage reservoir constraint at area '" + areaName() + "' in STS '" - + STSname() + "' at hour " + timeStep(); - default: - return kUnknown; - } -} -} // namespace Antares::Optimization diff --git a/src/solver/infeasible-problem-analysis/include/antares/solver/infeasible-problem-analysis/constraint-slack-analysis.h b/src/solver/infeasible-problem-analysis/include/antares/solver/infeasible-problem-analysis/constraint-slack-analysis.h index bb56228b2f..462d5f965f 100644 --- a/src/solver/infeasible-problem-analysis/include/antares/solver/infeasible-problem-analysis/constraint-slack-analysis.h +++ b/src/solver/infeasible-problem-analysis/include/antares/solver/infeasible-problem-analysis/constraint-slack-analysis.h @@ -23,9 +23,11 @@ #include #include "unfeasibility-analysis.h" +#include "watched-constraints.h" namespace operations_research { +class MPConstraint; class MPVariable; class MPSolver; } // namespace operations_research @@ -40,7 +42,6 @@ namespace Antares::Optimization class ConstraintSlackAnalysis: public UnfeasibilityAnalysis { public: - ConstraintSlackAnalysis() = default; ~ConstraintSlackAnalysis() override = default; void run(operations_research::MPSolver* problem) override; @@ -52,13 +53,14 @@ class ConstraintSlackAnalysis: public UnfeasibilityAnalysis } private: + void selectConstraintsToWatch(operations_research::MPSolver* problem); + void addSlackVariablesToConstraints(operations_research::MPSolver* problem); void buildObjective(operations_research::MPSolver* problem) const; - void addSlackVariables(operations_research::MPSolver* problem); + void sortSlackVariablesByValue(); + void trimSlackVariables(); + std::vector constraintsToWatch_; std::vector slackVariables_; - const std::string constraint_name_pattern = "^AreaHydroLevel::|::hourly::|::daily::|::weekly::|" - "^FictiveLoads::|^Level::|" - "^HydroPower::"; }; } // namespace Antares::Optimization diff --git a/src/solver/infeasible-problem-analysis/include/antares/solver/infeasible-problem-analysis/constraint.h b/src/solver/infeasible-problem-analysis/include/antares/solver/infeasible-problem-analysis/constraint.h deleted file mode 100644 index 52fb799692..0000000000 --- a/src/solver/infeasible-problem-analysis/include/antares/solver/infeasible-problem-analysis/constraint.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2007-2024, RTE (https://www.rte-france.com) - * See AUTHORS.txt - * SPDX-License-Identifier: MPL-2.0 - * This file is part of Antares-Simulator, - * Adequacy and Performance assessment for interconnected energy networks. - * - * Antares_Simulator is free software: you can redistribute it and/or modify - * it under the terms of the Mozilla Public Licence 2.0 as published by - * the Mozilla Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * Antares_Simulator is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * Mozilla Public Licence 2.0 for more details. - * - * You should have received a copy of the Mozilla Public Licence 2.0 - * along with Antares_Simulator. If not, see . - */ -#pragma once - -#include -#include - -namespace Antares::Optimization -{ -enum class ConstraintType -{ - binding_constraint_hourly, - binding_constraint_daily, - binding_constraint_weekly, - fictitious_load, - hydro_reservoir_level, - hydro_production_weekly, - short_term_storage_level, - none -}; - -class Constraint -{ -public: - Constraint() = default; - Constraint(const std::string& name, const double slackValue); - - double getSlackValue() const; - - void extractComponentsFromName(); - std::string prettyPrint() const; - ConstraintType type() const; - -private: - std::string name_; - std::vector nameComponents_; - double slackValue_; - - std::string areaName() const; - std::string STSname() const; - std::string timeStep() const; - std::string shortName() const; -}; -} // namespace Antares::Optimization diff --git a/src/solver/infeasible-problem-analysis/include/antares/solver/infeasible-problem-analysis/report.h b/src/solver/infeasible-problem-analysis/include/antares/solver/infeasible-problem-analysis/report.h index 898f4df359..ef242b1f36 100644 --- a/src/solver/infeasible-problem-analysis/include/antares/solver/infeasible-problem-analysis/report.h +++ b/src/solver/infeasible-problem-analysis/include/antares/solver/infeasible-problem-analysis/report.h @@ -21,10 +21,12 @@ #pragma once #include +#include #include #include -#include "constraint.h" +#include "ortools/linear_solver/linear_solver.h" +#include "watched-constraints.h" namespace operations_research { @@ -37,21 +39,14 @@ class InfeasibleProblemReport { public: InfeasibleProblemReport() = delete; - explicit InfeasibleProblemReport( - const std::vector& slackVariables); - void prettyPrint(); - -private: - void turnSlackVarsIntoConstraints( - const std::vector& slackVariables); - void sortConstraintsBySlackValue(); - void trimConstraints(); - void sortConstraintsByType(); + explicit InfeasibleProblemReport(const std::vector&); void logSuspiciousConstraints(); void logInfeasibilityCauses(); - std::vector constraints_; - std::map nbConstraintsByType_; - const unsigned int nbMaxVariables = 10; +private: + void buildConstraintsFromSlackVars(const std::vector&); + void filterConstraintsToOneByType(); + + std::vector> constraints_; }; } // namespace Antares::Optimization diff --git a/src/solver/infeasible-problem-analysis/include/antares/solver/infeasible-problem-analysis/watched-constraints.h b/src/solver/infeasible-problem-analysis/include/antares/solver/infeasible-problem-analysis/watched-constraints.h new file mode 100644 index 0000000000..1c1c533d71 --- /dev/null +++ b/src/solver/infeasible-problem-analysis/include/antares/solver/infeasible-problem-analysis/watched-constraints.h @@ -0,0 +1,113 @@ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace Antares::Optimization +{ +class WatchedConstraint +{ +public: + explicit WatchedConstraint(const std::string& name, const double slackValue); + virtual ~WatchedConstraint() = default; + virtual std::string infeasibility() = 0; + virtual std::string infeasibilityCause() = 0; + double slackValue() const; + +protected: + const std::vector& splitName() const; + +private: + std::vector splitName_; + double slack_value_; +}; + +class HourlyBC: public WatchedConstraint +{ + using WatchedConstraint::WatchedConstraint; + +public: + ~HourlyBC() override = default; + std::string infeasibility() override; + std::string infeasibilityCause() override; +}; + +class DailyBC: public WatchedConstraint +{ + using WatchedConstraint::WatchedConstraint; + +public: + ~DailyBC() override = default; + std::string infeasibility() override; + std::string infeasibilityCause() override; +}; + +class WeeklyBC: public WatchedConstraint +{ + using WatchedConstraint::WatchedConstraint; + +public: + ~WeeklyBC() override = default; + std::string infeasibility() override; + std::string infeasibilityCause() override; +}; + +class FictitiousLoad: public WatchedConstraint +{ + using WatchedConstraint::WatchedConstraint; + +public: + ~FictitiousLoad() override = default; + std::string infeasibility() override; + std::string infeasibilityCause() override; +}; + +class HydroLevel: public WatchedConstraint +{ + using WatchedConstraint::WatchedConstraint; + +public: + ~HydroLevel() override = default; + std::string infeasibility() override; + std::string infeasibilityCause() override; +}; + +class STS: public WatchedConstraint +{ + using WatchedConstraint::WatchedConstraint; + +public: + ~STS() override = default; + std::string infeasibility() override; + std::string infeasibilityCause() override; +}; + +class HydroProduction: public WatchedConstraint +{ + using WatchedConstraint::WatchedConstraint; + +public: + ~HydroProduction() override = default; + std::string infeasibility() override; + std::string infeasibilityCause() override; +}; + +class ConstraintsFactory +{ +public: + explicit ConstraintsFactory(); + std::unique_ptr create(const std::string&, const double) const; + std::regex constraintsFilter(); + +private: + std::map(const std::string&, const double)>> + regex_to_ctypes_; +}; + +} // namespace Antares::Optimization diff --git a/src/solver/infeasible-problem-analysis/report.cpp b/src/solver/infeasible-problem-analysis/report.cpp index 5154cffa0e..dc43da140a 100644 --- a/src/solver/infeasible-problem-analysis/report.cpp +++ b/src/solver/infeasible-problem-analysis/report.cpp @@ -21,107 +21,79 @@ #include "antares/solver/infeasible-problem-analysis/report.h" #include +#include +#include #include -#include "antares/solver/infeasible-problem-analysis/constraint.h" -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-parameter" -#include "ortools/linear_solver/linear_solver.h" -#pragma GCC diagnostic pop - -using namespace operations_research; - -static bool compareSlackSolutions(const Antares::Optimization::Constraint& a, - const Antares::Optimization::Constraint& b) -{ - return a.getSlackValue() > b.getSlackValue(); -} namespace Antares::Optimization { InfeasibleProblemReport::InfeasibleProblemReport( - const std::vector& slackVariables) + const std::vector& slackVariables) { - turnSlackVarsIntoConstraints(slackVariables); - sortConstraintsBySlackValue(); - trimConstraints(); - sortConstraintsByType(); + buildConstraintsFromSlackVars(slackVariables); } -void InfeasibleProblemReport::turnSlackVarsIntoConstraints( - const std::vector& slackVariables) +void InfeasibleProblemReport::buildConstraintsFromSlackVars( + const std::vector& slackVariables) { - for (const MPVariable* slack: slackVariables) + const ConstraintsFactory constraintsFactory; + for (const auto* slackVar: slackVariables) { - constraints_.emplace_back(slack->name(), slack->solution_value()); + auto constraint = constraintsFactory.create(slackVar->name(), slackVar->solution_value()); + if (constraint) + { + constraints_.push_back(std::move(constraint)); + } } } -void InfeasibleProblemReport::sortConstraintsBySlackValue() +bool lessTypeName(const std::shared_ptr a, + const std::shared_ptr b) { - std::sort(std::begin(constraints_), std::end(constraints_), ::compareSlackSolutions); + return std::type_index(typeid(*a)) < std::type_index(typeid(*b)); } -void InfeasibleProblemReport::trimConstraints() +bool sameType(const std::shared_ptr a, + const std::shared_ptr b) { - unsigned int nbConstraints = constraints_.size(); - constraints_.resize(std::min(nbMaxVariables, nbConstraints)); + return std::type_index(typeid(*a)) == std::type_index(typeid(*b)); } -void InfeasibleProblemReport::sortConstraintsByType() +bool greaterValue(const std::shared_ptr a, std::shared_ptr b) { - for (auto& c: constraints_) - { - c.extractComponentsFromName(); - nbConstraintsByType_[c.type()]++; - } + return a->slackValue() > b->slackValue(); +} + +void InfeasibleProblemReport::filterConstraintsToOneByType() +{ + // 1. Grouping constraints by C++ type (inside a group, order of instances remains unchanged) + std::ranges::stable_sort(constraints_, lessTypeName); + // 2. Keeping the first instances of each group, and rejecting others (= duplicates) to the end + // of vector + auto duplicates = std::ranges::unique(constraints_, sameType); + // 3. Removing trailing duplicates + constraints_.erase(duplicates.begin(), duplicates.end()); + // 4. Sorting remaining constraints by slack value (in descending order) + std::ranges::sort(constraints_, greaterValue); } void InfeasibleProblemReport::logSuspiciousConstraints() { - Antares::logs.error() << "The following constraints are suspicious (first = most suspicious)"; for (const auto& c: constraints_) { - Antares::logs.error() << c.prettyPrint(); + Antares::logs.error() << c->infeasibility(); } } void InfeasibleProblemReport::logInfeasibilityCauses() { + filterConstraintsToOneByType(); Antares::logs.error() << "Possible causes of infeasibility:"; - if (nbConstraintsByType_[ConstraintType::hydro_reservoir_level] > 0) - { - Antares::logs.error() << "* Hydro reservoir impossible to manage with cumulative options " - "\"hard bounds without heuristic\""; - } - if (nbConstraintsByType_[ConstraintType::hydro_production_weekly] > 0) - { - Antares::logs.error() << "* impossible to generate exactly the weekly hydro target"; - } - if (nbConstraintsByType_[ConstraintType::fictitious_load] > 0) - { - Antares::logs.error() << "* Last resort shedding status,"; - } - if (nbConstraintsByType_[ConstraintType::short_term_storage_level] > 0) - { - Antares::logs.error() - << "* Short-term storage reservoir level impossible to manage. Please check inflows, " - "lower & upper curves and initial level (if prescribed),"; - } - - const unsigned int bcCount = nbConstraintsByType_[ConstraintType::binding_constraint_hourly] - + nbConstraintsByType_[ConstraintType::binding_constraint_daily] - + nbConstraintsByType_[ConstraintType::binding_constraint_weekly]; - if (bcCount > 0) + for (const auto& c: constraints_) { - Antares::logs.error() << "* Binding constraints,"; + Antares::logs.error() << c->infeasibilityCause(); } } -void InfeasibleProblemReport::prettyPrint() -{ - logSuspiciousConstraints(); - logInfeasibilityCauses(); -} - } // namespace Antares::Optimization diff --git a/src/solver/infeasible-problem-analysis/watched-constraints.cpp b/src/solver/infeasible-problem-analysis/watched-constraints.cpp new file mode 100644 index 0000000000..e054f49f03 --- /dev/null +++ b/src/solver/infeasible-problem-analysis/watched-constraints.cpp @@ -0,0 +1,191 @@ +#include "antares/solver/infeasible-problem-analysis/watched-constraints.h" + +#include + +#include +#include +#include + +class StringIsNotWellFormated: public std::runtime_error +{ +public: + StringIsNotWellFormated(const std::string& error_message): + std::runtime_error(error_message) + { + } +}; + +std::string StringBetweenAngleBrackets(const std::string& constraintName) +{ + std::vector split_name; + boost::split(split_name, constraintName, boost::is_any_of("<>")); + + std::string err_msg = "Error: "; + if (split_name.size() < 3) + { + err_msg += "constraint name '" + constraintName + "' misses '<' and/or '>' bracket"; + throw StringIsNotWellFormated(err_msg); + } + if (split_name[1].empty()) + { + err_msg += "constraint name '" + constraintName + "' must be of format '**'"; + throw StringIsNotWellFormated(err_msg); + } + return split_name[1]; +} + +std::string timeStep(std::vector splitName) +{ + return StringBetweenAngleBrackets(splitName.at(splitName.size() - 2)); +} + +std::string shortName(std::vector splitName) +{ + return splitName.at(0); +} + +std::string areaName(std::vector splitName) +{ + return StringBetweenAngleBrackets(splitName.at(1)); +} + +std::string STSname(std::vector splitName) +{ + return StringBetweenAngleBrackets(splitName.at(2)); +} + +namespace Antares::Optimization +{ + +// --- Generic constraint --- +WatchedConstraint::WatchedConstraint(const std::string& name, const double slackValue): + slack_value_(slackValue) +{ + boost::algorithm::split_regex(splitName_, name, boost::regex("::")); +} + +const std::vector& WatchedConstraint::splitName() const +{ + return splitName_; +} + +double WatchedConstraint::slackValue() const +{ + return slack_value_; +} + +std::string HourlyBC::infeasibility() +{ + return "Hourly BC '" + shortName(splitName()) + "' at hour " + timeStep(splitName()); +} + +std::string HourlyBC::infeasibilityCause() +{ + return "* Hourly binding constraints."; +} + +// --- Daily BC constraint --- +std::string DailyBC::infeasibility() +{ + return "Daily BC '" + shortName(splitName()) + "' at day " + timeStep(splitName()); +} + +std::string DailyBC::infeasibilityCause() +{ + return "* Daily binding constraints,"; +} + +// --- Weekly BC constraint --- +std::string WeeklyBC::infeasibility() +{ + return "Weekly BC '" + shortName(splitName()); +} + +std::string WeeklyBC::infeasibilityCause() +{ + return "* Weekly binding constraints."; +} + +// --- Fictitious load constraint --- +std::string FictitiousLoad::infeasibility() +{ + return "Last resort shedding status at area '" + areaName(splitName()) + "' at hour " + + timeStep(splitName()); +} + +std::string FictitiousLoad::infeasibilityCause() +{ + return "* Last resort shedding status."; +} + +// --- Hydro level constraint --- +std::string HydroLevel::infeasibility() +{ + return "Hydro level constraint at area '" + areaName(splitName()) + "' at hour " + + timeStep(splitName()); +} + +std::string HydroLevel::infeasibilityCause() +{ + return "* Hydro reservoir impossible to manage with cumulative options " + "\"hard bounds without heuristic\""; +} + +// --- Short term storage constraint --- +std::string STS::infeasibility() +{ + return "Short-term-storage reservoir constraint at area '" + areaName(splitName()) + + "' in STS '" + STSname(splitName()) + "' at hour " + timeStep(splitName()); +} + +std::string STS::infeasibilityCause() +{ + return "* Short-term storage reservoir level impossible to manage. Please check inflows, " + "lower & upper curves and initial level (if prescribed),"; +} + +// --- Hydro production constraint --- +std::string HydroProduction::infeasibility() +{ + return "Hydro weekly production at area '" + areaName(splitName()) + "'"; +} + +std::string HydroProduction::infeasibilityCause() +{ + return "* impossible to generate exactly the weekly hydro target"; +} + +// --- Constraints factory --- +ConstraintsFactory::ConstraintsFactory() +{ + regex_to_ctypes_ = { + {"::hourly::", std::make_unique}, + {"::daily::", std::make_unique}, + {"::weekly::", std::make_unique}, + {"^FictiveLoads::", std::make_unique}, + {"^AreaHydroLevel::", std::make_unique}, + {"^Level::", std::make_unique}, + {"^HydroPower::", std::make_unique}}; +} + +std::unique_ptr ConstraintsFactory::create(const std::string& name, + const double value) const +{ + auto it = std::ranges::find_if(regex_to_ctypes_, + [&name](auto& pair) + { return std::regex_search(name, std::regex(pair.first)); }); + if (it != regex_to_ctypes_.end()) + { + return it->second(name, value); + } + return nullptr; +} + +std::regex ConstraintsFactory::constraintsFilter() +{ + auto keyView = std::views::keys(regex_to_ctypes_); + std::vector regex_ids = {keyView.begin(), keyView.end()}; + return std::regex(boost::algorithm::join(regex_ids, "|")); +} + +} // namespace Antares::Optimization diff --git a/src/tests/src/solver/infeasible-problem-analysis/test-unfeasible-problem-analyzer.cpp b/src/tests/src/solver/infeasible-problem-analysis/test-unfeasible-problem-analyzer.cpp index 8d960f7381..20086d505f 100644 --- a/src/tests/src/solver/infeasible-problem-analysis/test-unfeasible-problem-analyzer.cpp +++ b/src/tests/src/solver/infeasible-problem-analysis/test-unfeasible-problem-analyzer.cpp @@ -135,48 +135,51 @@ BOOST_AUTO_TEST_CASE(analysis_should_detect_inconsistent_variable_bounds) /*! * Creates a problem with 2 variables linked by 1 constraint: * - Variable 1 must be greater than 1 - * - Variable 2 must be lesser than -1 + * - Variable 2 must be smaller than -1 * - but if feasible is false, constraint enforces that variable 2 is greater than variable 1 --> * infeasible */ -std::unique_ptr createProblem(const std::string& constraintName, bool feasible) + +std::unique_ptr createProblem(const std::string& constraintName) { std::unique_ptr problem(MPSolver::CreateSolver("GLOP")); const double infinity = problem->infinity(); - auto var1 = problem->MakeNumVar(1, infinity, "var1"); - auto var2 = problem->MakeNumVar(-infinity, -1, "var2"); + problem->MakeNumVar(1, infinity, "var1"); + problem->MakeNumVar(-infinity, -1, "var2"); auto constraint = problem->MakeRowConstraint(constraintName); constraint->SetBounds(0, infinity); - if (feasible) - { - constraint->SetCoefficient(var1, 1); - constraint->SetCoefficient(var2, -1); - } - else - { - constraint->SetCoefficient(var1, -1); - constraint->SetCoefficient(var2, 1); - } return problem; } std::unique_ptr createFeasibleProblem(const std::string& constraintName) { - return createProblem(constraintName, true); + auto problem = createProblem(constraintName); + auto constraint = problem->LookupConstraintOrNull(constraintName); + auto var1 = problem->LookupVariableOrNull("var1"); + auto var2 = problem->LookupVariableOrNull("var2"); + constraint->SetCoefficient(var1, 1); + constraint->SetCoefficient(var2, -1); + return problem; } std::unique_ptr createUnfeasibleProblem(const std::string& constraintName) { - return createProblem(constraintName, false); + auto problem = createProblem(constraintName); + auto constraint = problem->LookupConstraintOrNull(constraintName); + auto var1 = problem->LookupVariableOrNull("var1"); + auto var2 = problem->LookupVariableOrNull("var2"); + constraint->SetCoefficient(var1, -1); + constraint->SetCoefficient(var2, 1); + return problem; } -static const std::string validConstraintNames[] = { - "BC-name-1::hourly::hour<36>", - "BC-name-2::daily::day<67>", - "BC-name-3::weekly::week<12>", - "FictiveLoads::hour<25>", - "AreaHydroLevel::hour<8>", -}; +static const std::string validConstraintNames[] = {"BC-name-1::hourly::hour<36>", + "BC-name-2::daily::day<67>", + "BC-name-3::weekly::week<12>", + "FictiveLoads::area::hour<25>", + "AreaHydroLevel::area::hour<8>", + "Level::area::hour<28>", + "HydroPower::area::week<45>"}; BOOST_DATA_TEST_CASE(analysis_should_detect_unfeasible_constraint, bdata::make(validConstraintNames),