dlinear  0.0.1
Delta-complete SMT solver for linear programming
Loading...
Searching...
No Matches
BoundPreprocessor.cpp
1
6#include "BoundPreprocessor.h"
7
8#include <algorithm>
9#include <cstddef>
10#include <iterator>
11#include <list>
12#include <ostream>
13
14#include "dlinear/libs/libgmp.h"
15#include "dlinear/util/Infinity.h"
16#include "dlinear/util/exception.h"
17
18#if DEBUGGING_PREPROCESSOR
19#include <string>
20
21#include "dlinear/solver/Context.h"
22#endif
23
24namespace dlinear {
25
26namespace {
27#if 0
28void cartesian_product(const std::set<LiteralSet>& a, const std::set<LiteralSet>& b, const std::set<LiteralSet>& c,
29 const LiteralSet& explanation_to_add, std::set<LiteralSet>& destination) {
30 const std::set<LiteralSet> empty_set{{}};
31 const std::set<LiteralSet>& first = a.empty() ? empty_set : a;
32 const std::set<LiteralSet>& second = b.empty() ? empty_set : b;
33 const std::set<LiteralSet>& third = c.empty() ? empty_set : c;
34 for (const auto& a_set : first) {
35 for (const auto& b_set : second) {
36 for (const auto& c_set : third) {
37 LiteralSet new_set;
38 new_set.insert(a_set.begin(), a_set.end());
39 new_set.insert(b_set.begin(), b_set.end());
40 new_set.insert(c_set.begin(), c_set.end());
41 new_set.insert(explanation_to_add.begin(), explanation_to_add.end());
42 destination.insert(new_set);
43 }
44 }
45 }
46}
47#endif
48
49#if DEBUGGING_PREPROCESSOR
50
51bool print_all = false;
52Variable find_variable(const BoundPreprocessor& preprocessor, const std::string& name) {
53 for (const Variable& var : preprocessor.theory_cols())
54 if (var.get_name() == name) return var;
55 DLINEAR_UNREACHABLE();
56}
57bool check_explanation(const BoundPreprocessor& preprocessor, const LiteralSet& explanation) {
58 mpq_class zero{0};
59 Config config = preprocessor.config();
60
61 config.m_filename() = "";
62 config.m_read_from_stdin() = false;
63 config.m_disable_eq_propagation() = true;
64 Context smt_solver{config};
65 for (const auto& [var, truth] : explanation) {
66 const Formula f = truth ? preprocessor.predicate_abstractor()[var] : !preprocessor.predicate_abstractor()[var];
67 for (const Variable& free : f.GetFreeVariables()) {
68 smt_solver.DeclareVariable(free);
69 }
70 smt_solver.Assert(f);
71 }
72 const auto result = smt_solver.CheckSat(&zero);
73 if (result != SatResult::SAT_UNSATISFIABLE) {
74 // DLINEAR_RUNTIME_ERROR_FMT("The explanation {} is not valid", explanation);
75 print_all = true;
76 return false;
77 }
78 return true;
79}
80[[maybe_unused]] bool check_explanation(const BoundPreprocessor& preprocessor,
81 const std::set<LiteralSet>& explanations) {
82 for (const auto& explanation : explanations) {
83 return check_explanation(preprocessor, explanation);
84 }
85 return true;
86}
87#endif
88} // namespace
89
91 : config_{predicate_abstractor.config()},
92 predicate_abstractor_{predicate_abstractor},
93 stats_{config_.with_timings(), "BoundPreprocessor", "Process"} {}
94
96 DLINEAR_TRACE_FMT("BoundPreprocessor::AddVariable({})", var);
99}
100
101std::set<LiteralSet> BoundPreprocessor::EnableLiterals(const std::vector<Literal>& enabled_literals) {
102 std::set<LiteralSet> explanations;
103 EnableLiterals(enabled_literals, explanations);
104 return explanations;
105}
106void BoundPreprocessor::EnableLiterals(const std::vector<Literal>& enabled_literals,
107 std::set<LiteralSet>& explanations) {
108 for (const Literal& l : enabled_literals) {
109 std::set<LiteralSet> explanation{EnableLiteral(l)};
110 if (!explanation.empty()) explanations.insert(explanation.begin(), explanation.end());
111 }
112}
113
114std::set<LiteralSet> BoundPreprocessor::EnableLiteral(const Literal& lit) {
115 std::set<LiteralSet> explanations;
116 EnableLiteral(lit, explanations);
117 return explanations;
118}
119void BoundPreprocessor::EnableLiteral(const Literal& lit, std::set<LiteralSet>& explanations) {
120 if (enabled_literals_.contains(lit)) return;
121 DLINEAR_TRACE_FMT("BoundPreprocessor::EnableLiteral({})", lit);
122 const auto& [formula_var, truth] = lit;
123 const Formula& formula = predicate_abstractor_[formula_var];
124
125 // If the formula is a simple bound (e.g. x <= 1), immediately add it to the theory_bounds
126 if (IsSimpleBound(formula)) {
127 // We know there is exactly 1 variable in the formula
128 const Variable& var = *formula.GetFreeVariables().cbegin();
129 BoundVector& bounds = theory_bounds_.at(var);
130 // Add the simple bound to the theory_bound. If a violation is detected, report it immediately
131 const BoundIterator violation{bounds.AddBound(GetSimpleBound(lit, formula))};
132 if (!violation.empty()) {
133 DLINEAR_DEBUG_FMT("BoundPreprocessor::EnableLiteral: {} conflict found", violation.explanation());
134 violation.explanations(explanations, lit);
135 }
136 const mpq_class* const eq_bound = bounds.GetActiveEqualityBound();
137 if (eq_bound != nullptr) {
138 DLINEAR_ASSERT(!env_.contains(var) || env_[var] == *eq_bound, "No conflict should arise from this assignment");
139 env_[var] = *eq_bound;
140 }
141 }
142 enabled_literals_.insert(lit);
143 DLINEAR_TRACE_FMT("BoundPreprocessor::EnableLiteral: added constraint {}", lit);
144}
145
147 const auto it = theory_bounds_.find(var);
148 if (it == theory_bounds_.end()) return;
149 it->second.GetActiveExplanation(explanation);
150}
151BoundPreprocessor::Explanations BoundPreprocessor::Process() {
152 Explanations explanations;
153 Process(enabled_literals_, explanations);
154 return explanations;
155}
156void BoundPreprocessor::Process(Explanations& explanations) { Process(enabled_literals_, explanations); }
157BoundPreprocessor::Explanations BoundPreprocessor::Process(const LiteralSet& enabled_literals) {
158 Explanations explanations;
159 Process(enabled_literals, explanations);
160 return explanations;
161}
162void BoundPreprocessor::Process(const LiteralSet& enabled_literals, Explanations& explanations) {
164 "Method Process should not be called with a frequency of NEVER");
165 TimerGuard timer_guard(&stats_.m_timer(), stats_.enabled());
167
168 DLINEAR_TRACE_FMT("BoundPreprocessor::Process({})", enabled_literals);
169 std::list<Literal> mutable_enabled_formula_vars{enabled_literals.begin(), enabled_literals.end()};
170
171 // Remove all rows that have only one free variable and the != relations, since they can't be propagated
172 mutable_enabled_formula_vars.remove_if([this](const Literal& lit) {
173 const Formula& formula = predicate_abstractor_[lit.var];
174 return formula.GetFreeVariables().size() <= 1 || IsNotEqualTo(formula, lit.truth);
175 });
176
177 PropagateConstraints(mutable_enabled_formula_vars, explanations);
178 DLINEAR_DEBUG_FMT("BoundPreprocessor::Process: {} conflict found in propagation", explanations.size());
179 if (!explanations.empty()) return;
180
181 // Add back all not equal to relations
182 for (const Literal& lit : enabled_literals) {
183 const Formula& formula = predicate_abstractor_[lit.var];
184 if (formula.GetFreeVariables().size() < 2) continue;
185 if (IsNotEqualTo(formula, lit.truth)) mutable_enabled_formula_vars.push_back(lit);
186 }
187
188 EvaluateFormulas(mutable_enabled_formula_vars, explanations);
189 DLINEAR_DEBUG_FMT("BoundPreprocessor::Process: {} conflict found in evaluation", explanations.size());
190}
191
192void BoundPreprocessor::SetInfinityBounds(const Variable& var, const mpq_class& lb, const mpq_class& ub) {
193 theory_bounds_.insert_or_assign(var, BoundVector{lb, ub});
194}
195
197 env_ = Environment{};
198 for (auto& [var, bounds] : theory_bounds_) bounds.Clear();
199 temporary_mpq_vector_.clear();
200 enabled_literals_.clear();
201}
202void BoundPreprocessor::Clear(const BoundPreprocessor& fixed_preprocessor) {
203 env_ = fixed_preprocessor.env_;
204 theory_bounds_ = fixed_preprocessor.theory_bounds_;
205 temporary_mpq_vector_.clear();
206 enabled_literals_ = fixed_preprocessor.enabled_literals_;
207}
208
209bool BoundPreprocessor::PropagateEqPolynomial(const Literal& lit, const Variable& var_to_propagate,
210 Explanations& explanations) {
211 DLINEAR_TRACE_FMT("BoundPreprocessor::PropagateEqPolynomial({})", lit);
212
213 const Formula& formula = predicate_abstractor_[lit.var];
214
215 DLINEAR_ASSERT_FMT(IsEqualTo(formula, lit.truth), "Lit must encode an equal to relation. Got {}", lit);
216 DLINEAR_ASSERT_FMT(formula.IsFlattened(), "The formula must be flattened. Got {}", formula);
217 DLINEAR_ASSERT(ShouldPropagateEqPolynomial(lit), "The formula should be propagated");
218 DLINEAR_ASSERT(!env_.contains(var_to_propagate), "The variable must not be in the environment yet");
219 DLINEAR_ASSERT(!var_to_propagate.is_dummy(), "The variable must be valid");
220
221 LiteralSet explanation{};
222 std::vector<Variable> dependencies;
223 dependencies.reserve(formula.GetFreeVariables().size() - 1);
224 mpq_class rhs{get_constant_value(get_rhs_expression(formula))};
225 mpq_class var_coeff{};
226 for (const auto& [expr, coeff] : get_expr_to_coeff_map_in_addition(get_lhs_expression(formula))) {
227 DLINEAR_ASSERT(is_variable(expr), "All expressions in lhs formula must be variables");
228 const Variable& var = get_variable(expr);
229 if (var.equal_to(var_to_propagate)) {
230 var_coeff = coeff;
231 continue;
232 }
233 rhs -= env_.at(var) * coeff;
234 dependencies.emplace_back(var);
235 }
236 DLINEAR_ASSERT(var_coeff != 0, "The propagated variable coefficient cannot be 0");
237
238 // Calculate the propagated value of the variable
239 rhs /= var_coeff;
240 // if (var_propagated.get_name() == "x_11") fmt::println("Propagating {} => {}", var_propagated, lit);
241 // Add all the dependencies edges to the graph
242 for (const Variable& dependency : dependencies) {
243 const LiteralSet dependency_explanation = theory_bounds_.at(dependency).GetActiveEqExplanation();
244 explanation.insert(dependency_explanation.begin(), dependency_explanation.end());
245 // if (var_propagated.get_name() == "x_11") fmt::println("{} => {}", dependency, theory_bounds_.at(dependency));
246 }
247 const auto [env_it, inserted] = env_.insert(var_to_propagate, rhs);
248 DLINEAR_ASSERT(inserted, "The variable was not in the environment before");
249 // fmt::println("BoundPreprocessor::PropagateConstraints: {} = {} thanks to constraint {} and {} ({})",
250 // var_propagated,
251 // rhs, lit, dependencies, explanation);
252 BoundIterator violation{
253 theory_bounds_.at(var_to_propagate).AddBound(env_it->second, LpColBound::B, lit, explanation)};
254 if (!violation.empty()) {
255 explanation.insert(lit);
256 violation.explanation(explanation);
257 DLINEAR_DEBUG_FMT("BoundPreprocessor::PropagateConstraints: {} conflict found", explanation);
258 explanations.insert(explanation);
259 // Remove the propagated constraint from the environment
260 env_.erase(env_it);
261 // fmt::println("BoundPreprocessor::PropagateConstraints: {} conflict found", explanation);
262 return false;
263 }
264 DLINEAR_TRACE_FMT("BoundPreprocessor::PropagateConstraints: {} = {} thanks to constraint {} and {}", var_to_propagate,
265 rhs, lit, dependencies);
266 return true;
267}
268
270 const dlinear::drake::symbolic::Variable& var_to_propagate,
271 const std::vector<Bound>& assumptions,
272 dlinear::BoundPreprocessor::Explanations& explanations) {
273 BoundVector& bounds = theory_bounds_.at(var_to_propagate);
274 DLINEAR_DEV_FMT("Bounds initial: {}", bounds);
275 for (auto assumption = assumptions.begin(); assumption != assumptions.end(); ++assumption) {
276 BoundIterator violation{bounds.AddBound(*assumption)};
277 if (!violation.empty()) {
278 for (auto added_assumption = assumptions.begin(); added_assumption != assumption; ++added_assumption) {
279 bounds.RemoveBound(*added_assumption);
280 }
281 return false;
282 }
283 }
284 DLINEAR_DEV_FMT("Bounds assumptions: {}", bounds);
285
286 const bool res = PropagateBoundsPolynomial(lit, var_to_propagate, explanations);
287 for (const Bound& assumption : assumptions) bounds.RemoveBound(assumption);
288 DLINEAR_DEV_FMT("Bounds after: {}", bounds);
289 return res;
290}
291
292bool BoundPreprocessor::PropagateBoundsPolynomial(const Literal& lit, const Variable& var_to_propagate,
293 Explanations& explanations) {
294 DLINEAR_TRACE_FMT("BoundPreprocessor::PropagateInPolynomial({})", lit);
295
296 const Formula& formula = predicate_abstractor_[lit.var];
297
298 DLINEAR_ASSERT(is_relational(formula), "Formula should be a relational relation other than = or !=");
299 DLINEAR_ASSERT(formula.IsFlattened(), "The formula must be flattened");
300 DLINEAR_ASSERT(!var_to_propagate.is_dummy(), "The variable must be valid");
301
302 BoundVector& var_bounds = theory_bounds_.at(var_to_propagate);
303 LiteralSet l_explanation{};
304 LiteralSet u_explanation{};
305 std::vector<Variable> dependencies;
306 dependencies.reserve(formula.GetFreeVariables().size() - 1);
307 mpq_class l_rhs{get_constant_value(get_rhs_expression(formula))};
308 mpq_class u_rhs{get_constant_value(get_rhs_expression(formula))};
309 mpq_class var_coeff{};
310 for (const auto& [expr, coeff] : get_expr_to_coeff_map_in_addition(get_lhs_expression(formula))) {
311 DLINEAR_ASSERT(is_variable(expr), "All expressions in lhs formula must be variables");
312 const Variable& var = get_variable(expr);
313 if (var.equal_to(var_to_propagate)) {
314 var_coeff = coeff;
315 continue;
316 }
317 DLINEAR_ASSERT(theory_bounds_.at(var).IsBounded(),
318 "All other variable, except the one being propagated, must be bounded");
319 const mpq_class& lower_bound = theory_bounds_.at(var).active_lower_bound();
320 const mpq_class& upper_bound = theory_bounds_.at(var).active_upper_bound();
321 dependencies.emplace_back(var);
322 if (coeff > 0) {
323 l_rhs -= upper_bound * coeff;
324 u_rhs -= lower_bound * coeff;
325 theory_bounds_.at(var).GetActiveBound(upper_bound).explanation(l_explanation);
326 theory_bounds_.at(var).GetActiveBound(lower_bound).explanation(u_explanation);
327 } else {
328 l_rhs -= lower_bound * coeff;
329 u_rhs -= upper_bound * coeff;
330 theory_bounds_.at(var).GetActiveBound(lower_bound).explanation(l_explanation);
331 theory_bounds_.at(var).GetActiveBound(upper_bound).explanation(u_explanation);
332 }
333 }
334 DLINEAR_ASSERT(var_coeff != 0, "The propagated variable coefficient cannot be 0");
335
336 // Calculate the propagated value of the bounds
337 l_rhs /= var_coeff;
338 u_rhs /= var_coeff;
339
340 if (var_coeff < 0) {
341 std::swap(l_rhs, u_rhs);
342 std::swap(l_explanation, u_explanation);
343 }
344
345 // fmt::println("PropagateBoundPolynomial: Propagating {}\n{} <= {} <= {}\n{} ({}) <= {} <= ({})", lit, l_rhs,
346 // var_propagated, u_rhs, var_coeff, l_explanation, var_coeff, u_explanation);
347
348 // The formula is an equality relation (==)
349 if (IsEqualTo(formula, lit.truth)) {
350 // Both bounds are equal. Add an equality bound to the variable
351 if (l_rhs == u_rhs) {
352 DLINEAR_ASSERT_FMT(l_explanation == u_explanation, "The explanations must be the same. {} vs {} instead",
353 l_explanation, u_explanation);
354 const Bound bound{StoreTemporaryMpq(l_rhs), LpColBound::B, lit, l_explanation};
355 BoundIterator violation{var_bounds.AddBound(bound)};
356 if (!violation.empty()) {
357 l_explanation.insert(lit);
358 // temporary_mpq_vector_.pop_back(); // Remove the unused bound
359 violation.explanation(l_explanation);
360 DLINEAR_DEBUG_FMT("BoundPreprocessor::PropagateConstraints2: Eq bound {} conflict found", l_explanation);
361 explanations.insert(l_explanation);
362 return false;
363 }
364 DLINEAR_ASSERT(!env_.contains(var_to_propagate) || env_[var_to_propagate] == *var_bounds.GetActiveEqualityBound(),
365 "No conflict should arise from this assignment");
366 env_[var_to_propagate] = *var_bounds.GetActiveEqualityBound();
367 return true;
368 }
369 // The two bounds are different. Add the lower and upper bounds to the variable
370 const Bound lower_bound{StoreTemporaryMpq(l_rhs), LpColBound::L, lit, l_explanation};
371 if (l_rhs >= var_bounds.active_lower_bound()) {
372 BoundIterator violation{var_bounds.AddBound(lower_bound)};
373 if (!violation.empty()) {
374 temporary_mpq_vector_.pop_back(); // Remove the unused lower bound
375 l_explanation.insert(lit);
376 violation.explanation(l_explanation);
377 DLINEAR_DEBUG_FMT("BoundPreprocessor::PropagateConstraints2: Lower bound {} conflict found", l_explanation);
378 explanations.insert(l_explanation);
379 return false;
380 }
381 } else {
382 temporary_mpq_vector_.pop_back(); // Remove the unused lower bound
383 }
384 const Bound upper_bound{StoreTemporaryMpq(u_rhs), LpColBound::U, lit, u_explanation};
385 if (u_rhs <= var_bounds.active_upper_bound()) {
386 BoundIterator violation{var_bounds.AddBound(upper_bound)};
387 if (!violation.empty()) {
388 temporary_mpq_vector_.pop_back(); // Remove the unused upper bound
389 // Also remove the unused lower bound, if had been added in the previous step
390 if (var_bounds.RemoveBound(lower_bound)) temporary_mpq_vector_.pop_back();
391 u_explanation.insert(lit);
392 violation.explanation(u_explanation);
393 DLINEAR_DEBUG_FMT("BoundPreprocessor::PropagateConstraints2: Upper bound {} conflict found", u_explanation);
394 explanations.insert(u_explanation);
395 return false;
396 }
397 } else {
398 temporary_mpq_vector_.pop_back(); // Remove the unused upper bound
399 }
400 DLINEAR_TRACE_FMT("BoundPreprocessor::PropagateConstraints: {} = [{}, {}] thanks to constraint {} and {}",
401 var_to_propagate, l_rhs, u_rhs, lit, dependencies);
402 // fmt::println("BoundPreprocessor::PropagateConstraints: {} = [{}, {}] thanks to constraint {} and {}",
403 // var_propagated, l_rhs, u_rhs, lit, dependencies);
404 const mpq_class* const eq_bound = var_bounds.GetActiveEqualityBound();
405 if (eq_bound != nullptr) {
406 DLINEAR_ASSERT(!env_.contains(var_to_propagate) || env_[var_to_propagate] == *eq_bound,
407 "No conflict should arise from this assignment");
408 env_[var_to_propagate] = *eq_bound;
409 }
410 return true;
411 }
412
413 Bound bound;
414 // Add the bound on the single unbounded variable based on the upper and lower bound computed over the polynomial
415 if (var_coeff > 0) {
416 if (IsGreaterThan(formula, lit.truth)) {
417 bound = {StoreTemporaryMpq(l_rhs), LpColBound::SL, lit, l_explanation};
418 } else if (IsGreaterThanOrEqualTo(formula, lit.truth)) {
419 bound = {StoreTemporaryMpq(l_rhs), LpColBound::L, lit, l_explanation};
420 } else if (IsLessThan(formula, lit.truth)) {
421 bound = {StoreTemporaryMpq(u_rhs), LpColBound::SU, lit, u_explanation};
422 } else {
423 DLINEAR_ASSERT(IsLessThanOrEqualTo(formula, lit.truth), "The formula must be a less than or equal to relation");
424 bound = {StoreTemporaryMpq(u_rhs), LpColBound::U, lit, u_explanation};
425 }
426 } else {
427 DLINEAR_ASSERT(var_coeff < 0, "The coefficient must be less than 0");
428 if (IsGreaterThan(formula, lit.truth)) {
429 bound = {StoreTemporaryMpq(u_rhs), LpColBound::SU, lit, u_explanation};
430 } else if (IsGreaterThanOrEqualTo(formula, lit.truth)) {
431 bound = {StoreTemporaryMpq(u_rhs), LpColBound::U, lit, u_explanation};
432 } else if (IsLessThan(formula, lit.truth)) {
433 bound = {StoreTemporaryMpq(l_rhs), LpColBound::SL, lit, l_explanation};
434 } else {
435 DLINEAR_ASSERT(IsLessThanOrEqualTo(formula, lit.truth), "The formula must be a less than or equal to relation");
436 bound = {StoreTemporaryMpq(l_rhs), LpColBound::L, lit, l_explanation};
437 }
438 }
439 // fmt::println("BoundPreprocessor::PropagateConstraints: {} = [{}, {}] thanks to constraint {} and {}",
440 // var_propagated,
441 // l_rhs, u_rhs, lit, dependencies);
442 BoundIterator violation{var_bounds.AddBound(bound)};
443 if (!violation.empty()) {
444 bound.explanation.insert(lit);
445 temporary_mpq_vector_.pop_back(); // Remove the unused bound
446 violation.explanation(bound.explanation);
447 DLINEAR_DEBUG_FMT("BoundPreprocessor::PropagateConstraints: {} conflict found", bound.explanation);
448 explanations.insert(bound.explanation);
449 return false;
450 }
451 DLINEAR_TRACE_FMT("BoundPreprocessor::PropagateConstraints: {} = [{}, {}] thanks to constraint {} and {}",
452 var_to_propagate, l_rhs, u_rhs, lit, dependencies);
453 const mpq_class* const eq_bound = var_bounds.GetActiveEqualityBound();
454 if (eq_bound != nullptr) {
455 DLINEAR_ASSERT(!env_.contains(var_to_propagate) || env_[var_to_propagate] == *eq_bound,
456 "No conflict should arise from this assignment");
457 env_[var_to_propagate] = *eq_bound;
458 }
459 return true;
460}
461
462void BoundPreprocessor::PropagateConstraints(std::list<Literal>& enabled_literals, Explanations& explanations) {
463 DLINEAR_TRACE("BoundPreprocessor::PropagateConstraints()");
464 // TODO(tend): reintroduce PropagateEqBinConstraints();
466 PropagateEqConstraints(enabled_literals, explanations);
468 return;
469 PropagateBoundConstraints(enabled_literals, explanations);
470}
471void BoundPreprocessor::PropagateEqConstraints(std::list<Literal>& enabled_literals, Explanations& explanations) {
472 DLINEAR_TRACE_FMT("BoundPreprocessor::PropagateEqConstraints({})", enabled_literals);
473 bool continue_propagating;
474 // While we still have constraints to propagate...
475 do {
476 continue_propagating = false;
477 for (auto it = enabled_literals.begin(); it != enabled_literals.end();) {
478 const Literal& lit = *it;
479 const Variable* const var_to_propagate = ShouldPropagateEqPolynomial(lit);
480 if (var_to_propagate == nullptr) {
481 ++it;
482 continue;
483 }
484 // Try to propagate the bounds to the variable. If a conflict is detected, update the explanation
485 PropagateEqPolynomial(lit, *var_to_propagate, explanations);
486 // Since we did a propagation (with a violation or not), delete the literal.
487 // Also signal that the other literals could have been updated. Therefore, continue the propagation
488 continue_propagating = true;
489 it = enabled_literals.erase(it);
490 }
491 } while (continue_propagating && explanations.empty());
492 DLINEAR_TRACE_FMT("BoundPreprocessor::PropagateEqConstraints: explanation -> {}", explanations);
493}
494void BoundPreprocessor::PropagateBoundConstraints(std::list<Literal>& enabled_literals, Explanations& explanations) {
495 DLINEAR_TRACE_FMT("BoundPreprocessor::PropagateBoundConstraints({})", enabled_literals);
496 bool continue_propagating;
497 // While we still have constraints to propagate...
498 do {
499 continue_propagating = false;
500 for (auto it = enabled_literals.begin(); it != enabled_literals.end();) {
501 const Literal& lit = *it;
502 // Check if there is a variable candidate for the propagation
503 const Variable* const var_to_propagate = ShouldPropagateBoundsPolynomial(lit);
504 if (var_to_propagate == nullptr) {
505 ++it;
506 continue;
507 }
508 // Try to propagate the bounds to the variable. If a conflict is detected, update the explanation
509 PropagateBoundsPolynomial(lit, *var_to_propagate, explanations);
510 // Since we did a propagation (with a violation or not), delete the literal.
511 // Also signal that the other literals could have been updated. Therefore, continue the propagation
512 continue_propagating = true;
513 it = enabled_literals.erase(it);
514 }
515 } while (continue_propagating && explanations.empty());
516 DLINEAR_TRACE_FMT("BoundPreprocessor::PropagateBoundConstraints: explanation -> {}", explanations);
517}
518
519void BoundPreprocessor::EvaluateFormulas(std::list<Literal>& enabled_literals, Explanations& explanations) {
520 DLINEAR_ASSERT(explanations.empty(), "The explanations vector must be empty");
521 DLINEAR_TRACE("BoundPreprocessor::EvaluateFormulas()");
522 for (const Literal& lit : enabled_literals) {
523 if (!ShouldEvaluate(lit)) continue;
524 const Formula& formula = predicate_abstractor_[lit.var];
525 const bool satisfied = formula.Evaluate(env_) == lit.truth;
526 if (!satisfied) {
527 DLINEAR_DEBUG_FMT("BoundPreprocessor::EvaluateFormulas: {} => FAIL", lit);
528 FormulaViolationExplanation(lit, formula, explanations);
529 }
530 }
531}
532
533void BoundPreprocessor::FormulaViolationExplanation(const Literal& lit, const Formula& formula,
534 Explanations& explanations) {
535 DLINEAR_TRACE_FMT("BoundPreprocessor::FormulaViolationExplanation({})", formula);
536 // TODO: produce more than just one explanation! Produce as many as possible!
537 LiteralSet explanation;
538 explanation.insert(lit);
539 for (const auto& var : formula.GetFreeVariables()) {
540 DLINEAR_ASSERT(env_.find(var) != env_.end(), "All free variables must be in the environment");
541 GetExplanation(var, explanation);
542 }
543#if DEBUGGING_PREPROCESSOR
544 const bool res = check_explanation(*this, explanation);
545 if (!res) {
546 for (const auto& var : formula.GetFreeVariables()) {
547 // fmt::println("Explanation origins: {} --> {}", var, GetExplanationOrigins(var));
548 LiteralSet ex;
549 GetExplanation(var, ex);
550 // fmt::println("Explanation: {} --> {}", var, ex);
551 }
552 for (const auto& var_name : {"x_87", "x_97"}) {
553 Variable var = find_variable(*this, var_name);
554 // fmt::println("Explanation origins: {} --> {}", var, GetExplanationOrigins(var));
555 LiteralSet ex;
556 GetExplanation(var, ex);
557 // fmt::println("Explanation: {} --> {}", var, ex);
558 }
559 DLINEAR_ASSERT(res, "Explanation must be valid");
560 }
561#endif
562 explanations.insert(explanation);
563}
564
565bool BoundPreprocessor::ShouldEvaluate(const Literal& lit) const {
566 DLINEAR_TRACE_FMT("BoundPreprocessor::ShouldEvaluate({})", lit);
567 const Formula& formula = predicate_abstractor_[lit.var];
568 return ShouldEvaluate(formula);
569}
570bool BoundPreprocessor::ShouldEvaluate(const Formula& formula) const {
571 DLINEAR_TRACE_FMT("BoundPreprocessor::ShouldEvaluate({})", formula);
572 // All free variables must be in the environment
573 return std::all_of(formula.GetFreeVariables().begin(), formula.GetFreeVariables().end(),
574 [this](const Variable& v) { return env_.contains(v); });
575}
576
577const Variable* BoundPreprocessor::ShouldPropagateEqPolynomial(const Literal& lit) const {
578 DLINEAR_TRACE_FMT("BoundPreprocessor::ShouldPropagateEqPolynomial({})", lit);
579 const auto& [formula_var, truth] = lit;
580 const Formula& formula = predicate_abstractor_[formula_var];
581 // There must be exactly two free variables and an equality relation between them
582 if (!IsEqualTo(formula, truth)) return nullptr;
583 return ShouldPropagateEqPolynomial(formula);
584}
585const Variable* BoundPreprocessor::ShouldPropagateEqPolynomial(const Formula& formula) const {
586 DLINEAR_TRACE_FMT("BoundPreprocessor::ShouldPropagateEqPolynomial({})", formula);
587 // There must be exactly two free variables and an equality relation between them
588 if (!is_equal_to(formula) && !is_not_equal_to(formula)) return nullptr;
589 if (formula.GetFreeVariables().size() < 2) return nullptr;
590 DLINEAR_ASSERT(is_addition(get_lhs_expression(formula)), "lhs expression must be an addition");
591
592 // The formula must be of the form 'a1x1 + a2x2 + ... + anxn + c = b', with ai,b != 0
593 const Variable* missing_var = nullptr;
594 for (const auto& [expr, coeff] : get_expr_to_coeff_map_in_addition(get_lhs_expression(formula))) {
595 DLINEAR_ASSERT(is_variable(expr), "All expressions in lhs formula must be variables");
596 if (env_.find(get_variable(expr)) != env_.cend()) continue;
597 // There is more than one variable missing its value
598 if (missing_var != nullptr) return nullptr;
599 missing_var = &get_variable(expr);
600 }
601 return missing_var;
602}
603const Variable* BoundPreprocessor::ShouldPropagateBoundsPolynomial(const Literal& lit) const {
604 DLINEAR_TRACE_FMT("BoundPreprocessor::ShouldPropagateInPolynomial({})", lit);
605 const auto& [formula_var, truth] = lit;
606 const Formula& formula = predicate_abstractor_[formula_var];
607 // There must be exactly two free variables and an equality relation between them
608 if (!is_relational(formula)) return nullptr;
609 if (IsNotEqualTo(formula, truth)) return nullptr;
610 return ShouldPropagateBoundsPolynomial(formula);
611}
612const Variable* BoundPreprocessor::ShouldPropagateBoundsPolynomial(const Formula& formula) const {
613 DLINEAR_TRACE_FMT("BoundPreprocessor::ShouldPropagateInPolynomial({})", formula);
614 // There must be more than two free variables and an inequality relation between them
615 if (!is_relational(formula)) return nullptr;
616 if (formula.GetFreeVariables().size() < 2) return nullptr;
617 DLINEAR_ASSERT(is_addition(get_lhs_expression(formula)), "lhs expression must be an addition");
618
619 // TODO(tend): being naive, this approach will not propagate some bounds that could be propagated
620 // since it forces the dependency variables to be bounded both ways for propagation
621 // The formula must be of the form 'a1x1 + a2x2 + ... + anxn + c <=> b', with ai,b != 0 and <=> in {<, <=, >, >=, !=}
622 const Variable* missing_var = nullptr;
623 for (const auto& [expr, coeff] : get_expr_to_coeff_map_in_addition(get_lhs_expression(formula))) {
624 DLINEAR_ASSERT(is_variable(expr), "All expressions in lhs formula must be variables");
625 const Variable& var = get_variable(expr);
626 const auto it = theory_bounds_.find(var);
627 if (it == theory_bounds_.end() || !it->second.IsBounded()) {
628 // There is more than one variable missing its value
629 if (missing_var != nullptr) return nullptr;
630 missing_var = &var;
631 }
632 }
633 return missing_var;
634}
635
636std::pair<Variable, Variable> BoundPreprocessor::ExtractBoundEdge(const Formula& formula) const {
637 DLINEAR_ASSERT(formula.GetFreeVariables().size() == 2, "Formula should have exactly two free variables");
638 DLINEAR_ASSERT(formula.IsFlattened(), "The formula must be flattened");
639 const Expression& lhs = get_lhs_expression(formula);
640
641 const std::map<Expression, mpq_class>& map = get_expr_to_coeff_map_in_addition(lhs);
642 DLINEAR_ASSERT(map.size() == 2, "Expression should have exactly two variables");
643 DLINEAR_ASSERT(get_constant_value(get_rhs_expression(formula)) == 0, "The right-hand side must be 0");
644
645 return {
646 get_variable(map.cbegin()->first), // From vertex (variable)
647 get_variable(std::next(map.cbegin())->first), // To vertex (variable)
648 };
649}
650
652 DLINEAR_ASSERT(is_equal_to(formula), "Formula should be an equality relation");
653 DLINEAR_ASSERT(formula.GetFreeVariables().size() == 2, "Formula should have exactly two free variables");
654 DLINEAR_ASSERT(formula.IsFlattened(), "The formula must be flattened");
655 const Expression& lhs = get_lhs_expression(formula);
656
657 const std::map<Expression, mpq_class>& map = get_expr_to_coeff_map_in_addition(lhs);
658 DLINEAR_ASSERT(map.size() == 2, "Expression should have exactly two variables");
659 DLINEAR_ASSERT(get_constant_value(get_rhs_expression(formula)) == 0, "The right-hand side must be 0");
660
661 return -(std::next(map.cbegin())->second) / map.cbegin()->second;
662}
663
664const mpq_class* BoundPreprocessor::StoreTemporaryMpq(const mpq_class& value) {
665 temporary_mpq_vector_.emplace_back(value);
666 return &temporary_mpq_vector_.back();
667}
668Bound BoundPreprocessor::GetSimpleBound(const dlinear::Literal& lit) const {
669 return GetSimpleBound(lit, predicate_abstractor_[lit.var]);
670}
671Bound BoundPreprocessor::GetSimpleBound(const Literal& lit, const Formula& formula) const {
672 DLINEAR_ASSERT(IsSimpleBound(formula), "Formula must be a simple bound");
673 const Expression& lhs{get_lhs_expression(formula)};
674 const Expression& rhs{get_rhs_expression(formula)};
675 if (IsEqualTo(formula, lit.truth)) {
676 if (is_variable(lhs) && is_constant(rhs)) return {&get_constant_value(rhs), LpColBound::B, lit};
677 if (is_constant(lhs) && is_variable(rhs)) return {&get_constant_value(lhs), LpColBound::B, lit};
678 }
679 if (IsGreaterThan(formula, lit.truth)) {
680 if (is_variable(lhs) && is_constant(rhs)) return {&get_constant_value(rhs), LpColBound::SL, lit};
681 if (is_constant(lhs) && is_variable(rhs)) return {&get_constant_value(lhs), LpColBound::SU, lit};
682 }
683 if (IsGreaterThanOrEqualTo(formula, lit.truth)) {
684 if (is_variable(lhs) && is_constant(rhs)) return {&get_constant_value(rhs), LpColBound::L, lit};
685 if (is_constant(lhs) && is_variable(rhs)) return {&get_constant_value(lhs), LpColBound::U, lit};
686 }
687 if (IsLessThan(formula, lit.truth)) {
688 if (is_variable(lhs) && is_constant(rhs)) return {&get_constant_value(rhs), LpColBound::SU, lit};
689 if (is_constant(lhs) && is_variable(rhs)) return {&get_constant_value(lhs), LpColBound::SL, lit};
690 }
691 if (IsLessThanOrEqualTo(formula, lit.truth)) {
692 if (is_variable(lhs) && is_constant(rhs)) return {&get_constant_value(rhs), LpColBound::U, lit};
693 if (is_constant(lhs) && is_variable(rhs)) return {&get_constant_value(lhs), LpColBound::L, lit};
694 }
695 if (IsNotEqualTo(formula, lit.truth)) {
696 if (is_variable(lhs) && is_constant(rhs)) return {&get_constant_value(rhs), LpColBound::D, lit};
697 if (is_constant(lhs) && is_variable(rhs)) return {&get_constant_value(lhs), LpColBound::D, lit};
698 }
699 DLINEAR_RUNTIME_ERROR_FMT("Formula {} not supported", formula);
700}
701
703 // Formula must be a relational formula: `lhs <= rhs`, `lhs >= rhs`, `lhs == rhs` or `lhs != rhs`.
704 if (!is_relational(formula)) return false;
705 // The number of variables must be exactly one
706 if (formula.GetFreeVariables().size() != 1) return false;
707
708 // one between lhs and rhs must be a constant and the other must be a variable.
709 const Expression& lhs{get_lhs_expression(formula)};
710 const Expression& rhs{get_rhs_expression(formula)};
711 return ((is_constant(lhs) && is_variable(rhs)) || (is_variable(lhs) && is_constant(rhs)));
712}
713
714bool BoundPreprocessor::IsEqualTo(const Formula& formula, const bool truth) {
715 return truth ? is_equal_to(formula) : is_not_equal_to(formula);
716}
717
718bool BoundPreprocessor::IsNotEqualTo(const Formula& formula, const bool truth) {
719 return truth ? is_not_equal_to(formula) : is_equal_to(formula);
720}
721
722bool BoundPreprocessor::IsGreaterThan(const Formula& formula, const bool truth) {
723 return truth ? is_greater_than(formula) : is_less_than_or_equal_to(formula);
724}
725
726bool BoundPreprocessor::IsLessThan(const Formula& formula, const bool truth) {
727 return truth ? is_less_than(formula) : is_greater_than_or_equal_to(formula);
728}
729
730bool BoundPreprocessor::IsGreaterThanOrEqualTo(const Formula& formula, const bool truth) {
731 return truth ? is_greater_than_or_equal_to(formula) : is_less_than(formula);
732}
733
734bool BoundPreprocessor::IsLessThanOrEqualTo(const Formula& formula, const bool truth) {
735 return truth ? is_less_than_or_equal_to(formula) : is_greater_than(formula);
736}
737
738void BoundPreprocessor::GetExplanation(const Variable& var, LiteralSet& explanation) {
739 const auto it = theory_bounds_.find(var);
740 if (it == theory_bounds_.end()) return;
741 it->second.GetActiveExplanation(explanation);
742}
743
744std::ostream& operator<<(std::ostream& os, const BoundPreprocessor& preprocessor) {
745 return os << "BoundPreprocessor{" << "env_ = " << preprocessor.env() << ", "
746 << "theory_bounds_ = " << preprocessor.theory_bounds() << "}";
747}
748
749} // namespace dlinear
BoundIterator class.
LiteralSet explanation() const
Produce and explanation formed by all the theory literals present in the violation.
This class uses some basic algebraic operations to preprocess the constraints and identify violations...
static bool IsSimpleBound(const Formula &formula)
Check whether the formula is a simple relational bound.
static bool IsGreaterThan(const Formula &formula, bool truth=true)
Check whether the formula is a simple relational bound with a greater than operator ( ).
std::list< mpq_class > temporary_mpq_vector_
Vector used to store temporary mpq values obtained from more complex constraints.
bool PropagateEqPolynomial(const Literal &lit, const Variable &var_to_propagate, Explanations &explanations)
Propagate the bounds of the variables in the given formula.
const mpq_class * StoreTemporaryMpq(const mpq_class &value)
Vector used to store the mpq_class elements obtained from more complex constraints.
static bool IsLessThan(const Formula &formula, bool truth=true)
Check whether the formula is a simple relational bound with a less than operator ( ).
static bool IsLessThanOrEqualTo(const Formula &formula, bool truth=true)
Check whether the formula is a simple relational bound with a less than or equal to operator ( ).
BoundPreprocessor(const PredicateAbstractor &predicate_abstractor)
Construct a new Bound Preprocessor object using the predicate_abstractor.
static bool IsNotEqualTo(const Formula &formula, bool truth=true)
Check whether the formula is a simple relational bound with a non-equality operator ( ).
std::set< LiteralSet > EnableLiteral(const Literal &lit)
Enable the literal lit.
std::set< LiteralSet > EnableLiterals(const std::vector< Literal > &enabled_literals)
Preprocess all the enabled_literals.
void SetInfinityBounds(const Variable &var, const mpq_class &lb, const mpq_class &ub)
Set the lower and upper infinity bounds (lb, ub) of the variable var.
IterationStats stats_
Statistics of the preprocessing.
mpq_class ExtractEqBoundCoefficient(const Formula &formula) const
Given a formula of the form , with being constants, extract the coefficient .
const Config & config_
Configuration of the preprocessor.
bool PropagateBoundsPolynomial(const Literal &lit, const Variable &var_to_propagate, Explanations &explanations)
Propagate the bounds of the variables in the given formula.
void Clear()
Clear the preprocessor.
void GetActiveExplanation(const Variable &var, LiteralSet &explanation)
Get the active explanation for the variable var.
Explanations Process()
Process all enabled literals.
BoundVectorMap theory_bounds_
Theory bounds for each theory variable.
static bool IsGreaterThanOrEqualTo(const Formula &formula, bool truth=true)
Check whether the formula is a simple relational bound with a less than or equal to operator ( ).
static bool IsEqualTo(const Formula &formula, bool truth=true)
Check whether the formula is a simple relational bound with an equality operator ( ).
const PredicateAbstractor & predicate_abstractor_
Predicate abstractor used to get the constraints from the literals.
Environment env_
Environment storing the values of all the variables with a fixed value.
void AddVariable(const Variable &var)
Add a theory variable to the preprocessor.
Data structure to store the LP solver bounds in a sorted vector to determine the active lower and upp...
Definition BoundVector.h:48
const mpq_class * GetActiveEqualityBound() const
Return the active equality bound.
const mpq_class & active_lower_bound() const
Get read-only access to the active_lower_bound of the BoundVector.
bool RemoveBound(const Bound &bound)
Remove bound from the vector if it is present.
BoundIterator AddBound(const Bound &bound)
Add a new bound to the vector.
const mpq_class & active_upper_bound() const
Get read-only access to the active_upper_bound of the BoundVector.
BoundPropagationType actual_bound_propagation_type() const
Get read-only access to the actual actual_bound_propagation_type parameter of the configuration.
Definition Config.cpp:48
@ EQ_POLYNOMIAL
Only propagate bound in theory formulae in the form .
@ BOUND_POLYNOMIAL
Propagate all possible constraints.
@ NEVER
Never run this preprocess, effectively disabling it.
PreprocessingRunningFrequency actual_bound_propagation_frequency() const
Get read-only access to the actual bound_propagation_frequency parameter of the configuration.
Definition Config.cpp:65
const Config & config() const
Get read-only access to the configuration of the FormulaVisitor.
static const mpq_class & ninfinity(const Config &config)
Get the negative infinity value for the given LP solver in the config.
Definition Infinity.cpp:31
static const mpq_class & infinity(const Config &config)
Get the positive infinity value for the given LP solver in the config.
Definition Infinity.cpp:30
void Increase()
Increase the iteration counter by one.
Definition Stats.cpp:39
Predicate abstraction is a method to convert a first-order logic formula into a Boolean formula.
Timer & m_timer()
Get read-write access to the timer of the stats.
Definition Stats.h:50
bool enabled() const
Check whether the stats is enabled.
Definition Stats.h:48
The TimeGuard wraps a timer object and pauses it when the guard object is destructed.
Definition Timer.h:129
Represents a symbolic environment (mapping from a variable to a value).
const mapped_type & at(const key_type &key) const
Returns the value that is mapped to a key equivalent to key.
iterator find(const key_type &key)
Finds element with specific key.
iterator end()
Returns an iterator to the end.
std::pair< Environment::iterator, bool > insert(const key_type &key, const mapped_type &elem)
Inserts a pair (key, elem).
const_iterator cend() const
Returns a const iterator to the end.
bool contains(const key_type &key) const
Checks whether the environment contains a variable key.
std::size_t erase(const key_type &key)
Erases the element with specific key.
Represents a symbolic form of an expression.
Represents a symbolic form of a first-order logic formula.
const Variables & GetFreeVariables() const
Gets free variables (unquantified variables).
Represents a symbolic variable.
bool is_dummy() const
Checks if this is a dummy variable (ID = 0) which is created by the default constructor.
bool equal_to(const Variable &v) const
Checks the equality of two variables based on their ID values.
size_type size() const
Returns the number of elements.
const_iterator cbegin() const
Returns a const iterator to the beginning.
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
@ SAT_UNSATISFIABLE
The problem is unsatisfiable.
@ U
Upper bound.
@ SL
Strict lower bound.
@ B
Both upper and lower bound are equal (fixed)
@ L
Lower bound.
@ D
Variable must be different from the bound.
@ SU
Strict upper bound.
std::set< Literal > LiteralSet
A set of literals.
Definition literal.h:28
Tuple containing the value, bound type and theory literal associated with the bound.
Definition Bound.h:24
LiteralSet explanation
Explanation for the existence of the bound (e.g. propagation)
Definition Bound.h:37
A literal is a variable with an associated truth value, indicating whether it is true or false.
Definition literal.h:24
bool truth
Truth value.
Definition literal.h:26
Variable var
Variable.
Definition literal.h:25