7#include "CompleteSoplexTheorySolver.h"
13#include "dlinear/libs/libsoplex.h"
14#include "dlinear/solver/LpColBound.h"
15#include "dlinear/solver/LpRowSense.h"
16#include "dlinear/symbolic/symbolic.h"
17#include "dlinear/util/Timer.h"
18#include "dlinear/util/exception.h"
19#include "dlinear/util/logging.h"
21#define STRICT_VARIABLE_IDX (spx_.numColsRational() - 1)
27inline std::size_t bool_vector_to_int(
const std::vector<bool> &v,
const std::set<std::size_t> &positions) {
28 std::size_t result = 0;
29 for (
const std::size_t &pos : positions) {
38using SoplexStatus = soplex::SPxSolver::Status;
39using soplex::Rational;
44CompleteSoplexTheorySolver::CompleteSoplexTheorySolver(PredicateAbstractor &predicate_abstractor,
45 const std::string &class_name)
46 : SoplexTheorySolver(predicate_abstractor, class_name),
47 nq_row_to_theory_rows_{},
49 last_theory_rows_to_explanation_{},
50 theory_rows_to_explanations_{},
51 locked_solver_{false} {
52 DLINEAR_ASSERT(config_.precision() == 0,
"CompleteSoplexTheorySolver does not support a positive precision");
53 DLINEAR_ASSERT(config_.simplex_sat_phase() == 1,
"CompleteSoplexTheorySolver must use phase 1");
56void CompleteSoplexTheorySolver::AddVariable(
const Variable &var) {
57 auto it = var_to_theory_col_.find(var.get_id());
59 if (it != var_to_theory_col_.end())
return;
60 SoplexTheorySolver::AddVariable(var);
63void CompleteSoplexTheorySolver::AddLiteral(
const Variable &formula_var,
const Formula &formula) {
64 DLINEAR_ASSERT(!is_consolidated_,
"Cannot add literals after consolidation");
66 if (lit_to_theory_row_.contains(formula_var.get_id()))
return;
68 DLINEAR_TRACE_FMT(
"CompleteSoplexTheorySolver::AddLiteral({})", formula);
75 const int spx_row{spx_rows_.num()};
78 soplex::DSVectorRational coeffs{ParseRowCoeff(formula)};
79 spx_rows_.add(soplex::LPRowRational(-soplex::infinity, coeffs, soplex::infinity));
82 lit_to_theory_row_.emplace(formula_var.get_id(), spx_row);
83 theory_row_to_lit_.emplace_back(formula_var,
true);
84 last_nq_status_.push_back(
false);
86 DLINEAR_ASSERT(
static_cast<size_t>(spx_row) == theory_row_to_lit_.size() - 1,
"incorrect theory_row_to_lit_.size()");
87 DLINEAR_ASSERT(
static_cast<size_t>(spx_row) == spx_sense_.size() - 1,
"incorrect spx_sense_.size()");
88 DLINEAR_ASSERT(
static_cast<size_t>(spx_row) == spx_rhs_.size() - 1,
"incorrect spx_rhs_.size()");
89 DLINEAR_ASSERT(
static_cast<size_t>(spx_row) == last_nq_status_.size() - 1,
"incorrect spx_rhs_.size()");
90 DLINEAR_DEBUG_FMT(
"CompleteSoplexTheorySolver::AddLiteral: {} ↦ {}", formula_var, spx_row);
94 DLINEAR_ASSERT(is_consolidated_,
"The solver must be consolidate before enabling a literal");
95 DLINEAR_ASSERT(predicate_abstractor_.var_to_formula_map().contains(lit.
var),
"var must map to a theory literal");
97 Explanations explanations{preprocessor_.EnableLiteral(lit)};
98 if (!explanations.empty())
return explanations;
100 const auto &[var, truth] = lit;
101 const auto it_row = lit_to_theory_row_.find(var.get_id());
103 if (it_row == lit_to_theory_row_.end()) {
104 DLINEAR_TRACE_FMT(
"CompleteSoplexTheorySolver::EnableLinearLiteral: ignoring ({})", lit);
109 const int spx_row = it_row->second;
111 theory_row_to_lit_[spx_row].truth = truth;
113 const Formula &formula = predicate_abstractor_[var];
114 DLINEAR_TRACE_FMT(
"CompleteSoplexTheorySolver::EnableLinearLiteral({})", lit);
118 if (!BoundPreprocessor::IsSimpleBound(formula)) EnableSpxRow(spx_row, truth);
122SatResult CompleteSoplexTheorySolver::CheckSatCore(mpq_class *actual_precision, Explanations &explanations) {
123 DLINEAR_ASSERT(is_consolidated_,
"The solver must be consolidate before checking for sat");
131 DLINEAR_DEBUG_FMT(
"CompleteSoplexTheorySolver::CheckSat: calling SoPlex (phase {})", config_.simplex_sat_phase());
135 DLINEAR_DEBUG_FMT(
"CompleteSoplexTheorySolver::CheckSat: no nq constraints sat_status = {}", sat_status);
136 if (sat_status != SatResult::SAT_SATISFIABLE) {
137 theory_rows_to_explanations_.insert(last_theory_rows_to_explanation_);
138 DLINEAR_ASSERT(theory_rows_to_explanations_.size() == 1,
"theory_rows_to_explanations_ must have size 1");
139 GetExplanation(explanations);
144 std::vector<bool> starting_iterator(nq_row_to_theory_rows_.size(),
false);
145 for (
size_t i = 0; i < nq_row_to_theory_rows_.size(); i++) {
146 starting_iterator[i] = last_nq_status_[nq_row_to_theory_rows_[i]];
148 BitIncrementIterator it(starting_iterator);
149 DLINEAR_DEBUG_FMT(
"CompleteSoplexTheorySolver::CheckSat: nq starting iterator size = {}", starting_iterator.size());
150 if (!it->empty()) DLINEAR_TRACE_FMT(
"CompleteSoplexTheorySolver::CheckSat: nq starting iterator = {}", it);
153 EnableNqLiterals(starting_iterator,
true);
156 last_theory_rows_to_explanation_.clear();
160 EnableNqLiterals(*it);
166 sat_status = SpxCheckSat();
167 DLINEAR_TRACE_FMT(
"CompleteSoplexTheorySolver::CheckSat: intermediate sat_status = {}", sat_status);
170 if (sat_status == SatResult::SAT_SATISFIABLE)
break;
174 if (!UpdateBitIncrementIteratorBasedOnExplanation(it))
break;
177 *actual_precision = 0;
178 switch (sat_status) {
179 case SatResult::SAT_SATISFIABLE:
180 UpdateModelSolution();
181 DLINEAR_DEBUG(
"CompleteSoplexTheorySolver::CheckSat: returning sat");
182 return SatResult::SAT_SATISFIABLE;
183 case SatResult::SAT_UNSATISFIABLE:
184 GetExplanation(explanations);
185 DLINEAR_DEBUG(
"CompleteSoplexTheorySolver::CheckSat: returning unsat");
186 return SatResult::SAT_UNSATISFIABLE;
188 DLINEAR_UNREACHABLE();
194 spx_.writeFile(
"/home/campus.ncl.ac.uk/c3054737/Programming/phd/dlinear/dump.lp");
196 SoplexStatus status = spx_.optimize();
197 Rational max_violation, sum_violation;
200 if (status != SoplexStatus::OPTIMAL && status != SoplexStatus::UNBOUNDED && status != SoplexStatus::INFEASIBLE) {
201 DLINEAR_RUNTIME_ERROR_FMT(
"SoPlex returned {}. That's not allowed here", status);
202 }
else if (spx_.getRowViolationRational(max_violation, sum_violation)) {
203 DLINEAR_ASSERT(max_violation.is_zero(),
"max_violation must be 0");
204 DLINEAR_DEBUG_FMT(
"CompleteSoplexTheorySolver::CheckSat: SoPlex returned {}, precision = 0", status);
206 DLINEAR_DEBUG_FMT(
"CompleteSoplexTheorySolver::CheckSat: SoPlex has returned {}, but no precision", status);
210 case SoplexStatus::OPTIMAL:
211 if (spx_.objValueRational() > 0)
212 return locked_solver_ ? SatResult::SAT_UNSATISFIABLE : SatResult::SAT_SATISFIABLE;
213 UpdateExplanationStrictInfeasible();
214 return SatResult::SAT_UNSATISFIABLE;
215 case SoplexStatus::INFEASIBLE:
216 UpdateExplanationInfeasible();
217 return SatResult::SAT_UNSATISFIABLE;
219 DLINEAR_UNREACHABLE();
223void CompleteSoplexTheorySolver::Reset() {
224 SoplexTheorySolver::Reset();
225 nq_row_to_theory_rows_.clear();
226 theory_rows_to_explanations_.clear();
227 nq_explanations_.clear();
228 locked_solver_ =
false;
229 last_theory_rows_to_explanation_.clear();
230 single_nq_rows_.clear();
233void CompleteSoplexTheorySolver::Consolidate(
const Box &box) {
234 if (is_consolidated_)
return;
237 spx_cols_.add(soplex::LPColRational(1, soplex::DSVectorRational(), 1, 0));
239 [[maybe_unused]]
const auto rowcount =
static_cast<size_t>(spx_rows_.num());
240 [[maybe_unused]]
const auto colcount =
static_cast<size_t>(spx_cols_.num());
241 DLINEAR_ASSERT_FMT(colcount == theory_col_to_var_.size() + 1,
"incorrect theory_col_to_var_.size(): {} vs {}",
242 colcount, theory_col_to_var_.size());
243 DLINEAR_ASSERT(colcount == var_to_theory_col_.size() + 1,
"incorrect var_to_theory_col_.size()");
244 DLINEAR_ASSERT(rowcount == spx_sense_.size(),
"incorrect spx_sense_.size()");
245 DLINEAR_ASSERT(rowcount == spx_rhs_.size(),
"incorrect spx_rhs_.size()");
246 DLINEAR_ASSERT(rowcount == last_nq_status_.size(),
"incorrect spx_rhs_.size()");
247 DLINEAR_ASSERT(rowcount == theory_row_to_lit_.size(),
"incorrect theory_row_to_lit_.size()");
248 DLINEAR_DEBUG(
"CompleteSoplexTheorySolver::Consolidate: consolidated");
250 SoplexTheorySolver::Consolidate(box);
253void CompleteSoplexTheorySolver::EnableNqLiteral(
const int spx_row,
const bool truth) {
254 last_nq_status_[spx_row] = truth;
256 const Rational rhs{spx_rhs_[spx_row].get_mpq_t()};
258 soplex::LPRowRational lp_row(spx_.numColsRational());
259 spx_.getRowRational(spx_row, lp_row);
260 soplex::DSVectorRational row_vector{lp_row.rowVector()};
262 const int pos = row_vector.pos(STRICT_VARIABLE_IDX);
263 DLINEAR_ASSERT(!row_vector.value(pos).is_zero(),
"Coefficient of the strict auxiliary variable cannot be 0");
264 DLINEAR_DEBUG_FMT(
"CompleteSoplexTheorySolver::EnableNqLiterals: Row({}) ↦ {} {} {}", spx_row, lp_row.lhs(),
265 lp_row.rowVector(), lp_row.rhs());
267 row_vector.value(pos) = truth ? -1 : 1;
269 lp_row.setLhs(truth ? rhs : -soplex::infinity);
270 lp_row.setRhs(truth ? soplex::infinity : rhs);
271 lp_row.setRowVector(row_vector);
273 spx_.changeRowRational(spx_row, lp_row);
276void CompleteSoplexTheorySolver::EnableNqLiterals(
const std::vector<bool> &nq_status,
const bool force) {
277 DLINEAR_TRACE_FMT(
"CompleteSoplexTheorySolver::EnableNqLiterals: nq_status = {}, force? = {}", nq_status, force);
278 for (
size_t i = 0; i < nq_status.size(); i++) {
279 const int &spx_row = nq_row_to_theory_rows_[i];
281 if (last_nq_status_[spx_row] == nq_status[i] && !force)
continue;
282 EnableNqLiteral(spx_row, nq_status[i]);
286void CompleteSoplexTheorySolver::DisableNqLiterals(
const std::set<size_t> &nq_constraints) {
287 DLINEAR_TRACE_FMT(
"CompleteSoplexTheorySolver::DisableNqLiterals: nq_constraints = {}", nq_constraints);
288 for (
const size_t i : nq_constraints) {
289 const int &spx_row = nq_row_to_theory_rows_[i];
290 spx_.changeRangeRational(spx_row, -soplex::infinity, soplex::infinity);
294void CompleteSoplexTheorySolver::UpdateExplanationInfeasible() {
295 DLINEAR_ASSERT(last_theory_rows_to_explanation_.empty(),
"last explanation must be empty");
296 soplex::VectorRational ray{spx_.numRowsRational()};
297 std::vector<BoundViolationType> bounds_ray(spx_.numColsRational() - 1, BoundViolationType::NO_BOUND_VIOLATION);
298 GetSpxInfeasibilityRay(ray, bounds_ray);
301 for (
int i = 0; i < spx_.numRowsRational(); ++i) {
302 if (ray[i].is_zero())
continue;
303 DLINEAR_TRACE_FMT(
"SoplexSatSolver::UpdateExplanation: ray[{}] = {}", i, ray[i]);
305 last_theory_rows_to_explanation_.insert(i);
308 const auto &row_formula = predicate_abstractor_[theory_row_to_lit_[i].var];
309 for (
const Variable &var : row_formula.GetFreeVariables()) {
310 const int &theory_col = var_to_theory_col_.at(var.get_id());
311 BoundsToTheoryRows(var, bounds_ray[theory_col], last_theory_rows_to_explanation_);
315 DLINEAR_ASSERT(!last_theory_rows_to_explanation_.empty(),
"explanation must contain at least a violation");
316 DLINEAR_DEBUG_FMT(
"CompleteSoplexTheorySolver::UpdateExplanationInfeasible: ↦ {}", last_theory_rows_to_explanation_);
319void CompleteSoplexTheorySolver::UpdateExplanationStrictInfeasible() {
320 DLINEAR_ASSERT(last_theory_rows_to_explanation_.empty(),
"last explanation must be empty");
323 soplex::VectorRational x{spx_.numColsRational()};
324 [[maybe_unused]]
bool has_sol = spx_.getPrimalRational(x);
325 DLINEAR_ASSERT(has_sol,
"has_sol must be true");
326 soplex::VectorRational dual{spx_.numRowsRational()};
327 [[maybe_unused]]
const bool has_dual = spx_.getDualRational(dual);
328 DLINEAR_ASSERT(has_dual,
"has_dual must be true");
330 std::set<Variable> visited_variables;
331 for (
int i = 0; i < dual.dim(); ++i) {
333 if (dual[i].is_zero())
continue;
334 last_theory_rows_to_explanation_.insert(i);
335 const auto &row_formula = predicate_abstractor_[theory_row_to_lit_[i].var];
336 for (
const Variable &var : row_formula.GetFreeVariables()) visited_variables.insert(var);
339 DLINEAR_DEBUG_FMT(
"Strict active rows: {}", last_theory_rows_to_explanation_);
342 for (
const Variable &var_id : visited_variables) {
344 const int theory_col = var_to_theory_col_.at(var_id.get_id());
345 BoundsToTheoryRows(var_id, mpq_class{x[theory_col].backend().data()}, last_theory_rows_to_explanation_);
348 DLINEAR_ASSERT(!last_theory_rows_to_explanation_.empty(),
"explanation must contain at least a violation");
349 DLINEAR_DEBUG_FMT(
"CompleteSoplexTheorySolver::UpdateExplanationStrictInfeasible: ↦ {}",
350 last_theory_rows_to_explanation_);
353void CompleteSoplexTheorySolver::GetExplanation(
Explanations &explanations) {
354 DLINEAR_ASSERT(!theory_rows_to_explanations_.empty(),
"there must be at least one explanation");
355 DLINEAR_ASSERT(std::all_of(theory_rows_to_explanations_.begin(), theory_rows_to_explanations_.end(),
356 [](
const std::set<int> &explanation) { return !explanation.empty(); }),
357 "no explanation can be empty");
358 for (
const std::set<int> &theory_rows_to_explanation : theory_rows_to_explanations_) {
360 for (
const int &theory_row : theory_rows_to_explanation) explanation.insert(theory_row_to_lit_[theory_row]);
361 explanations.insert(explanation);
363 DLINEAR_ASSERT(explanations.size() == theory_rows_to_explanations_.size(),
"explanations must have the same size");
366std::set<size_t> CompleteSoplexTheorySolver::IteratorNqRowsInLastExplanation()
const {
367 std::set<size_t> nq_row_to_iterator_index;
368 for (
size_t i = 0; i < nq_row_to_theory_rows_.size(); i++) {
369 const int &spx_row = nq_row_to_theory_rows_[i];
370 if (last_theory_rows_to_explanation_.find(spx_row) != last_theory_rows_to_explanation_.end()) {
371 nq_row_to_iterator_index.insert(i);
374 return nq_row_to_iterator_index;
377bool CompleteSoplexTheorySolver::UpdateBitIncrementIteratorBasedOnExplanation(
BitIncrementIterator &bit_iterator) {
380 if (last_theory_rows_to_explanation_.empty()) {
381 DLINEAR_ASSERT(locked_solver_,
"The solver must have been locked before");
382 locked_solver_ =
false;
383 if (single_nq_rows_.empty()) {
386 for (
const auto &[nq_row, nq_row_value] : single_nq_rows_) bit_iterator.
Learn(nq_row, !nq_row_value);
387 single_nq_rows_.clear();
389 EnableNqLiterals(*bit_iterator,
true);
391 return theory_rows_to_explanations_.empty();
394 const std::set<size_t> nq_in_explanation{IteratorNqRowsInLastExplanation()};
395 DLINEAR_TRACE_FMT(
"CompleteSoplexTheorySolver::CheckSat: nq_in_explanation = {}", nq_in_explanation);
396 DLINEAR_ASSERT(!nq_in_explanation.empty(),
"nq_in_explanation must not be empty");
399 auto it = nq_explanations_.find(nq_in_explanation);
400 if (it == nq_explanations_.end()) {
401 DLINEAR_TRACE(
"CompleteSoplexTheorySolver::CheckSat: explanation not seen yet. Adding to the set");
402 it = nq_explanations_.emplace(nq_in_explanation, nq_in_explanation).first;
406 const size_t idx = bool_vector_to_int(*bit_iterator, nq_in_explanation);
408 if (!nq_explanation.
visited[idx]) {
409 DLINEAR_TRACE(
"CompleteSoplexTheorySolver::CheckSat: new explanation. Adding the new explanation to the set");
410 nq_explanation.
visited[idx] =
true;
411 nq_explanation.
explanation.insert(last_theory_rows_to_explanation_.cbegin(),
412 last_theory_rows_to_explanation_.cend());
414 if (nq_in_explanation.size() == 1)
415 single_nq_rows_.emplace(*nq_in_explanation.begin(), bit_iterator[*nq_in_explanation.begin()]);
418 if (std::all_of(nq_explanation.
visited.begin(), nq_explanation.
visited.end(), [](
bool b) { return b; })) {
419 DLINEAR_DEBUG(
"CompleteSoplexTheorySolver::CheckSat: all combinations of nq_in_explanation tried. Infeasible");
420 theory_rows_to_explanations_.insert(nq_explanation.
explanation);
425 DLINEAR_DEBUG(
"CompleteSoplexTheorySolver::CheckSat: removing last infeasible nq constraints and try again");
426 DisableNqLiterals(nq_in_explanation);
427 locked_solver_ =
true;
431void CompleteSoplexTheorySolver::EnableSpxVarBound() {
432 SoplexTheorySolver::EnableSpxVarBound();
434 for (
const auto &[var, theory_bound] : preprocessor_.theory_bounds()) {
436 for (
BoundIterator it{theory_bound.GetActiveBound()}; it; ++it) {
437 const auto &[value, bound, lit, expl] = *it;
439 if (bound == LpColBound::SU || bound == LpColBound::SL || bound == LpColBound::D) {
440 SoplexTheorySolver::EnableSpxRow(lit_to_theory_row_.at(lit.var.get_id()));
446void CompleteSoplexTheorySolver::EnableSpxRow(
int spx_row,
bool truth) {
447 const LpRowSense sense = truth ? spx_sense_[spx_row] : !spx_sense_[spx_row];
448 const mpq_class &rhs{spx_rhs_[spx_row]};
449 soplex::LPRowRational lp_row;
450 spx_.getRowRational(spx_row, lp_row);
451 soplex::DSVectorRational row_vector{lp_row.rowVector()};
453 int strict_variable_pos = row_vector.pos(STRICT_VARIABLE_IDX);
454 if (strict_variable_pos < 0) {
455 row_vector.add(STRICT_VARIABLE_IDX, 1);
456 strict_variable_pos = row_vector.pos(STRICT_VARIABLE_IDX);
460 row_vector.value(strict_variable_pos) = 1;
463 row_vector.value(strict_variable_pos) = -1;
468 row_vector.value(strict_variable_pos) = 0;
472 nq_row_to_theory_rows_.push_back(spx_row);
475 DLINEAR_UNREACHABLE();
478 lp_row.setRowVector(row_vector);
479 lp_row.setLhs(sense == LpRowSense::GE || sense == LpRowSense::EQ || sense == LpRowSense::GT
480 ? Rational(gmp::ToMpq(rhs))
481 : Rational(-soplex::infinity));
482 lp_row.setRhs(sense == LpRowSense::LE || sense == LpRowSense::EQ || sense == LpRowSense::LT
483 ? Rational(gmp::ToMpq(rhs))
484 : Rational(soplex::infinity));
485 spx_.changeRowRational(spx_row, lp_row);
487 theory_rows_state_.at(spx_row) =
true;
488 DLINEAR_TRACE_FMT(
"CompleteSoplexTheorySolver::EnableLinearLiteral: Row({}, {}) ↦ {} {} {} | Sense({})", spx_row,
489 theory_row_to_lit_[spx_row], lp_row.lhs(), lp_row.rowVector(), lp_row.rhs(), sense);
492CompleteSoplexTheorySolver::NqExplanation::NqExplanation(
size_t size) : explanation{}, visited(size, false) {}
493CompleteSoplexTheorySolver::NqExplanation::NqExplanation(
const std::set<size_t> &bits)
494 : explanation{}, visited(1 << bits.size(), false) {}
BitIncrementIterator class.
bool Learn(std::size_t i)
Learn the value of the bit at position i by inverting the bit.
Collection of variables with associated intervals.
static const mpq_class strict_col_lb_
Zero. Used for the strict variable lower bound.
static const mpq_class strict_col_ub_
One. Used for the strict variable upper bound.
std::set< LiteralSet > Explanations
Set of explanations of the conflict.
Represents a symbolic variable.
Global namespace for the dlinear library.
SatResult
Result of running the theory solver over the literals decided by the SAT solver.
std::set< Literal > LiteralSet
A set of literals.
LpRowSense parseLpSense(const Formula &f)
Parse the sense from a formula.
LpRowSense
Sense of a linear programming row describing a constraint.
Structure used to store the infeasibility explanation of a subset of non-equal constraints.
std::set< int > explanation
Indexes of a subset of non-equal constraints that are part of the explanation.
std::vector< bool > visited
Configuration of the non-equal constraints that have been visited.
A literal is a variable with an associated truth value, indicating whether it is true or false.