6#include "delpi/solver/SoplexLpSolver.h"
12#include <unordered_map>
13#include <unordered_set>
17#include "delpi/util/error.h"
18#include "delpi/util/logging.h"
22using SoplexStatus = soplex::SPxSolver::Status;
24SoplexLpSolver::SoplexLpSolver(
Config config,
const std::string& class_name)
25 :
LpSolver{-soplex::infinity, soplex::infinity, std::move(config), class_name},
28 rninfinity_{-soplex::infinity},
29 rinfinity_{soplex::infinity} {
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());
40 spx_.setIntParam(soplex::SoPlex::OBJSENSE, soplex::SoPlex::OBJSENSE_MINIMIZE);
42 bool enable_iterative_refinement = config_.lp_mode() != Config::LpMode::PURE_PRECISION_BOOSTING;
43 spx_.setBoolParam(soplex::SoPlex::ITERATIVE_REFINEMENT, enable_iterative_refinement);
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);
50 "SoplexTheorySolver::SoplexTheorySolver: precision = {}, precision_boosting = {}, iterative_refinement = {}",
51 config_.delta(), enable_precision_boosting, enable_iterative_refinement);
54int SoplexLpSolver::num_columns()
const {
return consolidated_ ? spx_.numColsRational() : spx_cols_.num(); }
55int SoplexLpSolver::num_rows()
const {
return consolidated_ ? spx_.numRowsRational() : spx_rows_.num(); }
58 DELPI_ASSERT(column_idx < num_columns(),
"Column index out of bounds");
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()));
71 DELPI_ASSERT(row_idx < num_rows(),
"Row index out of bounds");
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()));
78 const soplex::SVectorRational addends =
80 for (
int i = 0; i < addends.size(); ++i) {
81 const soplex::Rational& coeff = addends.value(i);
83 row.addends.emplace_back(
var, std::move(gmp::ToMpqClass(coeff.backend().data())));
90 spx_cols_ = soplex::LPColSetRational(num_columns, num_columns);
94 spx_rows_ = soplex::LPRowSetRational(num_rows, num_rows);
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();
103 const soplex::LPColRational col_rational(obj.get_mpq_t(), soplex::DSVectorRational(), ub.get_mpq_t(), lb.get_mpq_t());
106 spx_.addColRational(col_rational);
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());
115 spx_.addRowRational(row_rational);
117 spx_rows_.add(row_rational);
118 return num_rows() - 1;
121LpSolver::RowIndex SoplexLpSolver::AddRow(
const Expression::Addends& lhs,
const FormulaKind sense,
122 const mpq_class& rhs) {
128 spx_.addRowRational(row_rational);
131 return num_rows() - 1;
142 DELPI_ASSERT(
row < num_rows(),
"Row index out of bounds");
143 DELPI_ASSERT(
column < num_columns(),
"Column index out of bounds");
152 if (DELPI_TRACE_ENABLED) {
154 DELPI_TRACE_FMT(
"SoplexLpSolver::SetCoefficient: row {}: {}",
row,
spx_.rowVectorRational(
row));
156 DELPI_TRACE_FMT(
"SoplexLpSolver::SetCoefficient: row {}: {}",
row,
spx_rows_.rowVector(
row));
160 DELPI_ASSERT(
column < num_columns(),
"Column index out of bounds");
162 spx_.changeObjRational(
column, value.get_mpq_t());
175 const SoplexStatus status =
spx_.optimize();
176 soplex::Rational max_violation, sum_violation;
179 if (status != SoplexStatus::OPTIMAL && status != SoplexStatus::UNBOUNDED && status != SoplexStatus::INFEASIBLE) {
180 DELPI_ERROR_FMT(
"SoplexLpSolver::Optimise: Unexpected SoPlex return -> {}", status);
182 }
else if (
spx_.getRedCostViolationRational(max_violation, sum_violation)) {
183 DELPI_DEBUG_FMT(
"SoplexLpSolver::Optimise: SoPlex returned {}, violation = {}", status, max_violation);
185 DELPI_DEBUG_FMT(
"SoplexLpSolver::Optimise: SoPlex has returned {}", status);
189 stats_.precision =
spx_.numPrecisionBoosts() == 0 ?
sizeof(double) * 8 : 167;
190 for (
int i = 1; i <
spx_.numPrecisionBoosts(); i++) {
192 static_cast<std::size_t
>(
stats_.precision *
spx_.realParam(soplex::SoPlex::PRECISION_BOOSTING_FACTOR));
196 case SoplexStatus::OPTIMAL:
199 case SoplexStatus::UNBOUNDED:
202 case SoplexStatus::INFEASIBLE:
211 DELPI_ASSERT(
solution_.empty(),
"solution_ must be empty");
212 DELPI_ASSERT(
dual_solution_.empty(),
"dual_solution_ must be empty");
214 const int colcount = num_columns();
215 const int rowcount = num_rows();
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()));
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()));
230 obj_lb_ = gmp::ToMpqClass((
spx_.objValueRational() - max_violation).backend().data());
231 obj_ub_ = gmp::ToMpqClass((
spx_.objValueRational() + max_violation).backend().data());
234 spx_.setIntParam(soplex::SoPlex::OBJSENSE,
235 is_min_ ? soplex::SoPlex::OBJSENSE_MINIMIZE : soplex::SoPlex::OBJSENSE_MAXIMIZE);
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");
243 const int rowcount = num_rows();
244 const int colcount = num_columns();
246 soplex::VectorRational farkas_ray{rowcount};
247 DELPI_ASSERT(farkas_ray.dim() == num_rows(),
"farkas_ray must have the same dimension as the rows");
249 [[maybe_unused]]
bool res = spx_.getDualFarkasRational(farkas_ray);
250 DELPI_ASSERT(res,
"getDualFarkasRational() must return true");
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);
260 soplex::Rational col_violation{0};
261 for (
int i = 0; i < colcount; i++) {
263 for (
int j = 0; j < rowcount; j++) {
264 col_violation += farkas_ray[j] * spx_.rowVectorRational(j)[i];
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);
274template <TypedIterable<std::pair<const Variable, mpq_
class>> T>
276 soplex::DSVectorRational coeffs{
static_cast<int>(literal_monomials.size())};
277 for (
const auto& [
var, coeff] : literal_monomials)
SetVarCoeff(coeffs,
var, coeff);
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_);
287 coeffs.add(it->second, gmp::ToMpq(value));
291void SoplexLpSolver::Dump() { spx_.writeFileRational(
"~/delpi.temp.dump.soplex.lp"); }
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);
Simple dataclass used to store the configuration of the program.
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.
mpq_class infinity_
Infinity threshold value.
bool is_min_
Whether this is a minimization or maximization LP problem.
mpq_class ninfinity_
Negative infinity threshold value.
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.
std::vector< mpq_class > dual_solution_
Dual solution vector.
mpq_class obj_lb_
Lower bound on the objective value, if any.
std::vector< Variable > col_to_var_
Literal ⇔ lp row.
LpStats stats_
Statistics of the solver.
virtual void ReserveRows(int size)
Reserve space for the given number of rows.
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.
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.
FormulaKind
Kinds of symbolic formulas.
Convenient structure representing a column in the LP solver.
Structure representing a row in the LP solver in the form of a linear combination of variables.