delpi  0.0.1
DElta-complete LP solver
Loading...
Searching...
No Matches
LpSolver.cpp
1
6#include "delpi/solver/LpSolver.h"
7
8#include <ostream>
9#include <utility>
10
11#if DELPI_ENABLED_QSOPTEX
12#include "delpi/solver/QsoptexLpSolver.h"
13#endif
14#if DELPI_ENABLED_SOPLEX
15#include "delpi/solver/SoplexLpSolver.h"
16#endif
17
18#include <map>
19#include <memory>
20#include <ranges> // NOLINT(build/include_order): c++20 header
21#include <set>
22#include <span> // NOLINT(build/include_order): c++20 header
23#include <string>
24#include <unordered_map>
25#include <unordered_set>
26#include <vector>
27
28#include "delpi/solver/DelpiLpSolver.h"
29#include "delpi/util/error.h"
30
31namespace delpi {
32
33namespace {
34bool IsYes(std::string value) {
35 // NOLINTNEXTLINE(build/include_what_you_use): c++20 header ranges
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";
38}
39} // namespace
40
41LpSolver::LpSolver(mpq_class ninfinity, mpq_class infinity, Config config, const std::string& class_name)
42 : config_{std::move(config)},
43 stats_{config.with_timings(), class_name},
46 solution_{},
48 solve_cb_{},
50 is_min_{true},
51 ninfinity_{std::move(ninfinity)},
52 infinity_{std::move(infinity)} {}
53
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);
62 default:
63 DELPI_UNREACHABLE();
64 }
65}
66
67LpResult LpSolver::expected() const {
68 if (!info_.contains(":status")) return LpResult::UNSOLVED;
69 const std::string& expected = info_.at(":status");
70 if (expected == "optimal") return LpResult::OPTIMAL;
71 if (expected == "delta-optimal") return LpResult::DELTA_OPTIMAL;
72 if (expected == "infeasible") return LpResult::INFEASIBLE;
73 if (expected == "unbounded") return LpResult::UNBOUNDED;
74 if (expected == "error") return LpResult::ERROR;
75 return LpResult::UNSOLVED;
76}
77
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;
83 model.reserve(col_to_var_.size());
84 for (std::size_t i = 0; i < col_to_var_.size(); ++i) model.emplace(col_to_var_.at(i), x.at(i));
85 return model;
86}
87
88LpSolver::ColumnIndex LpSolver::AddColumn(const Column& column) {
89 DELPI_ASSERT(!var_to_col_.contains(column.var), "Variable already exists in the LP.");
90 return AddColumn(column.var, column.obj.value_or(ninfinity_), column.lb.value_or(infinity_), column.ub.value_or(0));
91}
92LpSolver::ColumnIndex LpSolver::AddColumn(const Variable& var) {
93 DELPI_ASSERT(!var_to_col_.contains(var), "Variable already exists in the LP.");
94 // Add a column representing this variable to the lp solver
95 return AddColumn(var, 0, 0, infinity_);
96}
97LpSolver::ColumnIndex LpSolver::AddColumn(const Variable& var, const mpq_class& obj) {
98 DELPI_ASSERT(!var_to_col_.contains(var), "Variable already exists in the LP.");
99 // Add a column representing this variable to the lp solver
100 return AddColumn(var, obj, 0, infinity_);
101}
102LpSolver::ColumnIndex LpSolver::AddColumn(const Variable& var, const mpq_class& lb, const mpq_class& ub) {
103 DELPI_ASSERT(!var_to_col_.contains(var), "Variable already exists in the LP.");
104 // Add a column representing this variable to the lp solver
105 return AddColumn(var, 0, lb, ub);
106}
107
108void LpSolver::AddRows(const std::span<Row>& rows) {
109 ReserveRows(num_rows() + rows.size());
110 for (const Row& row : rows) AddRow(row);
111}
112LpSolver::RowIndex LpSolver::AddRow(const Row& row) {
113 return AddRow(row.addends, row.lb.value_or(ninfinity_), row.ub.value_or(infinity_));
114}
115LpSolver::RowIndex LpSolver::AddRow(const Formula& formula) {
116 return AddRow(formula.expression(), formula.kind(), formula.rhs());
117}
118LpSolver::RowIndex LpSolver::AddRow(const Expression& lhs, const FormulaKind sense, const mpq_class& rhs) {
119 return AddRow(lhs.addends(), sense, rhs);
120}
121
122std::vector<Formula> LpSolver::constraints() const {
123 std::vector<Formula> constraints;
124 constraints.reserve(num_rows() + num_columns());
125 // Collect all the row constraints
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()) {
130 constraints.emplace_back(Expression{addends}, FormulaKind::Eq, ub.value());
131 } else {
132 constraints.emplace_back(Expression{addends}, FormulaKind::Leq, ub.value());
133 constraints.emplace_back(Expression{addends}, FormulaKind::Geq, lb.value());
134 }
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());
139 }
140 }
141 // Collect all the bound constraints over the variables
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()) {
146 constraints.emplace_back(Expression{var}, FormulaKind::Eq, ub.value());
147 } else {
148 constraints.emplace_back(Expression{var}, FormulaKind::Leq, ub.value());
149 constraints.emplace_back(Expression{var}, FormulaKind::Geq, lb.value());
150 }
151 } else if (lb.has_value()) {
152 constraints.emplace_back(Expression{var}, FormulaKind::Geq, lb.value());
153 } else if (ub.has_value()) {
154 constraints.emplace_back(Expression{var}, FormulaKind::Leq, ub.value());
155 }
156 }
157 return constraints;
158}
159void LpSolver::ReserveColumns([[maybe_unused]] const int size) {
160 DELPI_ASSERT(size >= 0, "Invalid number of columns.");
161}
162void LpSolver::ReserveRows([[maybe_unused]] const int size) { DELPI_ASSERT(size >= 0, "Invalid number of rows."); }
163
164const std::string& LpSolver::GetInfo(const std::string& key) const { return info_.at(key); }
165void LpSolver::SetInfo(const std::string& key, const std::string& value) { info_.emplace(key, value); }
166void LpSolver::SetOption(const std::string& key, const std::string& value) {
167 DELPI_TRACE_FMT("LpSolver::SetOption({}, {})", key, value);
168 if (key == ":csv") {
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));
186 } else {
187 DELPI_ERROR_FMT("Unknown option: {} = {}. Ignored", key, value);
188 }
189}
190
191void LpSolver::SetObjective(const Expression& objective) {
193 for (const auto& [column, value] : objective.addends()) SetObjective(column, value);
194}
195void LpSolver::SetObjective(const std::unordered_map<int, mpq_class>& objective) {
197 for (const auto& [column, value] : objective) SetObjective(column, value);
198}
199void LpSolver::SetObjective(const std::vector<mpq_class>& objective) {
201 for (int i = 0; i < static_cast<int>(objective.size()); ++i) SetObjective(i, objective.at(i));
202}
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()");
207 const TimerGuard timer_guard(&stats_.solver_stats.m_timer(), stats_.solver_stats.enabled());
208 stats_.solver_stats.Increase();
209 solution_.clear();
210 dual_solution_.clear();
211 const LpResult result = SolveCore();
213 return result;
214}
215void LpSolver::SetObjective(const Variable& var, const mpq_class& value) { SetObjective(var_to_col_.at(var), value); }
216
217void LpSolver::Maximise(const Expression& objective_function) { Maximise(objective_function.addends()); }
218void LpSolver::AddColumns(const std::span<Column>& columns) {
219 DELPI_DEBUG_FMT("LpSolver::AddColumns({})", columns.size());
220 ReserveColumns(num_columns() + columns.size());
221 for (const Column& column : columns) AddColumn(column);
222}
223template <TypedIterable<std::pair<const Variable, mpq_class>> T>
224void LpSolver::Maximise(const T& objective_function) {
225 DELPI_TRACE_FMT("LpSolver::Maximise({})", objective_function);
226 EnsureSense(false);
228 for (const auto& [var, coeff] : objective_function) SetObjective(var, coeff);
229}
230void LpSolver::Minimise(const Expression& objective_function) { Minimise(objective_function.addends()); }
231template <TypedIterable<std::pair<const Variable, mpq_class>> T>
232void LpSolver::Minimise(const T& objective_function) {
233 DELPI_TRACE_FMT("LpSolver::Minimise({})", objective_function);
234 EnsureSense(true);
236 for (const auto& [var, coeff] : objective_function) SetObjective(var, coeff);
237}
238
240 for (int i = 0; i < num_columns(); ++i) SetObjective(i, 0);
241}
242
243bool LpSolver::CheckAgainstExpected(const LpResult result) const {
244 DELPI_TRACE_FMT("LpSolver::ConflictingExpected({})", result);
245 switch (expected()) {
247 return result == LpResult::OPTIMAL || result == LpResult::DELTA_OPTIMAL || result == LpResult::UNBOUNDED;
249 return result == LpResult::DELTA_OPTIMAL;
251 return result == LpResult::UNBOUNDED;
253 return result == LpResult::INFEASIBLE || result == LpResult::DELTA_OPTIMAL;
255 return true;
256 default:
257 return false;
258 }
259}
260
261bool LpSolver::Verify() const {
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);
266 return false;
267 }
268 }
269 return true;
270}
271void LpSolver::EnsureSense(const bool is_min) {
272 is_min_ = is_min;
274}
275bool LpSolver::SetSimpleBoundInsteadOfAddRow(const std::vector<Expression::Addend>& addends, const mpq_class& lb,
276 const mpq_class& ub) {
277 // Only one variable must be present for a simple bound
278 if (addends.size() != 1u) return false;
279
280 const mpq_class& coeff = addends.front().second;
281 if (coeff == 1) {
282 SetBound(addends.front().first, lb, ub);
283 } else if (coeff > 0) {
284 SetBound(addends.front().first, lb == ninfinity_ ? lb : lb / coeff, ub == infinity_ ? ub : ub / coeff);
285 } else {
286 DELPI_ASSERT(coeff != 0, "Coefficient must be non-zero");
287 SetBound(addends.front().first, ub == infinity_ ? ninfinity_ : ub / coeff,
288 lb == ninfinity_ ? infinity_ : lb / coeff);
289 }
290 return true;
291}
292
293std::ostream& operator<<(std::ostream& os, const LpSolver& solver) {
294 os << solver.stats().solver_stats.class_name() << " {";
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()) {
302 os << "solution: ";
303 for (int i = 0; i < static_cast<int>(solver.solution().size()); ++i) {
304 os << solver.variables().at(i) << " = " << solver.solution().at(i) << ", ";
305 }
306 }
307 os << "}";
308 return os;
309}
310
311template void LpSolver::Maximise(const std::vector<std::pair<Variable, mpq_class>>&);
312template void LpSolver::Maximise(const std::set<std::pair<Variable, mpq_class>>&);
313template void LpSolver::Maximise(const std::unordered_set<std::pair<Variable, mpq_class>>&);
314template void LpSolver::Maximise(const std::span<std::pair<Variable, mpq_class>>&);
315template void LpSolver::Maximise(const std::map<Variable, mpq_class>&);
316template void LpSolver::Maximise(const std::unordered_map<Variable, mpq_class>&);
317
318template void LpSolver::Minimise(const std::vector<std::pair<Variable, mpq_class>>&);
319template void LpSolver::Minimise(const std::set<std::pair<Variable, mpq_class>>&);
320template void LpSolver::Minimise(const std::unordered_set<std::pair<Variable, mpq_class>>&);
321template void LpSolver::Minimise(const std::span<std::pair<Variable, mpq_class>>&);
322template void LpSolver::Minimise(const std::map<Variable, mpq_class>&);
323
324} // namespace delpi
Simple dataclass used to store the configuration of the program.
Definition Config.h:36
@ DELPI
Delpi Solver.
Definition Config.h:42
@ QSOPTEX
Qsoptex Solver.
Definition Config.h:41
@ SOPLEX
Soplex Solver. Default option.
Definition Config.h:40
Represents a symbolic form of an expression.
Definition Expression.h:37
Symbolic formula used to represent a constraint in the LP problem.
Definition Formula.h:32
Facade class that hides the underlying LP solver used by delpi.
Definition LpSolver.h:60
virtual void ReserveColumns(int size)
Reserve space for the given number of columns and rows.
Definition LpSolver.cpp:159
const Variable & var(const int column) const
Shorthand notation to get the real variable linked with column column.
Definition LpSolver.h:205
void SetObjective(const Expression &objective)
Set the objective coefficients of the LP problem to the given objective.
Definition LpSolver.cpp:191
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.
Definition LpSolver.cpp:275
mpq_class infinity_
Infinity threshold value.
Definition LpSolver.h:553
PartialSolveCallback partial_solve_cb_
Callback to call after solving the LP problem with a partial solution.
Definition LpSolver.h:549
bool is_min_
Whether this is a minimization or maximization LP problem.
Definition LpSolver.h:551
mpq_class ninfinity_
Negative infinity threshold value.
Definition LpSolver.h:552
RowIndex AddRow(const Row &row)
Add a new row to the LP problem with the given row.
Definition LpSolver.cpp:112
void SetInfo(const std::string &key, const std::string &value)
Set the information stored under the given key to the given value.
Definition LpSolver.cpp:165
Config config_
Configuration to use.
Definition LpSolver.h:533
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.
Definition LpSolver.h:548
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.
Definition LpSolver.cpp:243
std::vector< mpq_class > solution_
Solution vector.
Definition LpSolver.h:543
std::unordered_map< Variable, int > var_to_col_
Theory column ⇔ Variable.
Definition LpSolver.h:537
mpq_class obj_ub_
Upper bound on the objective value, if any.
Definition LpSolver.h:546
bool Verify() const
Verify that the current solution_ satisfies all the constraints in the LpSolver.
Definition LpSolver.cpp:261
std::vector< mpq_class > dual_solution_
Dual solution vector.
Definition LpSolver.h:544
virtual void AddColumns(const std::span< Column > &columns)
Add a vector of columns to the LP problem.
Definition LpSolver.cpp:218
void EnsureSense(bool is_min)
Make sure the LP solvers are aware of the sense of the LP problem (minimisation or maximisation).
Definition LpSolver.cpp:271
virtual void AddRows(const std::span< Row > &rows)
Add a vector of rows to the LP problem.
Definition LpSolver.cpp:108
mpq_class obj_lb_
Lower bound on the objective value, if any.
Definition LpSolver.h:545
void Maximise(const Expression &objective_function)
Set the objective_function to maximise while being subject to all the constraints.
Definition LpSolver.cpp:217
std::vector< Variable > col_to_var_
Literal ⇔ lp row.
Definition LpSolver.h:540
ColumnIndex AddColumn(const Column &column)
Add a new column to the LP problem.
Definition LpSolver.cpp:88
void Minimise(const Expression &objective_function)
Set the objective_function to minimise while being subject to all the constraints.
Definition LpSolver.cpp:230
LpSolver(mpq_class ninfinity, mpq_class infinity, Config config={}, const std::string &class_name="LpSolver")
Construct a new LpSolver object with the given config.
Definition LpSolver.cpp:41
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.
Definition LpSolver.cpp:166
std::unordered_map< std::string, std::string > info_
Generic information map. Generally collected from the file.
Definition LpSolver.h:535
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.
Definition LpSolver.h:534
virtual void ReserveRows(int size)
Reserve space for the given number of rows.
Definition LpSolver.cpp:162
void ResetObjective()
Set all coefficients in the objective function to zero.
Definition LpSolver.cpp:239
const std::string & GetInfo(const std::string &key) const
Retrieve the information stored under the given key.
Definition LpSolver.cpp:164
LpResult Solve()
Optimise the LP problem with the given delta.
Definition LpSolver.cpp:203
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.
Definition Timer.h:132
Real symbolic variable.
Definition Variable.h:20
Global namespace for the delpi library.
LpResult
Possible outcomes of the LP solver.
Definition LpResult.h:14
@ INFEASIBLE
The problem is infeasible.
Definition LpResult.h:19
@ DELTA_OPTIMAL
The delta-relaxation of the problem is optimal.
Definition LpResult.h:17
@ UNBOUNDED
The problem is unbounded.
Definition LpResult.h:18
@ ERROR
An error occurred.
Definition LpResult.h:20
@ OPTIMAL
The problem is optimal.
Definition LpResult.h:16
@ UNSOLVED
The solver has not yet been run.
Definition LpResult.h:15
FormulaKind
Kinds of symbolic formulas.
Definition FormulaKind.h:14
Convenient structure representing a column in the LP solver.
Definition Column.h:23
IterationStats solver_stats
Time spent in the solver and number of iterations.
Definition LpStats.h:23
Structure representing a row in the LP solver in the form of a linear combination of variables.
Definition Row.h:24