dlinear  0.0.1
Delta-complete SMT solver for linear programming
Loading...
Searching...
No Matches
SoplexTheorySolver.cpp
1
6#include "SoplexTheorySolver.h"
7
8#include <algorithm>
9#include <cstddef>
10#include <map>
11#include <utility>
12
13#include "dlinear/util/Config.h"
14#include "dlinear/util/exception.h"
15#include "dlinear/util/logging.h"
16
17namespace dlinear {
18
19using soplex::Rational;
20
21mpq_class SoplexTheorySolver::infinity_{soplex::infinity};
22mpq_class SoplexTheorySolver::ninfinity_{-soplex::infinity};
23
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;
28 // Default SoPlex parameters
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());
37 // Default is maximize.
38 spx_.setIntParam(soplex::SoPlex::OBJSENSE, soplex::SoPlex::OBJSENSE_MAXIMIZE);
39 // Enable precision boosting
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);
44 // Enable iterative refinement
45 bool enable_iterative_refinement = config_.lp_mode() != Config::LPMode::PURE_PRECISION_BOOSTING;
46 spx_.setBoolParam(soplex::SoPlex::ITERATIVE_REFINEMENT, enable_iterative_refinement);
47 DLINEAR_DEBUG_FMT(
48 "SoplexTheorySolver::SoplexTheorySolver: precision = {}, precision_boosting = {}, iterative_refinement = {}",
49 config_.precision(), enable_precision_boosting, enable_iterative_refinement);
50}
51
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();
59}
60
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());
64 // The variable is already present
65 if (it != var_to_theory_col_.end()) return;
66
67 const int spx_col{spx_cols_.num()};
68 // obj, coeffs, upper, lower
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);
74}
75
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]);
86 }
87 return active_rows;
88}
89
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]);
100 }
101 return active_rows;
102}
103
104std::optional<Rational> SoplexTheorySolver::IsRowActive(const int spx_row) {
105 Rational row_value;
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>{};
113}
114
115bool SoplexTheorySolver::IsRowActive(const int spx_row, const Rational &value) {
116 Rational row_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;
124}
125
126soplex::DSVectorRational SoplexTheorySolver::ParseRowCoeff(const Formula &formula) {
127 DLINEAR_ASSERT_FMT(formula.IsFlattened(), "The formula {} must be flattened", formula);
128
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");
133
134 soplex::DSVectorRational coeffs;
135 spx_rhs_.emplace_back(get_constant_value(rhs));
136
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);
146 }
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));
156 } else {
157 DLINEAR_RUNTIME_ERROR_FMT("Formula {} not supported", formula);
158 }
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());
161 }
162 return coeffs;
163}
164
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);
171 }
172 coeffs.add(it->second, gmp::ToMpq(value));
173}
174
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);
186}
187
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");
190 // Get the Farkas ray to identify which rows are responsible for the conflict
191 [[maybe_unused]] bool res = spx_.getDualFarkasRational(farkas_ray);
192 DLINEAR_ASSERT(res, "getDualFarkasRational() must return true");
193}
194
195void SoplexTheorySolver::GetSpxInfeasibilityRay(soplex::VectorRational &farkas_ray,
196 std::vector<BoundViolationType> &bounds_ray) {
197 GetSpxInfeasibilityRay(farkas_ray);
198
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");
203 // Multiply the Farkas ray by the row coefficients to get the column violations: ray * A
204 // If the result is non-zero, the sign indicates the bound that caused the violation.
205 Rational col_violation{0};
206 for (int i = 0; i < static_cast<int>(theory_col_to_var_.size()); i++) {
207 col_violation = 0;
208 for (int j = 0; j < spx_.numRowsRational(); j++) {
209 col_violation += farkas_ray[j] * spx_.rowVectorRational(j)[i];
210 }
211 if (col_violation.is_zero()) continue;
212 DLINEAR_TRACE_FMT("CompleteSoplexTheorySolver::UpdateExplanationInfeasible: {}[{}] = {}", theory_col_to_var_[i], i,
213 col_violation);
214 bounds_ray.at(i) =
215 col_violation > 0 ? BoundViolationType::LOWER_BOUND_VIOLATION : BoundViolationType::UPPER_BOUND_VIOLATION;
216 }
217}
218
219void SoplexTheorySolver::Consolidate(const Box &box) {
220 if (is_consolidated_) return;
221
222 // Add columns and rows to the LP
223 spx_.addColsRational(spx_cols_);
224 spx_.addRowsRational(spx_rows_);
225
226 // Clear or set variable bounds
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");
230 if (box.has_variable(var)) {
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());
235 }
236 }
237
238 // Reset preprocessor to the fixed bounds
239 preprocessor_.Clear(fixed_preprocessor_);
240
241 TheorySolver::Consolidate(box);
242}
243
244void SoplexTheorySolver::Reset() {
245 TheorySolver::Reset();
246 // Omitting to do this seems to cause problems in soplex
247 // https://github.com/scipopt/soplex/issues/38
248 spx_.clearBasis();
249}
250
251void SoplexTheorySolver::UpdateModelSolution() {
252 const int colcount = spx_.numColsRational();
253 soplex::VectorRational x;
254 x.reDim(colcount);
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();
264 }
265}
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(),
269 [this](const Variable &var) {
270 const auto &[lb, ub] = preprocessor_.theory_bounds().at(var).GetActiveBoundsValue();
271 return lb <= ub;
272 }),
273 "All lower bounds must be <= upper bounds");
274
275 // Update the box with the new bounds, since the LP solver won't be called, for there are no constraints.
276 for (const auto &[var, bounds] : preprocessor_.theory_bounds()) {
277 const auto &[lb, ub] = bounds.GetActiveBoundsValue();
278 mpq_class val;
279 if (-soplex::infinity < lb) {
280 val = lb;
281 } else if (ub < soplex::infinity) {
282 val = ub;
283 } else {
284 val = 0;
285 }
286 DLINEAR_ASSERT(model_[var].lb() <= val && val <= model_[var].ub(), "val must be in bounds");
287 model_[var] = val;
288 }
289}
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);
294
295 explanation.clear();
296 // For each row in the ray
297 for (int i = 0; i < spx_.numRowsRational(); ++i) {
298 if (ray[i] == 0) continue; // The row did not participate in the conflict, ignore it
299 DLINEAR_TRACE_FMT("SoplexTheorySolver::UpdateExplanation: ray[{}] = {}", i, ray[i]);
300 // Insert the conflicting row literal to the explanation. Use the latest truth value from the SAT solver
301 explanation.insert(theory_row_to_lit_[i]);
302 }
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");
305 // Add all the active bounds for the variables responsible for the infeasibility
306 for (std::size_t theory_col = 0; theory_col < theory_col_to_var_.size(); theory_col++) {
307 const BoundViolationType bound_violation = bounds_ray[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]);
310 bounds
311 .GetActiveBounds(bound_violation == BoundViolationType::LOWER_BOUND_VIOLATION ? bounds.active_lower_bound()
312 : bounds.active_upper_bound())
313 .explanation(explanation);
314 }
315}
316
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);
320 }
321}
322
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());
329 }
330}
331
332void SoplexTheorySolver::EnableSpxRow(const int spx_row) {
333 const auto &[var, truth] = theory_row_to_lit_[spx_row];
334 EnableSpxRow(spx_row, truth);
335}
336
337} // namespace dlinear
Data structure to store the LP solver bounds in a sorted vector to determine the active lower and upp...
Definition BoundVector.h:48
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.
Definition Box.h:31
bool has_variable(const Variable &var) const
Checks if this box contains var.
Definition Box.cpp:117
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 form of a first-order logic formula.
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...
Definition symbolic.cpp:47
std::set< Literal > LiteralSet
A set of literals.
Definition literal.h:28