6#include "SoplexTheorySolver.h"
13#include "dlinear/util/Config.h"
14#include "dlinear/util/exception.h"
15#include "dlinear/util/logging.h"
19using soplex::Rational;
24SoplexTheorySolver::SoplexTheorySolver(PredicateAbstractor &predicate_abstractor,
const std::string &class_name)
25 : TheorySolver(predicate_abstractor, class_name), spx_{}, spx_cols_{}, spx_rows_{}, spx_rhs_{}, spx_sense_{} {
26 infinity_ = soplex::infinity;
27 ninfinity_ = -soplex::infinity;
29 spx_.setRealParam(soplex::SoPlex::FEASTOL, config_.precision());
30 spx_.setBoolParam(soplex::SoPlex::RATREC,
false);
31 spx_.setIntParam(soplex::SoPlex::READMODE, soplex::SoPlex::READMODE_RATIONAL);
32 spx_.setIntParam(soplex::SoPlex::SOLVEMODE, soplex::SoPlex::SOLVEMODE_RATIONAL);
33 spx_.setIntParam(soplex::SoPlex::CHECKMODE, soplex::SoPlex::CHECKMODE_RATIONAL);
34 spx_.setIntParam(soplex::SoPlex::SYNCMODE, soplex::SoPlex::SYNCMODE_AUTO);
35 spx_.setIntParam(soplex::SoPlex::SIMPLIFIER, soplex::SoPlex::SIMPLIFIER_INTERNAL);
36 spx_.setIntParam(soplex::SoPlex::VERBOSITY, config_.verbose_simplex());
38 spx_.setIntParam(soplex::SoPlex::OBJSENSE, soplex::SoPlex::OBJSENSE_MAXIMIZE);
40 bool enable_precision_boosting = config_.lp_mode() != Config::LPMode::PURE_ITERATIVE_REFINEMENT;
41 spx_.setBoolParam(soplex::SoPlex::ADAPT_TOLS_TO_MULTIPRECISION, enable_precision_boosting);
42 spx_.setBoolParam(soplex::SoPlex::PRECISION_BOOSTING, enable_precision_boosting);
43 spx_.setIntParam(soplex::SoPlex::RATFAC_MINSTALLS, enable_precision_boosting ? 0 : 2);
45 bool enable_iterative_refinement = config_.lp_mode() != Config::LPMode::PURE_PRECISION_BOOSTING;
46 spx_.setBoolParam(soplex::SoPlex::ITERATIVE_REFINEMENT, enable_iterative_refinement);
48 "SoplexTheorySolver::SoplexTheorySolver: precision = {}, precision_boosting = {}, iterative_refinement = {}",
49 config_.precision(), enable_precision_boosting, enable_iterative_refinement);
52void SoplexTheorySolver::AddLiterals() {
53 const int num_literals =
static_cast<int>(predicate_abstractor_.var_to_formula_map().size());
54 spx_rhs_.reserve(num_literals);
55 spx_sense_.reserve(num_literals);
56 spx_cols_ = soplex::LPColSetRational(num_literals, num_literals);
57 spx_rows_ = soplex::LPRowSetRational(num_literals, num_literals);
58 TheorySolver::AddLiterals();
61void SoplexTheorySolver::AddVariable(
const Variable &var) {
62 DLINEAR_ASSERT(!is_consolidated_,
"Cannot add variables after consolidation");
63 auto it = var_to_theory_col_.find(var.get_id());
65 if (it != var_to_theory_col_.end())
return;
67 const int spx_col{spx_cols_.num()};
69 spx_cols_.add(soplex::LPColRational(0, soplex::DSVectorRational(), soplex::infinity, -soplex::infinity));
70 var_to_theory_col_.emplace(var.get_id(), spx_col);
71 theory_col_to_var_.emplace_back(var);
72 fixed_preprocessor_.AddVariable(var);
73 DLINEAR_DEBUG_FMT(
"SoplexTheorySolver::AddVariable({} ↦ {})", var, spx_col);
76std::vector<std::pair<int, Rational>> SoplexTheorySolver::GetActiveRows() {
77 std::vector<std::pair<int, Rational>> active_rows;
78 soplex::VectorRational row_values{spx_.numRowsRational()};
79 soplex::LPRowSetRational lp_rows;
80 [[maybe_unused]]
const bool res = spx_.getRowsActivityRational(row_values);
81 DLINEAR_ASSERT(res,
"The problem must have a solution");
82 DLINEAR_TRACE_FMT(
"SoplexTheorySolver::GetActiveRows: row_values = {}", row_values);
83 spx_.getRowsRational(0, spx_.numRowsRational() - 1, lp_rows);
84 for (
int i = 0; i < lp_rows.num(); i++) {
85 if (lp_rows.lhs(i) == row_values[i] || lp_rows.rhs(i) == row_values[i]) active_rows.emplace_back(i, row_values[i]);
90std::vector<std::pair<int, soplex::Rational>> SoplexTheorySolver::GetActiveRows(
const std::vector<int> &spx_rows) {
91 std::vector<std::pair<int, Rational>> active_rows;
92 soplex::VectorRational row_values{spx_.numRowsRational()};
93 soplex::LPRowSetRational lp_rows;
94 [[maybe_unused]]
const bool res = spx_.getRowsActivityRational(spx_rows, row_values);
95 DLINEAR_TRACE_FMT(
"SoplexTheorySolver::GetActiveRows: row_values = {} in {} rows", row_values, spx_rows.size());
96 DLINEAR_ASSERT(res,
"The problem must have a solution");
97 spx_.getRowsRational(0, spx_.numRowsRational() - 1, lp_rows);
98 for (
const int i : spx_rows) {
99 if (lp_rows.lhs(i) == row_values[i] || lp_rows.rhs(i) == row_values[i]) active_rows.emplace_back(i, row_values[i]);
104std::optional<Rational> SoplexTheorySolver::IsRowActive(
const int spx_row) {
106 soplex::LPRowRational lp_row;
107 [[maybe_unused]]
const bool res = spx_.getRowActivityRational(spx_row, row_value);
108 DLINEAR_ASSERT(res,
"The problem must have a solution and the row must be present");
109 spx_.getRowRational(spx_row, lp_row);
110 DLINEAR_TRACE_FMT(
"SoplexTheorySolver::IsRowActive: {} =? {} =? {}", lp_row.lhs(), row_value, lp_row.rhs());
111 return lp_row.lhs() == row_value || lp_row.rhs() == row_value ? std::optional{std::move(row_value)}
112 : std::optional<Rational>{};
115bool SoplexTheorySolver::IsRowActive(
const int spx_row,
const Rational &value) {
117 soplex::LPRowRational lp_row;
118 [[maybe_unused]]
const bool res = spx_.getRowActivityRational(spx_row, row_value);
119 DLINEAR_ASSERT(res,
"The problem must have a solution and the row must be present");
120 if (row_value != value)
return false;
121 spx_.getRowRational(spx_row, lp_row);
122 DLINEAR_TRACE_FMT(
"SoplexTheorySolver::IsRowActive: {} =? {} =? {}", lp_row.lhs(), row_value, lp_row.rhs());
123 return lp_row.lhs() == row_value || lp_row.rhs() == row_value;
126soplex::DSVectorRational SoplexTheorySolver::ParseRowCoeff(
const Formula &formula) {
127 DLINEAR_ASSERT_FMT(formula.IsFlattened(),
"The formula {} must be flattened", formula);
129 const Expression &lhs = get_lhs_expression(formula);
130 const Expression &rhs = get_rhs_expression(formula);
131 DLINEAR_ASSERT(is_variable(lhs) || is_multiplication(lhs) || is_addition(lhs),
"Unsupported expression");
132 DLINEAR_ASSERT(is_constant(rhs),
"Unsupported expression");
134 soplex::DSVectorRational coeffs;
135 spx_rhs_.emplace_back(get_constant_value(rhs));
137 if (is_variable(lhs)) {
138 SetSPXVarCoeff(coeffs, get_variable(lhs), 1);
139 }
else if (is_addition(lhs)) {
140 DLINEAR_ASSERT(get_constant_in_addition(lhs) == 0,
"The addition constant must be 0");
141 const std::map<Expression, mpq_class> &
map = get_expr_to_coeff_map_in_addition(lhs);
142 coeffs.setMax(
static_cast<int>(
map.size()));
143 for (
const auto &[var, coeff] :
map) {
144 if (!is_variable(var)) DLINEAR_RUNTIME_ERROR_FMT(
"Expression {} not supported", lhs);
145 SetSPXVarCoeff(coeffs, get_variable(var), coeff);
147 }
else if (is_multiplication(lhs)) {
148 DLINEAR_ASSERT(get_base_to_exponent_map_in_multiplication(lhs).size() == 1,
"Only one variable is supported");
149 DLINEAR_ASSERT(is_variable(get_base_to_exponent_map_in_multiplication(lhs).begin()->first),
150 "Base must be a variable");
151 DLINEAR_ASSERT(is_constant(get_base_to_exponent_map_in_multiplication(lhs).begin()->second) &&
152 get_constant_value(get_base_to_exponent_map_in_multiplication(lhs).begin()->second) == 1,
153 "Only exp == 1 is supported");
154 SetSPXVarCoeff(coeffs, get_variable(get_base_to_exponent_map_in_multiplication(lhs).begin()->first),
155 get_constant_in_multiplication(lhs));
157 DLINEAR_RUNTIME_ERROR_FMT(
"Formula {} not supported", formula);
159 if (spx_rhs_.back() <= -soplex::infinity || spx_rhs_.back() >= soplex::infinity) {
160 DLINEAR_RUNTIME_ERROR_FMT(
"LP RHS value too large: {}", spx_rhs_.back());
165void SoplexTheorySolver::SetSPXVarCoeff(soplex::DSVectorRational &coeffs,
const Variable &var,
166 const mpq_class &value)
const {
167 const auto it = var_to_theory_col_.find(var.get_id());
168 if (it == var_to_theory_col_.end()) DLINEAR_RUNTIME_ERROR_FMT(
"Variable undefined: {}", var);
169 if (value <= -soplex::infinity || value >= soplex::infinity) {
170 DLINEAR_RUNTIME_ERROR_FMT(
"LP coefficient too large: {}", value);
172 coeffs.add(it->second, gmp::ToMpq(value));
175void SoplexTheorySolver::CreateArtificials(
const int spx_row) {
176 DLINEAR_RUNTIME_ERROR(
"Not implemented");
177 DLINEAR_ASSERT(2 == config_.simplex_sat_phase(),
"must be phase 2");
178 [[maybe_unused]]
const int spx_cols{spx_.numColsRational()};
179 soplex::DSVectorRational coeffsPos;
180 coeffsPos.add(spx_row, 1);
181 spx_.addColRational(soplex::LPColRational(1, coeffsPos, soplex::infinity, 0));
182 soplex::DSVectorRational coeffsNeg;
183 coeffsNeg.add(spx_row, -1);
184 spx_.addColRational(soplex::LPColRational(1, coeffsNeg, soplex::infinity, 0));
185 DLINEAR_DEBUG_FMT(
"SoplexTheorySolver::CreateArtificials({} -> ({}, {}))", spx_row, spx_cols, spx_cols + 1);
188void SoplexTheorySolver::GetSpxInfeasibilityRay(soplex::VectorRational &farkas_ray) {
189 DLINEAR_ASSERT(farkas_ray.dim() == spx_.numRowsRational(),
"farkas_ray must have the same dimension as the rows");
191 [[maybe_unused]]
bool res = spx_.getDualFarkasRational(farkas_ray);
192 DLINEAR_ASSERT(res,
"getDualFarkasRational() must return true");
195void SoplexTheorySolver::GetSpxInfeasibilityRay(soplex::VectorRational &farkas_ray,
196 std::vector<BoundViolationType> &bounds_ray) {
197 GetSpxInfeasibilityRay(farkas_ray);
199 DLINEAR_ASSERT(bounds_ray.size() == theory_col_to_var_.size(),
"bounds_ray must have the same dimension as the cols");
200 DLINEAR_ASSERT(std::all_of(bounds_ray.cbegin(), bounds_ray.cend(),
201 [](
const BoundViolationType &it) { return it == BoundViolationType::NO_BOUND_VIOLATION; }),
202 "bounds_ray must be initialized to NO_BOUND_VIOLATION");
205 Rational col_violation{0};
206 for (
int i = 0; i < static_cast<int>(theory_col_to_var_.size()); i++) {
208 for (
int j = 0; j < spx_.numRowsRational(); j++) {
209 col_violation += farkas_ray[j] * spx_.rowVectorRational(j)[i];
211 if (col_violation.is_zero())
continue;
212 DLINEAR_TRACE_FMT(
"CompleteSoplexTheorySolver::UpdateExplanationInfeasible: {}[{}] = {}", theory_col_to_var_[i], i,
215 col_violation > 0 ? BoundViolationType::LOWER_BOUND_VIOLATION : BoundViolationType::UPPER_BOUND_VIOLATION;
219void SoplexTheorySolver::Consolidate(
const Box &box) {
220 if (is_consolidated_)
return;
223 spx_.addColsRational(spx_cols_);
224 spx_.addRowsRational(spx_rows_);
227 for (
int theory_col = 0; theory_col < static_cast<int>(theory_col_to_var_.size()); theory_col++) {
228 const Variable &var{theory_col_to_var_[theory_col]};
229 DLINEAR_ASSERT(theory_col < spx_.numColsRational(),
"theory_col must be in bounds");
231 DLINEAR_ASSERT(ninfinity_ <= box[var].lb(),
"lower bound must be >= -infinity");
232 DLINEAR_ASSERT(box[var].lb() <= box[var].ub(),
"lower bound must be <= upper bound");
233 DLINEAR_ASSERT(box[var].ub() <= infinity_,
"upper bound must be <= infinity");
234 fixed_preprocessor_.SetInfinityBounds(var, box[var].lb(), box[var].ub());
239 preprocessor_.Clear(fixed_preprocessor_);
241 TheorySolver::Consolidate(box);
244void SoplexTheorySolver::Reset() {
245 TheorySolver::Reset();
251void SoplexTheorySolver::UpdateModelSolution() {
252 const int colcount = spx_.numColsRational();
253 soplex::VectorRational x;
255 [[maybe_unused]]
const bool has_sol = spx_.getPrimalRational(x);
256 DLINEAR_ASSERT(has_sol,
"has_sol must be true");
257 DLINEAR_ASSERT(x.dim() >= colcount,
"x.dim() must be >= colcount");
258 for (
int theory_col = 0; theory_col < static_cast<int>(theory_col_to_var_.size()); theory_col++) {
259 const Variable &var{theory_col_to_var_[theory_col]};
260 DLINEAR_ASSERT(model_[var].lb() <= gmp::ToMpqClass(x[theory_col].backend().data()) &&
261 gmp::ToMpqClass(x[theory_col].backend().data()) <= model_[var].ub(),
262 "val must be in bounds");
263 model_[var] = x[theory_col].backend().data();
266void SoplexTheorySolver::UpdateModelBounds() {
267 DLINEAR_ASSERT(spx_.numRowsRational() == 0,
"There must be no rows in the LP solver");
268 DLINEAR_ASSERT(std::all_of(theory_col_to_var_.cbegin(), theory_col_to_var_.cend(),
270 const auto &[lb, ub] = preprocessor_.theory_bounds().at(var).GetActiveBoundsValue();
273 "All lower bounds must be <= upper bounds");
276 for (
const auto &[var, bounds] : preprocessor_.theory_bounds()) {
277 const auto &[lb, ub] = bounds.GetActiveBoundsValue();
279 if (-soplex::infinity < lb) {
281 }
else if (ub < soplex::infinity) {
286 DLINEAR_ASSERT(model_[var].lb() <= val && val <= model_[var].ub(),
"val must be in bounds");
290void SoplexTheorySolver::UpdateExplanation(
LiteralSet &explanation) {
291 soplex::VectorRational ray{spx_.numRowsRational()};
292 std::vector<BoundViolationType> bounds_ray(theory_col_to_var_.size(), BoundViolationType::NO_BOUND_VIOLATION);
293 GetSpxInfeasibilityRay(ray, bounds_ray);
297 for (
int i = 0; i < spx_.numRowsRational(); ++i) {
298 if (ray[i] == 0)
continue;
299 DLINEAR_TRACE_FMT(
"SoplexTheorySolver::UpdateExplanation: ray[{}] = {}", i, ray[i]);
301 explanation.insert(theory_row_to_lit_[i]);
303 DLINEAR_ASSERT(!explanation.empty(),
"explanation must have at least one literal");
304 DLINEAR_ASSERT(bounds_ray.size() == theory_col_to_var_.size(),
"bounds_ray must have the same size as the variables");
306 for (std::size_t theory_col = 0; theory_col < theory_col_to_var_.size(); theory_col++) {
308 if (bound_violation == BoundViolationType::NO_BOUND_VIOLATION)
continue;
309 const BoundVector &bounds = preprocessor_.theory_bounds().at(theory_col_to_var_[theory_col]);
313 .explanation(explanation);
317void SoplexTheorySolver::DisableSpxRows() {
318 for (
int theory_row = 0; theory_row < spx_.numRowsRational(); theory_row++) {
319 if (!theory_rows_state_.at(theory_row)) spx_.changeRangeRational(theory_row, -soplex::infinity, soplex::infinity);
323void SoplexTheorySolver::EnableSpxVarBound() {
324 for (
const auto &[var, bounds] : preprocessor_.theory_bounds()) {
325 spx_.changeBoundsRational(var_to_theory_col_.at(var.get_id()), bounds.active_lower_bound().get_mpq_t(),
326 bounds.active_upper_bound().get_mpq_t());
327 DLINEAR_TRACE_FMT(
"EnableSpxVarBound: {} = [{}, {}]", var, bounds.active_lower_bound(),
328 bounds.active_upper_bound());
332void SoplexTheorySolver::EnableSpxRow(
const int spx_row) {
333 const auto &[var, truth] = theory_row_to_lit_[spx_row];
334 EnableSpxRow(spx_row, truth);
Data structure to store the LP solver bounds in a sorted vector to determine the active lower and upp...
BoundIterator GetActiveBounds() const
Return a BoundIterator containing a set of bounds enclosing the interval [active_lower_bound_,...
const mpq_class & active_lower_bound() const
Get read-only access to the active_lower_bound of the BoundVector.
const mpq_class & active_upper_bound() const
Get read-only access to the active_upper_bound of the BoundVector.
Collection of variables with associated intervals.
bool has_variable(const Variable &var) const
Checks if this box contains var.
static mpq_class infinity_
Positive infinity.
static mpq_class ninfinity_
Negative infinity.
BoundViolationType
Enum used to describe how the bounds on a variable participate in the infeasibility result of an LP p...
Represents a symbolic form of an expression.
Represents a symbolic variable.
Global namespace for the dlinear library.
std::set< Formula > map(const std::set< Formula > &formulas, const std::function< Formula(const Formula &)> &func)
Given formulas = {f₁, ..., fₙ} and a func : Formula → Formula, map(formulas, func) returns a set {fun...
std::set< Literal > LiteralSet
A set of literals.