6#include "delpi/solver/LpSolver.h"
11#if DELPI_ENABLED_QSOPTEX
12#include "delpi/solver/QsoptexLpSolver.h"
14#if DELPI_ENABLED_SOPLEX
15#include "delpi/solver/SoplexLpSolver.h"
24#include <unordered_map>
25#include <unordered_set>
28#include "delpi/solver/DelpiLpSolver.h"
29#include "delpi/util/error.h"
34bool IsYes(std::string value) {
36 std::ranges::transform(value, value.begin(), [](
const unsigned char c) { return std::tolower(c); });
37 return value ==
"yes" || value ==
"true" || value ==
"1" || value ==
"on";
43 stats_{config.with_timings(), class_name},
54std::unique_ptr<LpSolver> LpSolver::GetInstance(
const Config& config) {
55 switch (config.lp_solver()) {
57 return std::make_unique<SoplexLpSolver>(config);
59 return std::make_unique<QsoptexLpSolver>(config);
61 return std::make_unique<DelpiLpSolver>(config);
69 const std::string& expected =
info_.at(
":status");
78std::unordered_map<Variable, mpq_class> LpSolver::model()
const {
return model(
solution_); }
79std::unordered_map<Variable, mpq_class> LpSolver::model(
const std::vector<mpq_class>& x)
const {
80 if (x.empty())
return {};
81 DELPI_ASSERT(
col_to_var_.size() == x.size(),
"All variables must appear in the solution");
82 std::unordered_map<Variable, mpq_class> model;
89 DELPI_ASSERT(!
var_to_col_.contains(
column.var),
"Variable already exists in the LP.");
93 DELPI_ASSERT(!
var_to_col_.contains(
var),
"Variable already exists in the LP.");
98 DELPI_ASSERT(!
var_to_col_.contains(
var),
"Variable already exists in the LP.");
103 DELPI_ASSERT(!
var_to_col_.contains(
var),
"Variable already exists in the LP.");
116 return AddRow(formula.expression(), formula.kind(), formula.rhs());
119 return AddRow(lhs.addends(), sense, rhs);
122std::vector<Formula> LpSolver::constraints()
const {
123 std::vector<Formula> constraints;
124 constraints.reserve(num_rows() + num_columns());
126 for (
int i = 0; i < num_rows(); ++i) {
127 const auto [addends, lb, ub] =
row(i);
128 if (lb.has_value() && ub.has_value()) {
129 if (lb.value() == ub.value()) {
132 constraints.emplace_back(Expression{addends},
FormulaKind::Leq, ub.value());
133 constraints.emplace_back(Expression{addends},
FormulaKind::Geq, lb.value());
135 }
else if (lb.has_value()) {
136 constraints.emplace_back(Expression{addends},
FormulaKind::Geq, lb.value());
137 }
else if (ub.has_value()) {
138 constraints.emplace_back(Expression{addends},
FormulaKind::Leq, ub.value());
142 for (
int i = 0; i < num_columns(); ++i) {
143 const auto [
var, lb, ub, obj] =
column(i);
144 if (lb.has_value() && ub.has_value()) {
145 if (lb.value() == ub.value()) {
151 }
else if (lb.has_value()) {
153 }
else if (ub.has_value()) {
160 DELPI_ASSERT(size >= 0,
"Invalid number of columns.");
167 DELPI_TRACE_FMT(
"LpSolver::SetOption({}, {})", key, value);
169 config_.m_csv().SetFromFile(IsYes(value));
170 }
else if (key ==
":silent") {
171 config_.m_silent().SetFromFile(IsYes(value));
172 }
else if (key ==
":with-timings") {
173 config_.m_with_timings().SetFromFile(IsYes(value));
174 }
else if (key ==
":delta") {
175 config_.m_delta().SetFromFile(std::stod(value));
176 }
else if (key ==
":continuous-output") {
177 config_.m_continuous_output().SetFromFile(IsYes(value));
178 }
else if (key ==
":verbosity") {
179 config_.m_verbose_delpi().SetFromFile(std::stoi(value));
180 }
else if (key ==
":simplex-verbosity") {
181 config_.m_verbose_simplex().SetFromFile(std::stoi(value));
182 }
else if (key ==
":produce-models") {
183 config_.m_produce_models().SetFromFile(IsYes(value));
184 }
else if (key ==
":timeout") {
185 config_.m_timeout().SetFromFile(std::stoi(value));
187 DELPI_ERROR_FMT(
"Unknown option: {} = {}. Ignored", key, value);
201 for (
int i = 0; i < static_cast<int>(objective.size()); ++i)
SetObjective(i, objective.at(i));
204 DELPI_ASSERT(num_rows() > 0,
"Cannot optimise without rows.");
205 DELPI_ASSERT(num_columns() > 0,
"Cannot optimise without columns.");
206 DELPI_DEBUG(
"LpSolver::Solve()");
208 stats_.solver_stats.Increase();
219 DELPI_DEBUG_FMT(
"LpSolver::AddColumns({})", columns.size());
223template <TypedIterable<std::pair<const Variable, mpq_
class>> T>
225 DELPI_TRACE_FMT(
"LpSolver::Maximise({})", objective_function);
231template <TypedIterable<std::pair<const Variable, mpq_
class>> T>
233 DELPI_TRACE_FMT(
"LpSolver::Minimise({})", objective_function);
240 for (
int i = 0; i < num_columns(); ++i)
SetObjective(i, 0);
244 DELPI_TRACE_FMT(
"LpSolver::ConflictingExpected({})", result);
245 switch (expected()) {
262 const std::unordered_map m{model()};
263 for (
const Formula& constraint : constraints()) {
264 if (!constraint.Evaluate(m)) {
265 DELPI_ERROR_FMT(
"Constraint {} violated by the model", constraint);
276 const mpq_class& ub) {
278 if (addends.size() != 1u)
return false;
280 const mpq_class& coeff = addends.front().second;
282 SetBound(addends.front().first, lb, ub);
283 }
else if (coeff > 0) {
286 DELPI_ASSERT(coeff != 0,
"Coefficient must be non-zero");
293std::ostream& operator<<(std::ostream& os,
const LpSolver& solver) {
295 os <<
"num_columns: " << solver.num_columns() <<
", ";
296 os <<
"num_rows: " << solver.num_rows() <<
", ";
297 os <<
"ninfinity: " << solver.ninfinity() <<
", ";
298 os <<
"infinity: " << solver.infinity() <<
", ";
299 os <<
"stats: " << solver.stats() <<
", ";
300 os <<
"config: " << solver.config() <<
", ";
301 if (!solver.solution().empty()) {
303 for (
int i = 0; i < static_cast<int>(solver.solution().size()); ++i) {
304 os << solver.variables().at(i) <<
" = " << solver.solution().at(i) <<
", ";
313template void LpSolver::Maximise(
const std::unordered_set<std::pair<Variable, mpq_class>>&);
320template void LpSolver::Minimise(
const std::unordered_set<std::pair<Variable, mpq_class>>&);
Simple dataclass used to store the configuration of the program.
@ SOPLEX
Soplex Solver. Default option.
Represents a symbolic form of an expression.
Facade class that hides the underlying LP solver used by delpi.
virtual void ReserveColumns(int size)
Reserve space for the given number of columns and rows.
const Variable & var(const int column) const
Shorthand notation to get the real variable linked with column column.
void SetObjective(const Expression &objective)
Set the objective coefficients of the LP problem to the given objective.
bool SetSimpleBoundInsteadOfAddRow(const std::vector< Expression::Addend > &addends, const mpq_class &lb, const mpq_class &ub)
Check whether the row that is about to be added is a simple bound.
mpq_class infinity_
Infinity threshold value.
PartialSolveCallback partial_solve_cb_
Callback to call after solving the LP problem with a partial solution.
bool is_min_
Whether this is a minimization or maximization LP problem.
mpq_class ninfinity_
Negative infinity threshold value.
RowIndex AddRow(const Row &row)
Add a new row to the LP problem with the given row.
void SetInfo(const std::string &key, const std::string &value)
Set the information stored under the given key to the given value.
Config config_
Configuration to use.
virtual Column column(int column_idx) const =0
Get the column at the given column_idx index.
SolveCallback solve_cb_
Callback to call after solving the LP problem.
virtual LpResult SolveCore()=0
Internal method that optimises the LP problem with the given delta.
bool CheckAgainstExpected(LpResult result) const
Check whether the result obtained by the solver is compatible with the one collected from the file.
std::vector< mpq_class > solution_
Solution vector.
std::unordered_map< Variable, int > var_to_col_
Theory column ⇔ Variable.
mpq_class obj_ub_
Upper bound on the objective value, if any.
bool Verify() const
Verify that the current solution_ satisfies all the constraints in the LpSolver.
std::vector< mpq_class > dual_solution_
Dual solution vector.
virtual void AddColumns(const std::span< Column > &columns)
Add a vector of columns to the LP problem.
void EnsureSense(bool is_min)
Make sure the LP solvers are aware of the sense of the LP problem (minimisation or maximisation).
virtual void AddRows(const std::span< Row > &rows)
Add a vector of rows to the LP problem.
mpq_class obj_lb_
Lower bound on the objective value, if any.
void Maximise(const Expression &objective_function)
Set the objective_function to maximise while being subject to all the constraints.
std::vector< Variable > col_to_var_
Literal ⇔ lp row.
ColumnIndex AddColumn(const Column &column)
Add a new column to the LP problem.
void Minimise(const Expression &objective_function)
Set the objective_function to minimise while being subject to all the constraints.
LpSolver(mpq_class ninfinity, mpq_class infinity, Config config={}, const std::string &class_name="LpSolver")
Construct a new LpSolver object with the given config.
virtual void EnsureSenseCore()=0
Make sure the LP solvers are aware of the sense of the LP problem (minimisation or maximisation).
void SetOption(const std::string &key, const std::string &value)
Set the option identified by the given key to the given value.
std::unordered_map< std::string, std::string > info_
Generic information map. Generally collected from the file.
virtual void SetBound(Variable var, const mpq_class &lb, const mpq_class &ub)=0
Set the bounds of a var in the LP problem to the given lb and ub.
LpStats stats_
Statistics of the solver.
virtual void ReserveRows(int size)
Reserve space for the given number of rows.
void ResetObjective()
Set all coefficients in the objective function to zero.
const std::string & GetInfo(const std::string &key) const
Retrieve the information stored under the given key.
LpResult Solve()
Optimise the LP problem with the given delta.
virtual Row row(int row_idx) const =0
Get the row at the given row_idx index.
The TimeGuard wraps a timer object and pauses it when the guard object is destructed.
Global namespace for the delpi library.
LpResult
Possible outcomes of the LP solver.
@ INFEASIBLE
The problem is infeasible.
@ DELTA_OPTIMAL
The delta-relaxation of the problem is optimal.
@ UNBOUNDED
The problem is unbounded.
@ ERROR
An error occurred.
@ OPTIMAL
The problem is optimal.
@ UNSOLVED
The solver has not yet been run.
FormulaKind
Kinds of symbolic formulas.
Convenient structure representing a column in the LP solver.
IterationStats solver_stats
Time spent in the solver and number of iterations.
Structure representing a row in the LP solver in the form of a linear combination of variables.