6#include "NNSoplexTheorySolver.h"
8#include "dlinear/util/exception.h"
9#include "dlinear/util/logging.h"
13using soplex::Rational;
14using SoplexStatus = soplex::SPxSolver::Status;
16NNSoplexTheorySolver::NNSoplexTheorySolver(PredicateAbstractor &predicate_abstractor,
const std::string &class_name)
17 : SoplexTheorySolver(predicate_abstractor, class_name),
19 enabled_pl_constraints_{},
22 spx_.setIntParam(soplex::SoPlex::OBJSENSE, soplex::SoPlex::OBJSENSE_MINIMIZE);
25void NNSoplexTheorySolver::SetPiecewiseLinearConstraints(
26 const std::vector<std::unique_ptr<PiecewiseLinearConstraint>> &relu_constraints) {
27 for (
const auto &relu_constraint : relu_constraints) {
28 pl_theory_lit_.emplace(relu_constraint->active_var(), relu_constraint.get());
29 pl_theory_lit_.emplace(relu_constraint->inactive_var(), relu_constraint.get());
35void NNSoplexTheorySolver::AddLiteral(
const Variable &formula_var,
const Formula &formula) {
36 DLINEAR_ASSERT(!is_consolidated_,
"Cannot add literals after consolidation");
38 if (lit_to_theory_row_.contains(formula_var.get_id()))
return;
41 DLINEAR_TRACE_FMT(
"NNSoplexTheorySolver::AddLiteral({})", formula);
48 const int spx_row{spx_rows_.num()};
50 const bool is_simple_bound = BoundPreprocessor::IsSimpleBound(formula);
51 soplex::DSVectorRational coeffs{is_simple_bound ? soplex::DSVectorRational{} : ParseRowCoeff(formula)};
52 if (is_simple_bound) spx_rhs_.emplace_back(0);
53 spx_rows_.add(soplex::LPRowRational(-soplex::infinity, coeffs, soplex::infinity));
54 if (2 == config_.simplex_sat_phase()) CreateArtificials(spx_row);
57 lit_to_theory_row_.emplace(formula_var.get_id(), spx_row);
58 theory_row_to_lit_.emplace_back(formula_var,
true);
60 DLINEAR_ASSERT(
static_cast<std::size_t
>(spx_row) == theory_row_to_lit_.size() - 1,
61 "incorrect theory_row_to_lit_.size()");
62 DLINEAR_ASSERT(
static_cast<std::size_t
>(spx_row) == spx_sense_.size() - 1,
"incorrect spx_sense_.size()");
63 DLINEAR_ASSERT(
static_cast<std::size_t
>(spx_row) == spx_rhs_.size() - 1,
"incorrect spx_rhs_.size()");
64 DLINEAR_DEBUG_FMT(
"NNSoplexTheorySolver::AddLiteral: {} ↦ {}", formula, spx_row);
68 DLINEAR_ASSERT(is_consolidated_,
"The solver must be consolidate before enabling a literal");
69 DLINEAR_ASSERT(predicate_abstractor_.var_to_formula_map().contains(lit.
var),
"var must map to a theory literal");
71 const auto &[var, truth] = lit;
72 DLINEAR_DEV_FMT(
"Enabled Literal {}", lit);
75 if (pl_theory_lit_.contains(var)) {
78 if (!truth)
return {};
82 if (!constraint.
fixed()) {
84 enabled_pl_constraints_.emplace_back(&constraint, is_active,
false);
85 DLINEAR_DEV_FMT(
"ENABLED {} = fixed? {}\nIs active?: {}\n{}", lit, constraint.
fixed(),
86 enabled_pl_constraints_.back().active, constraint);
92 Explanations explanations{preprocessor_.EnableLiteral(lit)};
93 if (!explanations.empty())
return explanations;
95 const auto it = lit_to_theory_row_.find(var.get_id());
97 if (it == lit_to_theory_row_.end()) {
98 DLINEAR_TRACE_FMT(
"NNSoplexTheorySolver::EnableLinearLiteral: ignoring ({}{})", truth ?
"" :
"¬", var);
103 const int spx_row = it->second;
105 theory_row_to_lit_[spx_row].truth = truth;
107 DLINEAR_TRACE_FMT(
"NNSoplexTheorySolver::EnableLinearLiteral({})", lit);
109 EnableSpxRow(spx_row, truth);
114SatResult NNSoplexTheorySolver::CheckSatCore(mpq_class *actual_precision, std::set<LiteralSet> &explanations) {
125 DLINEAR_DEBUG_FMT(
"NNSoplexTheorySolver::CheckSat: calling SoPlex (phase {})", config_.simplex_sat_phase());
129 const std::size_t prev_size = explanations.size();
132 switch (SpxCheckSat(actual_precision)) {
133 case SpxCheckSatResult::SAT:
134 UpdateModelSolution();
135 DLINEAR_DEBUG(
"NNSoplexTheorySolver::CheckSat: returning sat");
136 return SatResult::SAT_DELTA_SATISFIABLE;
137 case SpxCheckSatResult::INFEASIBLE:
138 UpdateExplanations(explanations);
139 DLINEAR_DEBUG(
"NNSoplexTheorySolver::CheckSat: returning unsat");
140 return SatResult::SAT_UNSATISFIABLE;
141 case SpxCheckSatResult::SOI_VIOLATION:
143 UpdateExplanationsWithCurrentPiecewiseLinearLiterals(explanations);
145 if (explanations.size() == prev_size || !InvertGreatestViolation()) {
146 DLINEAR_DEBUG(
"NNSoplexTheorySolver::CheckSat: returning unsat");
147 return SatResult::SAT_UNSATISFIABLE;
151 DLINEAR_UNREACHABLE();
156NNSoplexTheorySolver::SpxCheckSatResult NNSoplexTheorySolver::SpxCheckSat(mpq_class *actual_precision) {
158 spx_.writeFile(
"/home/campus.ncl.ac.uk/c3054737/Programming/phd/dlinear/dump.lp");
160 SoplexStatus status = spx_.optimize();
161 Rational max_violation, sum_violation;
164 if (status != SoplexStatus::OPTIMAL && status != SoplexStatus::UNBOUNDED && status != SoplexStatus::INFEASIBLE) {
165 DLINEAR_RUNTIME_ERROR_FMT(
"SoPlex returned {}. That's not allowed here", status);
166 }
else if (spx_.getRowViolationRational(max_violation, sum_violation)) {
167 DLINEAR_DEBUG_FMT(
"DeltaSoplexTheorySolver::CheckSat: SoPlex returned {}, precision = {}", status, max_violation);
169 DLINEAR_DEBUG_FMT(
"NNSoplexTheorySolver::CheckSat: SoPlex has returned {}, but no precision", status);
173 const mpq_class violation_rhs{is_addition(soi_) ? -get_constant_in_addition(soi_) + *actual_precision
174 : *actual_precision};
176 case SoplexStatus::OPTIMAL:
177 fmt::println(
"Objective value: {}", gmp::ToMpqClass(spx_.objValueRational().backend().data()).get_d());
178 return spx_.objValueRational() <= violation_rhs.get_mpq_t() ? SpxCheckSatResult::SAT
179 : SpxCheckSatResult::SOI_VIOLATION;
180 case SoplexStatus::INFEASIBLE:
181 return SpxCheckSatResult::INFEASIBLE;
183 DLINEAR_UNREACHABLE();
187bool NNSoplexTheorySolver::InvertGreatestViolation() {
188 const int colcount = spx_.numColsRational();
189 soplex::VectorRational x;
191 [[maybe_unused]]
const bool has_sol = spx_.getPrimalRational(x);
192 DLINEAR_ASSERT(has_sol,
"has_sol must be true");
193 DLINEAR_ASSERT(x.dim() >= colcount,
"x.dim() must be >= colcount");
195 for (
int theory_col = 0; theory_col < static_cast<int>(theory_col_to_var_.size()); theory_col++) {
196 const Variable &var{theory_col_to_var_[theory_col]};
197 env[var] = gmp::ToMpqClass(x[theory_col].backend().data());
201 mpq_class max_cost{0};
202 std::size_t max_cost_index{0};
203 for (std::size_t i = 0; i < enabled_pl_constraints_.size(); i++) {
204 const mpq_class cost{enabled_pl_constraints_[i].constraint->Cost(env, enabled_pl_constraints_[i].active)};
206 if (cost > max_cost) {
214 DLINEAR_ASSERT(max_cost > 0,
"max_cost must be > 0");
216 const auto [constraint_ptr, active, fixed] = enabled_pl_constraints_[max_cost_index];
219 DLINEAR_ERROR(
"NNSoplexTheorySolver::InvertGreatestViolation: max cost belongs to a fixed constraint");
222 enabled_pl_constraints_[max_cost_index].fixed =
true;
226 soi_ += active ? -constraint.
active_soi() + constraint.inactive_soi()
227 : -constraint.inactive_soi() + constraint.active_soi();
229 enabled_pl_constraints_[max_cost_index].active = !active;
238void NNSoplexTheorySolver::UpdateExplanationsWithCurrentPiecewiseLinearLiterals(std::set<LiteralSet> &explanations) {
240 for (
const PlConstraint &enabled_pl_constraint : enabled_pl_constraints_) {
241 explanation.emplace(enabled_pl_constraint.active ? enabled_pl_constraint.constraint->active_var()
242 : enabled_pl_constraint.constraint->inactive_var(),
244 DLINEAR_DEV_FMT(
"addidg {} to explanation", enabled_pl_constraint.active
245 ? enabled_pl_constraint.constraint->active_var()
246 : enabled_pl_constraint.constraint->inactive_var());
248 if (explanations.contains(explanation))
return;
249 pl_explanations_.emplace(explanation);
250 explanations.emplace(explanation);
253void NNSoplexTheorySolver::SoiToObjFunction() {
254 DLINEAR_DEBUG_FMT(
"NNSoplexTheorySolver::SoiToObjFunction: soi_ = {}", soi_);
255 if (is_variable(soi_)) {
256 for (std::size_t i = 0; i < theory_col_to_var_.size(); i++) {
257 const Variable &var{theory_col_to_var_[i]};
258 spx_.changeObjRational(
static_cast<int>(i), var.equal_to(get_variable(soi_)) ? Rational(1) : Rational(0));
260 }
else if (is_multiplication(soi_)) {
261 for (std::size_t i = 0; i < theory_col_to_var_.size(); i++) {
262 const Variable &var{theory_col_to_var_[i]};
263 const auto it = get_base_to_exponent_map_in_multiplication(soi_).find(var);
264 spx_.changeObjRational(
static_cast<int>(i), it == get_base_to_exponent_map_in_multiplication(soi_).end()
266 : Rational(get_constant_in_multiplication(soi_).get_mpq_t()));
268 }
else if (is_addition(soi_)) {
269 for (std::size_t i = 0; i < theory_col_to_var_.size(); i++) {
270 const Variable &var{theory_col_to_var_[i]};
271 const auto it = get_expr_to_coeff_map_in_addition(soi_).find(var);
272 spx_.changeObjRational(
static_cast<int>(i), it == get_expr_to_coeff_map_in_addition(soi_).end()
274 : Rational(it->second.get_mpq_t()));
279void NNSoplexTheorySolver::Reset() {
280 SoplexTheorySolver::Reset();
282 enabled_pl_constraints_.clear();
285void NNSoplexTheorySolver::Consolidate(
const Box &box) {
286 if (is_consolidated_)
return;
287 enabled_pl_constraints_.reserve(pl_theory_lit_.size() / 2);
288 SoplexTheorySolver::Consolidate(box);
291void NNSoplexTheorySolver::EnableSpxRow(
int spx_row,
bool truth) {
293 const LpRowSense sense = truth ? spx_sense_[spx_row] : -spx_sense_[spx_row];
294 if (sense == LpRowSense::NQ)
return;
296 const mpq_class &rhs{spx_rhs_[spx_row]};
297 spx_.changeRangeRational(
299 sense == LpRowSense::GE || sense == LpRowSense::EQ ? Rational(gmp::ToMpq(rhs)) : Rational(-soplex::infinity),
300 sense == LpRowSense::LE || sense == LpRowSense::EQ ? Rational(gmp::ToMpq(rhs)) : Rational(soplex::infinity));
301 theory_rows_state_.at(spx_row) =
true;
302 DLINEAR_ASSERT(truth == theory_row_to_lit_[spx_row].truth,
"truth must be equal to stored lit truth");
303 DLINEAR_TRACE_FMT(
"NNSoplexTheorySolver::EnableLinearLiteral({} {} {})", theory_row_to_lit_[spx_row], sense, rhs);
Collection of variables with associated intervals.
A piecewise linear constraint is a constraint that assumes different linear functions depending on th...
bool fixed() const
Check whether the constraint is fixed.
const Variable & active_var() const
Get read-only access to the boolean variable associated with the theory literal of the constraint.
const Expression & active_soi() const
Get read-only access to the expression of the constraint.
const Expression & inactive_soi() const
Get read-only access to the expression of the constraint.
std::set< LiteralSet > Explanations
Set of explanations of the conflict.
Represents a symbolic environment (mapping from a variable to a value).
Represents a symbolic variable.
bool equal_to(const Variable &v) const
Checks the equality of two variables based on their ID values.
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.
A literal is a variable with an associated truth value, indicating whether it is true or false.