delpi  0.0.1
DElta-complete LP solver
Loading...
Searching...
No Matches
SoplexLpSolver.cpp
1
6#include "delpi/solver/SoplexLpSolver.h"
7
8#include <map>
9#include <set>
10#include <span> // NOLINT(build/include_order): c++20 header
11#include <string>
12#include <unordered_map>
13#include <unordered_set>
14#include <utility>
15#include <vector>
16
17#include "delpi/util/error.h"
18#include "delpi/util/logging.h"
19
20namespace delpi {
21
22using SoplexStatus = soplex::SPxSolver::Status;
23
24SoplexLpSolver::SoplexLpSolver(Config config, const std::string& class_name)
25 : LpSolver{-soplex::infinity, soplex::infinity, std::move(config), class_name},
26 consolidated_{false},
27 spx_{},
28 rninfinity_{-soplex::infinity},
29 rinfinity_{soplex::infinity} {
30 // Default SoPlex parameters
31 spx_.setRealParam(soplex::SoPlex::OPTTOL, config_.delta());
32 spx_.setRealParam(soplex::SoPlex::FEASTOL, 0);
33 spx_.setBoolParam(soplex::SoPlex::RATREC, false);
34 spx_.setIntParam(soplex::SoPlex::READMODE, soplex::SoPlex::READMODE_RATIONAL);
35 spx_.setIntParam(soplex::SoPlex::SOLVEMODE, soplex::SoPlex::SOLVEMODE_RATIONAL);
36 spx_.setIntParam(soplex::SoPlex::SYNCMODE, soplex::SoPlex::SYNCMODE_AUTO);
37 spx_.setIntParam(soplex::SoPlex::SIMPLIFIER, soplex::SoPlex::SIMPLIFIER_INTERNAL);
38 spx_.setIntParam(soplex::SoPlex::VERBOSITY, config_.verbose_simplex());
39 // Default is maximise.
40 spx_.setIntParam(soplex::SoPlex::OBJSENSE, soplex::SoPlex::OBJSENSE_MINIMIZE);
41 // Enable iterative refinement
42 bool enable_iterative_refinement = config_.lp_mode() != Config::LpMode::PURE_PRECISION_BOOSTING;
43 spx_.setBoolParam(soplex::SoPlex::ITERATIVE_REFINEMENT, enable_iterative_refinement);
44 // Enable precision boosting
45 bool enable_precision_boosting = config_.lp_mode() != Config::LpMode::PURE_ITERATIVE_REFINEMENT;
46 spx_.setBoolParam(soplex::SoPlex::ADAPT_TOLS_TO_MULTIPRECISION, enable_precision_boosting);
47 spx_.setBoolParam(soplex::SoPlex::PRECISION_BOOSTING, enable_precision_boosting);
48 spx_.setIntParam(soplex::SoPlex::RATFAC_MINSTALLS, !enable_iterative_refinement ? 0 : 2);
49 DELPI_DEBUG_FMT(
50 "SoplexTheorySolver::SoplexTheorySolver: precision = {}, precision_boosting = {}, iterative_refinement = {}",
51 config_.delta(), enable_precision_boosting, enable_iterative_refinement);
52}
53
54int SoplexLpSolver::num_columns() const { return consolidated_ ? spx_.numColsRational() : spx_cols_.num(); }
55int SoplexLpSolver::num_rows() const { return consolidated_ ? spx_.numRowsRational() : spx_rows_.num(); }
56
57Column SoplexLpSolver::column(const ColumnIndex column_idx) const {
58 DELPI_ASSERT(column_idx < num_columns(), "Column index out of bounds");
59 const soplex::Rational& lower = consolidated_ ? spx_.lowerRational(column_idx) : spx_cols_.lower(column_idx);
60 const soplex::Rational& upper = consolidated_ ? spx_.upperRational(column_idx) : spx_cols_.upper(column_idx);
61 const soplex::Rational& obj = consolidated_ ? spx_.objRational(column_idx) : spx_cols_.maxObj(column_idx);
62 Column column{};
63 column.var = col_to_var_.at(column_idx);
64 if (lower > -soplex::infinity) column.lb = std::move(gmp::ToMpqClass(lower.backend().data()));
65 if (upper < soplex::infinity) column.ub = std::move(gmp::ToMpqClass(upper.backend().data()));
66 if (!obj.is_zero()) column.obj = std::move(gmp::ToMpqClass(obj.backend().data()));
67
68 return column;
69}
70Row SoplexLpSolver::row(const RowIndex row_idx) const {
71 DELPI_ASSERT(row_idx < num_rows(), "Row index out of bounds");
72 const soplex::Rational lhs = consolidated_ ? spx_.lhsRational(row_idx) : spx_rows_.lhs(row_idx);
73 const soplex::Rational rhs = consolidated_ ? spx_.rhsRational(row_idx) : spx_rows_.rhs(row_idx);
74 Row row{};
75 if (lhs > -soplex::infinity) row.lb = std::move(gmp::ToMpqClass(lhs.backend().data()));
76 if (rhs < soplex::infinity) row.ub = std::move(gmp::ToMpqClass(rhs.backend().data()));
77
78 const soplex::SVectorRational addends =
79 consolidated_ ? spx_.rowVectorRational(row_idx) : spx_rows_.rowVector(row_idx);
80 for (int i = 0; i < addends.size(); ++i) {
81 const soplex::Rational& coeff = addends.value(i);
82 const Variable& var = col_to_var_.at(addends.index(i));
83 row.addends.emplace_back(var, std::move(gmp::ToMpqClass(coeff.backend().data())));
84 }
85 return row;
86}
87
88void SoplexLpSolver::ReserveColumns(const int num_columns) {
89 LpSolver::ReserveColumns(num_columns);
90 spx_cols_ = soplex::LPColSetRational(num_columns, num_columns);
91}
92void SoplexLpSolver::ReserveRows(const int num_rows) {
93 LpSolver::ReserveRows(num_rows);
94 spx_rows_ = soplex::LPRowSetRational(num_rows, num_rows);
95}
96
97LpSolver::ColumnIndex SoplexLpSolver::AddColumn(const Variable& var, const mpq_class& obj, const mpq_class& lb,
98 const mpq_class& ub) {
99 DELPI_ASSERT_FMT(!var_to_col_.contains(var), "Variable '{}' already exists in the LP.", var);
100 const ColumnIndex column_idx = num_columns();
101 var_to_col_.emplace(var, column_idx);
102 col_to_var_.emplace_back(var);
103 const soplex::LPColRational col_rational(obj.get_mpq_t(), soplex::DSVectorRational(), ub.get_mpq_t(), lb.get_mpq_t());
104 // Add the column to the LP
105 if (consolidated_)
106 spx_.addColRational(col_rational);
107 else
108 spx_cols_.add(col_rational);
109 return column_idx;
110}
111LpSolver::RowIndex SoplexLpSolver::AddRow(const std::vector<Expression::Addend>& addends, const mpq_class& lb,
112 const mpq_class& ub) {
113 const soplex::LPRowRational row_rational(lb.get_mpq_t(), ParseRowCoeff(addends), ub.get_mpq_t());
114 if (consolidated_)
115 spx_.addRowRational(row_rational);
116 else
117 spx_rows_.add(row_rational);
118 return num_rows() - 1;
119}
120
121LpSolver::RowIndex SoplexLpSolver::AddRow(const Expression::Addends& lhs, const FormulaKind sense,
122 const mpq_class& rhs) {
123 DELPI_ASSERT(sense == FormulaKind::Leq || sense == FormulaKind::Eq || sense == FormulaKind::Geq, "Invalid row sense");
124 const soplex::LPRowRational row_rational((sense == FormulaKind::Leq ? ninfinity_.get_mpq_t() : rhs.get_mpq_t()),
125 ParseRowCoeff(lhs),
126 (sense == FormulaKind::Geq ? infinity_.get_mpq_t() : rhs.get_mpq_t()));
127 if (consolidated_)
128 spx_.addRowRational(row_rational);
129 else
130 spx_rows_.add(row_rational);
131 return num_rows() - 1;
132}
133void SoplexLpSolver::SetBound(const Variable var, const mpq_class& lb, const mpq_class& ub) {
134 if (consolidated_) {
135 spx_.changeBoundsRational(var_to_col_.at(var), lb.get_mpq_t(), ub.get_mpq_t());
136 } else {
137 spx_cols_.lower_w()[var_to_col_.at(var)] = lb.get_mpq_t();
138 spx_cols_.upper_w()[var_to_col_.at(var)] = ub.get_mpq_t();
139 }
140}
141void SoplexLpSolver::SetCoefficient(const int row, const int column, const mpq_class& value) {
142 DELPI_ASSERT(row < num_rows(), "Row index out of bounds");
143 DELPI_ASSERT(column < num_columns(), "Column index out of bounds");
144 DELPI_ASSERT(ninfinity_ <= value && value <= infinity_, "LP coefficient value too large");
145
146 if (consolidated_) {
147 spx_.changeElementRational(row, column, value.get_mpq_t());
148 } else {
149 spx_rows_.rowVector_w(row).value(column) = value.get_mpq_t();
150 }
151
152 if (DELPI_TRACE_ENABLED) {
153 if (consolidated_)
154 DELPI_TRACE_FMT("SoplexLpSolver::SetCoefficient: row {}: {}", row, spx_.rowVectorRational(row));
155 else
156 DELPI_TRACE_FMT("SoplexLpSolver::SetCoefficient: row {}: {}", row, spx_rows_.rowVector(row));
157 }
158}
159void SoplexLpSolver::SetObjective(const int column, const mpq_class& value) {
160 DELPI_ASSERT(column < num_columns(), "Column index out of bounds");
161 if (consolidated_)
162 spx_.changeObjRational(column, value.get_mpq_t());
163 else
164 spx_cols_.maxObj_w(column) = value.get_mpq_t();
165}
166
168 if (!consolidated_) {
169 spx_.addColsRational(spx_cols_);
170 spx_.addRowsRational(spx_rows_);
171 consolidated_ = true;
172 spx_cols_.clear();
173 spx_rows_.clear();
174 }
175 const SoplexStatus status = spx_.optimize();
176 soplex::Rational max_violation, sum_violation;
177
178 // The status must be OPTIMAL, UNBOUNDED, or INFEASIBLE. Anything else is an error
179 if (status != SoplexStatus::OPTIMAL && status != SoplexStatus::UNBOUNDED && status != SoplexStatus::INFEASIBLE) {
180 DELPI_ERROR_FMT("SoplexLpSolver::Optimise: Unexpected SoPlex return -> {}", status);
181 return LpResult::ERROR;
182 } else if (spx_.getRedCostViolationRational(max_violation, sum_violation)) {
183 DELPI_DEBUG_FMT("SoplexLpSolver::Optimise: SoPlex returned {}, violation = {}", status, max_violation);
184 } else {
185 DELPI_DEBUG_FMT("SoplexLpSolver::Optimise: SoPlex has returned {}", status);
186 }
187
188 stats_.refinements = spx_.numRefinements();
189 stats_.precision = spx_.numPrecisionBoosts() == 0 ? sizeof(double) * 8 : 167;
190 for (int i = 1; i < spx_.numPrecisionBoosts(); i++) {
191 stats_.precision =
192 static_cast<std::size_t>(stats_.precision * spx_.realParam(soplex::SoPlex::PRECISION_BOOSTING_FACTOR));
193 }
194
195 switch (status) {
196 case SoplexStatus::OPTIMAL:
197 UpdateFeasible(max_violation);
198 return max_violation.is_zero() ? LpResult::OPTIMAL : LpResult::DELTA_OPTIMAL;
199 case SoplexStatus::UNBOUNDED:
200 UpdateFeasible(max_violation);
201 return LpResult::UNBOUNDED;
202 case SoplexStatus::INFEASIBLE:
203 // if (store_solution) UpdateInFeasible();
205 default:
206 DELPI_UNREACHABLE();
207 }
208}
209
210void SoplexLpSolver::UpdateFeasible(const soplex::Rational& max_violation) {
211 DELPI_ASSERT(solution_.empty(), "solution_ must be empty");
212 DELPI_ASSERT(dual_solution_.empty(), "dual_solution_ must be empty");
213 // Set the feasible information
214 const int colcount = num_columns();
215 const int rowcount = num_rows();
216 solution_.reserve(colcount);
217 dual_solution_.reserve(rowcount);
218
219 soplex::VectorRational solution{colcount};
220 [[maybe_unused]] const bool has_sol = spx_.getPrimalRational(solution);
221 DELPI_ASSERT(has_sol, "has_sol must be true");
222 DELPI_ASSERT(solution.dim() >= colcount, "x.dim() must be >= colcount");
223 for (int i = 0; i < solution.dim(); i++) solution_.emplace_back(gmp::ToMpqClass(solution[i].backend().data()));
224
225 soplex::VectorRational dual{rowcount};
226 [[maybe_unused]] const bool has_dual = spx_.getDualRational(dual);
227 DELPI_ASSERT(has_dual, "has_dual must be true");
228 for (int i = 0; i < rowcount; i++) dual_solution_.emplace_back(gmp::ToMpqClass(dual[i].backend().data()));
229
230 obj_lb_ = gmp::ToMpqClass((spx_.objValueRational() - max_violation).backend().data());
231 obj_ub_ = gmp::ToMpqClass((spx_.objValueRational() + max_violation).backend().data());
232}
234 spx_.setIntParam(soplex::SoPlex::OBJSENSE,
235 is_min_ ? soplex::SoPlex::OBJSENSE_MINIMIZE : soplex::SoPlex::OBJSENSE_MAXIMIZE);
236}
237
238#if 0
239void SoplexLpSolver::UpdateInfeasible() {
240 DELPI_ASSERT(infeasible_rows_.empty(), "infeasible_rows_ must be empty");
241 DELPI_ASSERT(infeasible_bounds_.empty(), "infeasible_bounds_ must be empty");
242 // Set the infeasible information
243 const int rowcount = num_rows();
244 const int colcount = num_columns();
245
246 soplex::VectorRational farkas_ray{rowcount};
247 DELPI_ASSERT(farkas_ray.dim() == num_rows(), "farkas_ray must have the same dimension as the rows");
248 // Get the Farkas ray to identify which rows are responsible for the conflict
249 [[maybe_unused]] bool res = spx_.getDualFarkasRational(farkas_ray);
250 DELPI_ASSERT(res, "getDualFarkasRational() must return true");
251
252 // Add the non-zero rows to the infeasible core
253 for (int i = 0; i < rowcount; i++) {
254 if (farkas_ray[i].is_zero()) continue;
255 DELPI_TRACE_FMT("SoplexLpSolver::NotifyInfeasible: ray[{}] = {}", i, farkas_ray[i]);
256 infeasible_rows_.emplace_back(i);
257 }
258 // Multiply the Farkas ray by the row coefficients to get the column violations: ray * A
259 // If the result is non-zero, the sign indicates the bound that caused the violation.
260 soplex::Rational col_violation{0};
261 for (int i = 0; i < colcount; i++) {
262 col_violation = 0;
263 for (int j = 0; j < rowcount; j++) {
264 col_violation += farkas_ray[j] * spx_.rowVectorRational(j)[i];
265 }
266 if (col_violation.is_zero()) continue;
267 if (DELPI_TRACE_ENABLED && static_cast<std::size_t>(i) < col_to_var_.size())
268 DELPI_TRACE_FMT("SoplexLpSolver::NotifyInfeasible: {}[{}] = {}", col_to_var_.at(i), i, col_violation);
269 infeasible_bounds_.emplace_back(i, col_violation < 0);
270 }
271}
272#endif
273
274template <TypedIterable<std::pair<const Variable, mpq_class>> T>
275soplex::DSVectorRational SoplexLpSolver::ParseRowCoeff(const T& literal_monomials) {
276 soplex::DSVectorRational coeffs{static_cast<int>(literal_monomials.size())};
277 for (const auto& [var, coeff] : literal_monomials) SetVarCoeff(coeffs, var, coeff);
278 return coeffs;
279}
280
281void SoplexLpSolver::SetVarCoeff(soplex::DSVectorRational& coeffs, const Variable& var, const mpq_class& value) const {
282 const auto it = var_to_col_.find(var);
283 if (it == var_to_col_.end()) DELPI_RUNTIME_ERROR_FMT("Undefined variable in the SoPlex LP solver: {}", var);
284 if (value <= ninfinity_ || value >= infinity_) {
285 DELPI_RUNTIME_ERROR_FMT("LP coefficient too large for SoPlex: {} <= {} <= {}", ninfinity_, value, infinity_);
286 }
287 coeffs.add(it->second, gmp::ToMpq(value));
288}
289
290#ifndef NDEBUG
291void SoplexLpSolver::Dump() { spx_.writeFileRational("~/delpi.temp.dump.soplex.lp"); }
292#endif
293
294template soplex::DSVectorRational SoplexLpSolver::ParseRowCoeff(
295 const std::vector<std::pair<const Variable, mpq_class>>& literal_monomials);
296template soplex::DSVectorRational SoplexLpSolver::ParseRowCoeff(
297 const std::set<std::pair<const Variable, mpq_class>>& literal_monomials);
298template soplex::DSVectorRational SoplexLpSolver::ParseRowCoeff(
299 const std::unordered_set<std::pair<const Variable, mpq_class>>& literal_monomials);
300template soplex::DSVectorRational SoplexLpSolver::ParseRowCoeff(
301 const std::span<std::pair<const Variable, mpq_class>>& literal_monomials);
302template soplex::DSVectorRational SoplexLpSolver::ParseRowCoeff(const std::map<Variable, mpq_class>& literal_monomials);
303template soplex::DSVectorRational SoplexLpSolver::ParseRowCoeff(
304 const std::unordered_map<Variable, mpq_class>& literal_monomials);
305
306} // namespace delpi
Simple dataclass used to store the configuration of the program.
Definition Config.h:36
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
mpq_class infinity_
Infinity threshold value.
Definition LpSolver.h:553
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
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
std::vector< mpq_class > dual_solution_
Dual solution vector.
Definition LpSolver.h:544
mpq_class obj_lb_
Lower bound on the objective value, if any.
Definition LpSolver.h:545
std::vector< Variable > col_to_var_
Literal ⇔ lp row.
Definition LpSolver.h:540
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 SetCoefficient(RowIndex row, ColumnIndex column, const mpq_class &value) override
Set the coefficient of the row constraint to apply at the column decisional variable.
ColumnIndex AddColumn(const Variable &var, const mpq_class &obj, const mpq_class &lb, const mpq_class &ub) override
Add a new bounded column to the LP problem, ensuring that the variable var is in the range and has t...
soplex::SoPlex spx_
SoPlex LP solver.
soplex::DSVectorRational ParseRowCoeff(const T &literal_monomials)
Parse a sequence of literal_monomials and set the coefficient for each decisional variable appearing ...
void ReserveRows(int num_rows) override
Reserve space for the given number of rows.
void SetBound(Variable var, const mpq_class &lb, const mpq_class &ub) override
Set the bounds of a var in the LP problem to the given lb and ub.
void EnsureSenseCore() override
Make sure the LP solvers are aware of the sense of the LP problem (minimisation or maximisation).
bool consolidated_
Whether the LP problem has been consolidated.
soplex::LPColSetRational spx_cols_
Columns of the LP problem.
void UpdateFeasible(const soplex::Rational &max_violation)
Use the result from the lp solver to update the solution vector and objective value.
void ReserveColumns(int num_columns) override
Reserve space for the given number of columns and rows.
void SetVarCoeff(soplex::DSVectorRational &coeffs, const Variable &var, const mpq_class &value) const
Set the coefficients to apply to var on a specific row.
Column column(ColumnIndex column_idx) const override
Get the column at the given column_idx index.
void SetObjective(int column, const mpq_class &value) override
The the objective coefficient of the given column to the given value.
LpResult SolveCore() override
Internal method that optimises the LP problem with the given delta.
Row row(RowIndex row_idx) const override
Get the row at the given row_idx index.
soplex::LPRowSetRational spx_rows_
Rows of the LP problem.
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
FormulaKind
Kinds of symbolic formulas.
Definition FormulaKind.h:14
Convenient structure representing a column in the LP solver.
Definition Column.h:23
Structure representing a row in the LP solver in the form of a linear combination of variables.
Definition Row.h:24