Skip to content

Commit

Permalink
Unfeasible problem analyzer: check variable bounds consistency (#1689)
Browse files Browse the repository at this point in the history
The analyzer has been refactored into a list of particular
analysis, to be able to add more in the future.
  • Loading branch information
guilpier-code authored Nov 3, 2023
1 parent a630959 commit 2242747
Show file tree
Hide file tree
Showing 21 changed files with 609 additions and 211 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ jobs:
if: ${{ env.IS_PUSH == 'true' }}
run: |
cd _build
ctest -C Release --output-on-failure -R unfeasible
ctest -C Release --output-on-failure -R "^unfeasible$"
- name: Run unit and end-to-end tests
if: ${{ env.IS_PUSH == 'true' }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/windows-vcpkg.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ jobs:
- name: Run unfeasibility-related tests
run: |
cd _build
ctest -C Release --output-on-failure -R unfeasible
ctest -C Release --output-on-failure -R "^unfeasible$"
- name: Run unit and end-to-end tests
run: |
Expand Down
15 changes: 11 additions & 4 deletions src/solver/infeasible-problem-analysis/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
project(infeasible-problem-analysis)

set(SRC_INFEASIBLE_PROBLEM_ANALYSIS
problem.cpp
problem.h
unfeasibility-analysis.h
constraint-slack-analysis.h
constraint-slack-analysis.cpp
variables-bounds-consistency.h
variables-bounds-consistency.cpp
unfeasible-pb-analyzer.cpp
unfeasible-pb-analyzer.h
report.h
report.cpp
constraint.h
constraint.cpp
exceptions.h
exceptions.cpp
)

add_library(infeasible_problem_analysis ${SRC_INFEASIBLE_PROBLEM_ANALYSIS})
target_link_libraries(infeasible_problem_analysis
PUBLIC ortools::ortools sirius_solver
utils #ortools-utils, not Antares::utils
)
target_include_directories(infeasible_problem_analysis
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/..
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#include <regex>
#include "constraint-slack-analysis.h"
#include <antares/logs/logs.h>
#include "report.h"

using namespace operations_research;

namespace Antares::Optimization
{

void ConstraintSlackAnalysis::run(MPSolver* problem)
{
addSlackVariables(problem);
if (slackVariables_.empty())
{
logs.error() << title() << " : no constraints have been selected";
return;
}

buildObjective(problem);

const MPSolver::ResultStatus status = problem->Solve();
if ((status != MPSolver::OPTIMAL) && (status != MPSolver::FEASIBLE))
{
logs.error() << title() << " : modified linear problem could not be solved";
return;
}

hasDetectedInfeasibilityCause_ = true;
}

void ConstraintSlackAnalysis::addSlackVariables(MPSolver* problem)
{
/* Optimization:
We assess that less than 1 every 3 constraint will match
the regex. If more, push_back may force the copy of memory blocks.
This should not happen in most cases.
*/
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())
{
if (std::regex_search(constraint->name(), rgx))
{
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);
}
}
}
}

void ConstraintSlackAnalysis::buildObjective(MPSolver* problem) const
{
MPObjective* objective = problem->MutableObjective();
// Reset objective function
objective->Clear();
// Only slack variables have a non-zero cost
for (const MPVariable* slack : slackVariables_)
{
objective->SetCoefficient(slack, 1.);
}
objective->SetMinimization();
}

void ConstraintSlackAnalysis::printReport() const
{
InfeasibleProblemReport report(slackVariables_);
report.prettyPrint();
}

} // namespace Antares::Optimization
30 changes: 30 additions & 0 deletions src/solver/infeasible-problem-analysis/constraint-slack-analysis.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#pragma once

#include "unfeasibility-analysis.h"

namespace Antares::Optimization
{

/*!
* That particular analysis relaxes all constraints by
* adding slack variables for each one.
*/
class ConstraintSlackAnalysis : public UnfeasibilityAnalysis
{
public:
ConstraintSlackAnalysis() = default;
~ConstraintSlackAnalysis() override = default;

void run(operations_research::MPSolver* problem) override;
void printReport() const override;
std::string title() const override { return "Slack variables analysis"; }

private:
void buildObjective(operations_research::MPSolver* problem) const;
void addSlackVariables(operations_research::MPSolver* problem);

std::vector<const operations_research::MPVariable*> slackVariables_;
const std::string constraint_name_pattern = "^AreaHydroLevel::|::hourly::|::daily::|::weekly::|^FictiveLoads::";
};

} // namespace Antares::Optimization
18 changes: 0 additions & 18 deletions src/solver/infeasible-problem-analysis/exceptions.cpp

This file was deleted.

21 changes: 0 additions & 21 deletions src/solver/infeasible-problem-analysis/exceptions.h

This file was deleted.

90 changes: 0 additions & 90 deletions src/solver/infeasible-problem-analysis/problem.cpp

This file was deleted.

32 changes: 0 additions & 32 deletions src/solver/infeasible-problem-analysis/problem.h

This file was deleted.

43 changes: 23 additions & 20 deletions src/solver/infeasible-problem-analysis/report.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,42 @@
#include <antares/logs/logs.h>
#include <algorithm>

using namespace operations_research;

static bool compareSlackSolutions(const Antares::Optimization::Constraint& a,
const Antares::Optimization::Constraint& b)
{
return a.getSlackValue() > b.getSlackValue();
}

namespace Antares
namespace Antares::Optimization
{
namespace Optimization
InfeasibleProblemReport::InfeasibleProblemReport(const std::vector<const MPVariable*>& slackVariables)
{
InfeasibleProblemReport::InfeasibleProblemReport(
const std::vector<const operations_research::MPVariable*>& slackVariables)
turnSlackVarsIntoConstraints(slackVariables);
sortConstraints();
trimConstraints();
}

void InfeasibleProblemReport::turnSlackVarsIntoConstraints(const std::vector<const MPVariable*>& slackVariables)
{
for (const operations_research::MPVariable* slack : slackVariables)
for (const MPVariable* slack : slackVariables)
{
append(slack->name(), slack->solution_value());
mConstraints.emplace_back(slack->name(), slack->solution_value());
}
trim();
}

void InfeasibleProblemReport::append(const std::string& constraintName, double value)
void InfeasibleProblemReport::sortConstraints()
{
mConstraints.emplace_back(constraintName, value);
std::sort(std::begin(mConstraints), std::end(mConstraints), ::compareSlackSolutions);
}

void InfeasibleProblemReport::trimConstraints()
{
if (nbVariables <= mConstraints.size())
{
mConstraints.resize(nbVariables);
}
}

void InfeasibleProblemReport::extractItems()
Expand Down Expand Up @@ -74,14 +87,4 @@ void InfeasibleProblemReport::prettyPrint()
logSuspiciousConstraints();
}

void InfeasibleProblemReport::trim()
{
std::sort(std::begin(mConstraints), std::end(mConstraints), ::compareSlackSolutions);
if (nbVariables <= mConstraints.size())
{
mConstraints.resize(nbVariables);
}
}

} // namespace Optimization
} // namespace Antares
} // namespace Antares::Optimization
Loading

0 comments on commit 2242747

Please sign in to comment.