6#include "ContextImpl.h"
13#include "dlinear/solver/BoundImplicator.h"
14#include "dlinear/solver/CadicalSatSolver.h"
15#include "dlinear/solver/PicosatSatSolver.h"
16#include "dlinear/solver/SatResult.h"
17#include "dlinear/symbolic/IfThenElseEliminator.h"
18#include "dlinear/symbolic/literal.h"
19#include "dlinear/util/OptionValue.hpp"
20#include "dlinear/util/Stats.h"
21#include "dlinear/util/exception.h"
22#include "dlinear/util/logging.h"
24#ifdef DLINEAR_PYDLINEAR
25#include "pydlinear/interrupt.h"
27#ifdef DLINEAR_ENABLED_QSOPTEX
28#include "dlinear/solver/DeltaQsoptexTheorySolver.h"
30#ifdef DLINEAR_ENABLED_SOPLEX
31#include "dlinear/solver/CompleteSoplexTheorySolver.h"
32#include "dlinear/solver/DeltaSoplexTheorySolver.h"
33#include "dlinear/solver/NNSoplexTheorySolver.h"
38bool ParseBooleanOption([[maybe_unused]]
const std::string &key,
const std::string &val) {
39 if (val ==
"true")
return true;
40 if (val ==
"false")
return false;
41 DLINEAR_INVALID_ARGUMENT_EXPECTED(key, val,
"bool [true, false]");
43double ParseDoubleOption([[maybe_unused]]
const std::string &key,
const std::string &val) {
45 return std::stod(val);
46 }
catch (
const std::invalid_argument &e) {
47 DLINEAR_INVALID_ARGUMENT_EXPECTED(key, val,
"double");
48 }
catch (
const std::out_of_range &e) {
49 DLINEAR_OUT_OF_RANGE_FMT(
"Out of range value {} is provided for option {}. Expected double", val, key);
61 model_{config_.lp_solver()},
62 have_objective_{false},
64 predicate_abstractor_{config},
65 ite_eliminator_{config},
66 sat_solver_{GetSatSolver()},
67 theory_solver_{GetTheorySolver()} {
72 if (is_true(f))
return;
78 DLINEAR_DEBUG_FMT(
"ContextImpl::Assert({})", f);
79 const Formula no_ite{ite_eliminator_.Process(f)};
82 for (
const auto &[ite_expr, ite_var] : ite_eliminator_.variables()) AddToBox(ite_var);
83 stack_.push_back(no_ite);
84 sat_solver_->AddFormula(no_ite);
88 DLINEAR_TRACE_FMT(
"ContextImpl::AssertPiecewiseLinearFunction({})", var);
90 DLINEAR_ASSERT(is_relational(cond),
"Condition must be a relational formula");
92 const Formula condition_lit = predicate_abstractor_(cond);
93 const Formula active_lit = predicate_abstractor_(var - active == 0);
94 const Formula inactive_lit = predicate_abstractor_(var - inactive == 0);
96 const Formula force_assignment(condition_lit || !condition_lit);
97 const Formula active_assertion{active_lit || !condition_lit};
98 const Formula inactive_assertion{inactive_lit || condition_lit};
101 stack_.push_back(force_assignment);
102 stack_.push_back(active_assertion);
103 stack_.push_back(inactive_assertion);
104 sat_solver_->AddClause(force_assignment);
105 sat_solver_->AddClause(active_assertion);
106 sat_solver_->AddClause(inactive_assertion);
110 std::unique_ptr<PiecewiseLinearConstraint> &&constraint) {
111 return *pl_constraints_.emplace_back(std::move(constraint)).get();
115 DLINEAR_DEBUG(
"ContextImpl::Pop()");
122 DLINEAR_DEBUG(
"ContextImpl::Push()");
125 boxes_.push_back(boxes_.last());
130 if (!logic_.has_value()) DLINEAR_WARN(
"Logic is not set. Defaulting to QF_LRA.");
131 if (config_.skip_check_sat()) {
132 DLINEAR_DEBUG(
"ContextImpl::CheckSat() - Skipping SAT check");
137 const SatResult result = CheckSatCore(precision);
141 model_ = ExtractModel(
box());
142 DLINEAR_DEBUG_FMT(
"ContextImpl::CheckSat() - Found Model\n{}", model_);
145 DLINEAR_DEBUG(
"ContextImpl::CheckSat() - Model not found");
149 DLINEAR_DEBUG(
"ContextImpl::CheckSat() - Unknown");
153 DLINEAR_UNREACHABLE();
156 if (output_ ==
nullptr)
return result;
157 DLINEAR_DEBUG(
"ContextImpl::CheckSat() - Setting output");
158 output_->actual_precision = *precision;
159 UpdateAndPrintOutput(parse_smt_result(result));
164 if (!logic_.has_value()) DLINEAR_WARN(
"Logic is not set. Defaulting to QF_LRA.");
165 if (config_.skip_check_sat()) {
166 DLINEAR_DEBUG(
"ContextImpl::CheckOpt() - Skipping SAT check");
170 const LpResult result = CheckOptCore(obj_lo, obj_up);
172 model_ = ExtractModel(
box());
173 DLINEAR_DEBUG_FMT(
"ContextImpl::CheckOpt() - Found Model\n{}", model_);
175 DLINEAR_DEBUG(
"ContextImpl::CheckOpt() - Model not found");
179 if (output_ ==
nullptr)
return result;
180 DLINEAR_DEBUG(
"ContextImpl::CheckSat() - Setting output");
181 output_->lower_bound = *obj_lo;
182 output_->upper_bound = *obj_up;
183 UpdateAndPrintOutput(parse_smt_result(result));
188 DLINEAR_DEBUG_FMT(
"ContextImpl::AddToBox({})", v);
189 if (!
box().has_variable(v))
box().
Add(v);
193 DLINEAR_DEBUG_FMT(
"ContextImpl::DeclareVariable({})", v);
195 if (is_model_variable) MarkModelVariable(v);
199 DLINEAR_TRACE_FMT(
"ContextImpl::SetDomain({}, [{}, {}])", v, lb, ub);
200 const mpq_class &lb_fp = lb.
Evaluate();
201 const mpq_class &ub_fp = ub.
Evaluate();
207 MinimizeCore(obj_function);
212 MinimizeCore((-obj_function).Expand());
216 DLINEAR_DEBUG_FMT(
"ContextImpl::SetInfo({} ↦ {})", key, val);
221 const auto it = info_.find(key);
222 if (it == info_.end())
return "";
227 DLINEAR_DEBUG_FMT(
"ContextImpl::SetInterval({} = [{}, {}])", v, lb, ub);
228 if (lb > ub) DLINEAR_RUNTIME_ERROR_FMT(
"Lower bound {} is greater than upper bound {}.", lb, ub);
233 DLINEAR_DEBUG_FMT(
"ContextImpl::SetLogic({})", logic);
240 DLINEAR_RUNTIME_ERROR_FMT(
"Unsupported logic: {}", logic);
245 DLINEAR_DEBUG_FMT(
"ContextImpl::SetOption({} ↦ {})", key, val);
247 if (key ==
":precision") config_.m_precision().SetFromFile(ParseDoubleOption(key, val));
248 if (key ==
":produce-models")
return config_.m_produce_models().SetFromFile(ParseBooleanOption(key, val));
252 if (key ==
":produce-models")
return fmt::format(
"{}", config_.produce_models());
253 if (key ==
":precision")
return fmt::format(
"{}", config_.precision());
254 const auto it = option_.find(key);
255 if (it == option_.end())
return "";
260 if (
static_cast<int>(model_variables_.size()) ==
box.
size()) {
266 if (IsModelVariable(v)) {
267 new_box.Add(v,
box[v].lb(),
box[v].ub());
274 return model_variables_.find(v.get_id()) != model_variables_.end();
290 DLINEAR_ASSERT(!val.is_empty(),
"Variable cannot have an empty value interval");
291 env.
insert(var, val.ub());
293 for (
const Formula &assertion : stack_) {
294 if (!assertion.Evaluate(env)) {
295 DLINEAR_ERROR_FMT(
"Not satisfied clause: {} - model {}", assertion,
model);
303 DLINEAR_DEBUG(
"ContextImpl::CheckSatCore()");
304 DLINEAR_TRACE_FMT(
"ContextImpl::CheckSat: Box =\n{}",
box());
307 for (
const Formula &f : stack_.get_vector()) {
309 DLINEAR_DEBUG_FMT(
"ContextImpl::CheckSat: Found false formula = {}", f);
315 if (stack_.empty() || (stack_.size() == 1 && is_true(stack_.first()))) {
316 DLINEAR_DEBUG_FMT(
"ContextImpl::CheckSatCore() - Found Model\n{}",
box());
320 DLINEAR_DEV_FMT(
"STATE: {}", fmt::join(
assertions(),
"\n"));
326 predicate_abstractor_};
331 theory_solver_->AddLiterals();
332 theory_solver_->Consolidate(
box());
335 DLINEAR_TRACE_FMT(
"Fixed theory literals: {}", sat_solver_->FixedTheoryLiterals());
336 const std::set<LiteralSet> explanations{theory_solver_->PreprocessFixedLiterals(sat_solver_->FixedTheoryLiterals())};
337 if (!explanations.empty()) {
338 DLINEAR_DEBUG(
"ContextImpl::CheckSatCore() - Fixed bound check = UNSAT");
339 LearnExplanations(explanations);
343 DLINEAR_DEV(
"Start tightening bounds");
345 for (
const std::unique_ptr<PiecewiseLinearConstraint> &constraint : pl_constraints_) {
346 const std::set<LiteralSet> tight_explanations{constraint->TightenBounds(theory_solver_->m_fixed_preprocessor())};
347 DLINEAR_DEV_TRACE_FMT(
"Tighten Bounds: {} - {}", constraint->theory_var(), *constraint);
348 if (tight_explanations.empty())
continue;
349 DLINEAR_DEV_DEBUG(
"ContextImpl::CheckSatCore() - Tightening bounds check = UNSAT");
350 LearnExplanations(tight_explanations);
353 theory_solver_->Reset();
355 for (
auto it = pl_constraints_.begin(); it != pl_constraints_.end();) {
356 const LiteralSet learned_clauses{it->get()->LearnedClauses()};
357 for (
const Literal &learned_clause : learned_clauses) sat_solver_->AddLearnedClause(learned_clause);
360 const Variable &var = it->get()->theory_var();
361 const auto &bound = theory_solver_->fixed_theory_bounds().at(var);
362 fmt::println(
"Fixed Bounds: {} = [{}, {}]", var, bound.active_lower_bound().get_d(),
363 bound.active_upper_bound().get_d());
365 it = learned_clauses.empty() ? std::next(it) : pl_constraints_.erase(it);
368 if (!config_.onnx_file().empty()) {
370 nnSoplexTheorySolver.SetPiecewiseLinearConstraints(pl_constraints_);
373 bool have_unsolved =
false;
376#ifdef DLINEAR_PYDLINEAR
380 DLINEAR_DEV_DEBUG(
"New iteration");
383 const auto optional_model = sat_solver_->CheckSat();
386 if (!optional_model) {
388 DLINEAR_DEBUG(
"ContextImpl::CheckSatCore() - Sat Check = UNKNOWN");
389 DLINEAR_RUNTIME_ERROR(
"LP solver failed to solve some instances");
391 DLINEAR_DEBUG(
"ContextImpl::CheckSatCore() - Sat Check = UNSAT");
397 DLINEAR_DEBUG(
"ContextImpl::CheckSatCore() - Sat Check = SAT");
400 const std::vector<Literal> &boolean_model{optional_model->first};
401 const std::vector<Literal> &theory_model{optional_model->second};
404 for (
const auto &[var, truth] : boolean_model) {
405 box()[var] = truth ? 1 : 0;
411 theory_solver_->Reset();
413 if (!bound_explanations.empty()) {
414 DLINEAR_DEBUG(
"ContextImpl::CheckSatCore() - Enable bound check = UNSAT");
415 LearnExplanations(bound_explanations);
421 SatResult theory_result = theory_solver_->CheckSat(
box(), actual_precision, theory_explanations);
424 DLINEAR_DEBUG_FMT(
"ContextImpl::CheckSatCore() - Theory Check = {}", theory_result);
425 box() = theory_solver_->model();
426 return theory_result;
430 DLINEAR_DEBUG(
"ContextImpl::CheckSatCore() - Theory Check = UNSAT");
433 DLINEAR_WARN(
"ContextImpl::CheckSatCore() - Theory Check = UNKNOWN");
434 have_unsolved =
true;
435 theory_explanations.emplace(theory_model.cbegin(), theory_model.cend());
437 DLINEAR_ASSERT(!theory_explanations.empty(),
"theory_explanations must not be empty");
438 LearnExplanations(theory_explanations);
443 DLINEAR_RUNTIME_ERROR(
"CheckOptCore() Not implemented");
445void Context::Impl::MinimizeCore([[maybe_unused]]
const Expression &obj_expr) {
446 DLINEAR_RUNTIME_ERROR(
"MinimizeCore() Not implemented");
449 switch (config_.lp_solver()) {
450#ifdef DLINEAR_ENABLED_QSOPTEX
452 if (!config_.complete())
453 return std::make_unique<DeltaQsoptexTheorySolver>(predicate_abstractor_);
454 DLINEAR_RUNTIME_ERROR(
"Only delta-complete mode is supported for qsoptex");
456#ifdef DLINEAR_ENABLED_SOPLEX
458 if (!config_.onnx_file().empty())
459 return std::make_unique<NNSoplexTheorySolver>(predicate_abstractor_);
460 else if (config_.complete())
461 return std::make_unique<CompleteSoplexTheorySolver>(predicate_abstractor_);
463 return std::make_unique<DeltaSoplexTheorySolver>(predicate_abstractor_);
466 DLINEAR_UNREACHABLE();
470 switch (config_.sat_solver()) {
471#ifdef DLINEAR_ENABLED_CADICAL
473 return std::make_unique<CadicalSatSolver>(predicate_abstractor_);
475#ifdef DLINEAR_ENABLED_PICOSAT
477 return std::make_unique<PicosatSatSolver>(predicate_abstractor_);
480 DLINEAR_UNREACHABLE();
484 DLINEAR_DEBUG_FMT(
"ContextImpl::LearnExplanation(): size of explanation = {} - stack size = {}", explanation.size(),
485 stack_.get_vector().size());
486 DLINEAR_DEV_FMT(
"ContextImpl::LearnExplanation({})", explanation);
487 DLINEAR_ASSERT(!explanations_so_far.contains(explanation),
"Explanation already present, looping!");
488 DLINEAR_ASSERT(!explanation.empty(),
"No explanation is provided. Infinite loop detected.");
490 explanations_so_far.insert(explanation);
492 sat_solver_->AddLearnedClause(explanation);
496 DLINEAR_ASSERT(!explanations.empty(),
"Explanations cannot be empty");
497 for (
const LiteralSet &explanation : explanations) LearnExplanation(explanation);
501 if (output_ ==
nullptr)
return;
502 DLINEAR_DEBUG(
"ContextImpl::UpdateAndPrintOutput() - Setting output");
503 output_->result = smt_result;
505 if (config_.produce_models()) output_->model = model_;
506 if (config_.verify()) output_->complete_model =
box();
507 if (config_.with_timings()) {
508 DLINEAR_DEBUG(
"ContextImpl::UpdateAndPrintOutput() - Setting timings");
509 output_->sat_stats = sat_solver_->stats();
510 output_->ite_stats = ite_eliminator_.stats();
511 output_->theory_stats = theory_solver_->stats();
512 output_->preprocessor_stats = theory_solver_->preprocessor().stats() + theory_solver_->fixed_preprocessor().stats();
513 output_->predicate_abstractor_stats = predicate_abstractor_.stats();
514 output_->cnfizer_stats = sat_solver_->cnfizer_stats();
516 if (!config_.silent() && config_.csv()) {
517 std::cout <<
"file,complete,satSolver,lpSolver,assertions,precision,actualPrecision,simplexPhase,"
518 "boundPropagationType,boundPropagationFrequency,boundImplicationFrequency,satDefaultPhase,lpMode,"
519 "timeUnit,parserTime,satTime,preprocessorTime,theoryTime,smtTime,result\n";
520 std::cout << config_.filename() <<
","
521 << config_.complete() <<
","
522 << config_.sat_solver() <<
","
523 << config_.lp_solver() <<
","
524 << output_->n_assertions <<
","
525 << config_.precision() <<
","
526 << output_->actual_precision.get_d() <<
","
527 << config_.simplex_sat_phase() <<
","
528 << config_.actual_bound_propagation_type() <<
","
529 << config_.actual_bound_propagation_frequency() <<
","
530 << config_.actual_bound_implication_frequency() <<
","
531 << config_.sat_default_phase() <<
","
532 << config_.actual_lp_mode() <<
","
534 << output_->parser_stats.timer().seconds() <<
","
535 << output_->sat_stats.timer().seconds() <<
","
536 << output_->preprocessor_stats.timer().seconds() <<
","
537 << output_->theory_stats.timer().seconds() <<
","
538 << output_->smt_solver_timer.seconds() <<
","
539 << output_->result << std::endl;
540 }
else if (!config_.silent()) {
541 std::cout << *output_ << std::endl;
Use theory reasoning to add relations between literals using some simple theory inferences.
void Propagate()
Use theory reasoning to propagate some simple theory inferences.
Collection of variables with associated intervals.
void Add(const Variable &v)
Add the variable v to the box.
const std::vector< Variable > & variables() const
Get read-only access to the variables of the box.
const std::vector< Interval > & interval_vector() const
Return the interval vector of the box.
Config::LPSolver lp_solver() const
Get read-only access to the lp_solver of the box.
int size() const
Get read-only access to the number of variables of the box.
const Variable & variable(int i) const
Return the i -th variable in the box.
Simple dataclass used to store the configuration of the program.
@ CADICAL
Cadical Solver. Default option.
@ SOPLEX
Soplex Solver. Default option.
const LPSolver & lp_solver() const
Get read-write access to the lp_solver parameter of the configuration.
@ NEVER
Never run this preprocess, effectively disabling it.
void Assert(const Formula &f)
Assert a formula f.
Box ExtractModel(const Box &box) const
Extract a model from the box.
void Push()
Push the current set of assertions to the stack.
void SetDomain(const Variable &v, const Expression &lb, const Expression &ub)
Set a domain for the variable v, restricting it to the interval .
SatResult CheckSat(mpq_class *precision)
Check the satisfiability of the asserted formulas, and sets actual_precision to the actual max infeas...
void UpdateAndPrintOutput(SmtResult smt_result) const
Update the output_ with the last smt_result .
bool have_objective() const
Check whether or not there is an objective function (which may be zero) to optimize.
void SetOption(const std::string &key, const std::string &val)
Set an option key with a value val.
void LearnExplanations(const TheorySolver::Explanations &explanations)
The TheorySolver found a number of conflicts and the explanations are the set of literals that are re...
void SetInterval(const Variable &v, const mpq_class &lb, const mpq_class &ub)
Set the interval of v to in the current box (top one in boxes_).
bool Verify(const Box &model) const
Check whether the model satisfies all the assertions loaded in the context.
void SetLogic(Logic logic)
Set the current logic to logic.
const ScopedVector< Formula > & assertions() const
Get the the asserted formulas.
void MarkModelVariable(const Variable &v)
Mark the variable v as a model variable.
std::string GetInfo(const std::string &key) const
Get the info key.
void DeclareVariable(const Variable &v, bool is_model_variable)
Declare a variable v.
std::string GetOption(const std::string &key) const
Get the option key.
void SetInfo(const std::string &key, const std::string &val)
Set an info key with a value val.
bool is_max() const
Check whether or not the objective function (if present) is a maximization.
void Minimize(const Expression &obj_function)
Assert a formula minimizing a cost function f.
void AddToBox(const Variable &v)
Add the variable v to the current box.
void LearnExplanation(const LiteralSet &explanation)
The TheorySolver found a conflict and the explanation is the set of literals that are responsible.
SatResult CheckSatCore(mpq_class *actual_precision)
Check the satisfiability of the current set of assertions.
ScopedVector< Box > boxes_
Stack of boxes. The top one is the current box.
LpResult CheckOpt(mpq_class *obj_lo, mpq_class *obj_up)
Check the satisfiability of the asserted formulas, and (where possible) optimizes an objective functi...
void Maximize(const Expression &obj_function)
Assert a formula maximizing a cost function f.
std::unique_ptr< SatSolver > GetSatSolver()
Get the correct sat solver subclass based on the configuration.
bool IsModelVariable(const Variable &v) const
Check if the variable v is a model variable.
Impl(Config &config, SmtSolverOutput *output=nullptr)
Construct a context with config.
void Pop()
Pop the top of the stack of assertions.
void AssertPiecewiseLinearFunction(const Variable &var, const Formula &cond, const Expression &active, const Expression &inactive)
Assert a piecewise linear function with two possible branches.
LpResult CheckOptCore(mpq_class *obj_lo, mpq_class *obj_up)
Check the satisfiability of the asserted formulas, and (where possible) optimizes an objective functi...
Config & config_
Configuration of the context. It could be modified by the problem instance.
std::unique_ptr< TheorySolver > GetTheorySolver()
Get the correct theory solver subclass based on the configuration.
void DeclareVariable(const Variable &v, bool is_model_variable=true)
Declare a variable v.
const ScopedVector< Formula > & assertions() const
Get the the asserted formulas.
void SetInterval(const Variable &v, const mpq_class &lb, const mpq_class &ub)
Set the interval of v to in the current box (top one in boxes_).
const Box & model() const
Get a representation of a model computed by the solver in response to the last invocation of the chec...
const Box & box() const
Get the current active box from the top of the stack of boxes.
Specialised theory solver for neural networks using SoPlex.
A piecewise linear constraint is a constraint that assumes different linear functions depending on th...
std::set< LiteralSet > Explanations
Set of explanations of the conflict.
Represents a symbolic environment (mapping from a variable to a value).
std::pair< Environment::iterator, bool > insert(const key_type &key, const mapped_type &elem)
Inserts a pair (key, elem).
Represents a symbolic form of an expression.
mpq_class Evaluate(const Environment &env=Environment{}) const
Evaluates under a given environment (by default, an empty environment).
Represents a symbolic variable.
@ CONTINUOUS
A CONTINUOUS variable takes a mpq_class value.
bool is_dummy() const
Checks if this is a dummy variable (ID = 0) which is created by the default constructor.
Global namespace for the dlinear library.
SatResult
Result of running the theory solver over the literals decided by the SAT solver.
@ SAT_NO_RESULT
No result has been obtained yet.
@ SAT_SATISFIABLE
The problem is satisfiable.
@ SAT_UNSATISFIABLE
The problem is unsatisfiable.
@ SAT_UNSOLVED
The problem has not been solved. An error may have occurred.
@ SAT_DELTA_SATISFIABLE
The problem is satisfiable, but with a delta >= 0.
Logic
The SMT-LIB logic the SMT2 file is using.
@ QF_LRA
Quantifier free linear real arithmetic. It is the only one dlinear supports.
@ LRA
Linear real arithmetic.
SmtResult
SmtSolver Result based on the result of the solver.
@ SKIP_SAT
The user asked to skip the satisfiability check.
LpResult
Result of running the LP solver over the input problem.
@ LP_NO_RESULT
No result has been obtained yet.
@ LP_DELTA_OPTIMAL
The problem has been solved optimally, but with a delta.
@ LP_OPTIMAL
The problem has been solved optimally.
std::set< Literal > LiteralSet
A set of literals.
A literal is a variable with an associated truth value, indicating whether it is true or false.
Data struct containing the output of the solver, such as the result of the computation as well as som...