dlinear  0.0.1
Delta-complete SMT solver for linear programming
Loading...
Searching...
No Matches
QsoptexTheorySolver.cpp
1
6#include "QsoptexTheorySolver.h"
7
8#include <limits>
9#include <map>
10#include <utility>
11
12#include "dlinear/util/Timer.h"
13#include "dlinear/util/exception.h"
14#include "dlinear/util/logging.h"
15
16namespace dlinear {
17
18QsoptexTheorySolver::QsoptexTheorySolver(PredicateAbstractor &predicate_abstractor, const std::string &class_name)
19 : TheorySolver(predicate_abstractor, class_name), qsx_{nullptr}, ray_{0}, x_{0} {
20 qsopt_ex::QSXStart();
21 qsx_ = mpq_QScreate_prob(nullptr, QS_MIN);
22 DLINEAR_ASSERT(qsx_, "Failed to create QSopt_ex problem");
23 if (config_.verbose_simplex() > 3) {
24 DLINEAR_RUNTIME_ERROR("With --lp-solver qsoptex, maximum value for --verbose-simplex is 3");
25 }
26 mpq_QSset_param(qsx_, QS_PARAM_SIMPLEX_DISPLAY, config_.verbose_simplex());
27 DLINEAR_DEBUG_FMT("QsoptexTheorySolver::QsoptexTheorySolver: precision = {}", config_.precision());
28}
29
30QsoptexTheorySolver::~QsoptexTheorySolver() {
31 mpq_QSfree_prob(qsx_);
32 qsopt_ex::QSXFinish();
33}
34
35void QsoptexTheorySolver::AddVariable(const Variable &var) {
36 auto it = var_to_theory_col_.find(var.get_id());
37 // The variable is already present
38 if (it != var_to_theory_col_.end()) return;
39
40 const int qsx_col = mpq_QSget_colcount(qsx_);
41 [[maybe_unused]] int status = mpq_QSnew_col(qsx_, mpq_zeroLpNum, mpq_NINFTY, mpq_INFTY, var.get_name().c_str());
42 DLINEAR_ASSERT(!status, "Invalid status");
43 var_to_theory_col_.emplace(var.get_id(), qsx_col);
44 theory_col_to_var_.emplace_back(var);
45 fixed_preprocessor_.AddVariable(var);
46 DLINEAR_DEBUG_FMT("QsoptexTheorySolver::AddVariable({} ↦ {})", var, qsx_col);
47}
48
49void QsoptexTheorySolver::AddLiterals() {
50 qsx_rhs_.reserve(predicate_abstractor_.var_to_formula_map().size());
51 qsx_sense_.reserve(predicate_abstractor_.var_to_formula_map().size());
52 TheorySolver::AddLiterals();
53}
54
55void QsoptexTheorySolver::Consolidate(const Box &box) {
56 if (is_consolidated_) return;
57
58 // Clear variable bounds
59 for (int theory_col = 0; theory_col < static_cast<int>(theory_col_to_var_.size()); theory_col++) {
60 const Variable &var{theory_col_to_var_[theory_col]};
61 DLINEAR_ASSERT(theory_col < mpq_QSget_colcount(qsx_), "theory_col must be in bounds");
62 if (box.has_variable(var)) {
63 DLINEAR_ASSERT(mpq_NINFTY <= box[var].lb(), "Lower bound too low");
64 DLINEAR_ASSERT(box[var].lb() <= box[var].ub(), "Lower bound must be smaller than upper bound");
65 DLINEAR_ASSERT(box[var].ub() <= mpq_INFTY, "Upper bound too high");
66 fixed_preprocessor_.SetInfinityBounds(var, box[var].lb(), box[var].ub());
67 }
68 }
69 preprocessor_.Clear(fixed_preprocessor_);
70
71 TheorySolver::Consolidate(box);
72}
73
74void QsoptexTheorySolver::UpdateModelSolution() {
75 DLINEAR_ASSERT(x_.size() >= static_cast<std::size_t>(mpq_QSget_rowcount(qsx_)), "x.dim() must be >= colcount");
76 for (int theory_col = 0; theory_col < static_cast<int>(theory_col_to_var_.size()); theory_col++) {
77 const Variable &var{theory_col_to_var_[theory_col]};
78 DLINEAR_ASSERT(
79 model_[var].lb() <= gmp::ToMpqClass(x_[theory_col]) && gmp::ToMpqClass(x_[theory_col]) <= model_[var].ub(),
80 "val must be in bounds");
81 model_[var] = x_[theory_col];
82 }
83}
84void QsoptexTheorySolver::UpdateModelBounds() {
85 DLINEAR_ASSERT(mpq_QSget_rowcount(qsx_) == 0, "There must be no rows in the LP solver");
86 DLINEAR_ASSERT(std::all_of(theory_col_to_var_.cbegin(), theory_col_to_var_.cend(),
87 [this](const Variable &it) {
88 const int theory_col = &it - &theory_col_to_var_[0];
89 [[maybe_unused]] int res;
90 mpq_t temp;
91 mpq_init(temp);
92 res = mpq_QSget_bound(qsx_, theory_col, 'L', &temp);
93 DLINEAR_ASSERT(!res, "Invalid res");
94 mpq_class lb{temp};
95 res = mpq_QSget_bound(qsx_, theory_col, 'U', &temp);
96 DLINEAR_ASSERT(!res, "Invalid res");
97 mpq_class ub{temp};
98 mpq_clear(temp);
99 return lb <= ub;
100 }),
101 "All lower bounds must be <= upper bounds");
102
103 // Update the box with the new bounds, since the theory solver won't be called, for there are no constraints.
104 mpq_t temp;
105 mpq_init(temp);
106 for (int theory_col = 0; theory_col < static_cast<int>(theory_col_to_var_.size()); theory_col++) {
107 const Variable &var{theory_col_to_var_[theory_col]};
108 [[maybe_unused]] int res;
109 res = mpq_QSget_bound(qsx_, theory_col, 'L', &temp);
110 DLINEAR_ASSERT(!res, "Invalid res");
111 mpq_class lb{temp};
112 res = mpq_QSget_bound(qsx_, theory_col, 'U', &temp);
113 DLINEAR_ASSERT(!res, "Invalid res");
114 mpq_class ub{temp};
115 mpq_class val;
116 if (mpq_NINFTY < lb) {
117 val = lb;
118 } else if (ub < mpq_INFTY) {
119 val = ub;
120 } else {
121 val = 0;
122 }
123 DLINEAR_ASSERT(model_[var].lb() <= val && val <= model_[var].ub(), "Val must be in bounds");
124 model_[var] = val;
125 }
126 mpq_clear(temp);
127}
128
129void QsoptexTheorySolver::ClearLinearObjective() {
130 const int qsx_colcount = mpq_QSget_colcount(qsx_);
131 mpq_t c_value;
132 mpq_init(c_value);
133 mpq_set_d(c_value, 0); // Initialized to zero
134 // Set all objective coefficients to zero
135 for (int i = 0; i < qsx_colcount; ++i) mpq_QSchange_objcoef(qsx_, i, c_value);
136 mpq_clear(c_value);
137}
138
139void QsoptexTheorySolver::SetRowCoeff(const Formula &formula, const int qsx_row) {
140 DLINEAR_ASSERT_FMT(formula.IsFlattened(), "The formula {} must be flattened", formula);
141
142 const Expression &lhs = get_lhs_expression(formula);
143 const Expression &rhs = get_rhs_expression(formula);
144 DLINEAR_ASSERT(is_variable(lhs) || is_multiplication(lhs) || is_addition(lhs), "Unsupported expression");
145 DLINEAR_ASSERT(is_constant(rhs), "Unsupported expression");
146
147 qsx_rhs_.emplace_back(get_constant_value(rhs));
148
149 if (is_variable(lhs)) {
150 SetQsxVarCoeff(qsx_row, get_variable(lhs), 1);
151 } else if (is_addition(lhs)) {
152 DLINEAR_ASSERT(get_constant_in_addition(lhs) == 0, "The addition constant must be 0");
153 const std::map<Expression, mpq_class> &map = get_expr_to_coeff_map_in_addition(lhs);
154 for (const auto &[var, coeff] : map) {
155 DLINEAR_ASSERT_FMT(is_variable(var), "Only variables are supported in the addition, got {}", var);
156 SetQsxVarCoeff(qsx_row, get_variable(var), coeff);
157 }
158 } else if (is_multiplication(lhs)) {
159 DLINEAR_ASSERT(get_base_to_exponent_map_in_multiplication(lhs).size() == 1, "Only one variable is supported");
160 DLINEAR_ASSERT(is_variable(get_base_to_exponent_map_in_multiplication(lhs).begin()->first),
161 "Base must be a variable");
162 DLINEAR_ASSERT(is_constant(get_base_to_exponent_map_in_multiplication(lhs).begin()->second) &&
163 get_constant_value(get_base_to_exponent_map_in_multiplication(lhs).begin()->second) == 1,
164 "Only exp == 1 is supported");
165 SetQsxVarCoeff(qsx_row, get_variable(get_base_to_exponent_map_in_multiplication(lhs).begin()->first),
166 get_constant_in_multiplication(lhs));
167 } else {
168 DLINEAR_RUNTIME_ERROR_FMT("Formula {} not supported", formula);
169 }
170 if (qsx_rhs_.back() <= mpq_NINFTY || qsx_rhs_.back() >= mpq_INFTY) {
171 DLINEAR_RUNTIME_ERROR_FMT("LP RHS value too large: {}", qsx_rhs_.back());
172 }
173}
174
175void QsoptexTheorySolver::SetLinearObjective(const Expression &expr) {
176 ClearLinearObjective();
177 if (is_constant(expr)) {
178 if (0 != get_constant_value(expr)) {
179 DLINEAR_RUNTIME_ERROR_FMT("Expression {} not supported in objective", expr);
180 }
181 } else if (is_variable(expr)) {
182 SetQsxVarObjCoeff(get_variable(expr), 1);
183 } else if (is_addition(expr)) {
184 const std::map<Expression, mpq_class> &map = get_expr_to_coeff_map_in_addition(expr);
185 if (0 != get_constant_in_addition(expr)) {
186 DLINEAR_RUNTIME_ERROR_FMT("Expression {} not supported in objective", expr);
187 }
188 for (const auto &[var, coeff] : map) {
189 if (!is_variable(var)) {
190 DLINEAR_RUNTIME_ERROR_FMT("Expression {} not supported in objective", expr);
191 }
192 SetQsxVarObjCoeff(get_variable(var), coeff);
193 }
194 } else {
195 DLINEAR_RUNTIME_ERROR_FMT("Expression {} not supported in objective", expr);
196 }
197}
198
199void QsoptexTheorySolver::EnableQsxVarBound() {
200 for (const auto &[var, bounds] : preprocessor_.theory_bounds()) {
201 const int theory_col = var_to_theory_col_.at(var.get_id());
202 mpq_QSchange_bound(qsx_, theory_col, 'L', bounds.active_lower_bound().get_mpq_t());
203 mpq_QSchange_bound(qsx_, theory_col, 'U', bounds.active_upper_bound().get_mpq_t());
204 DLINEAR_TRACE_FMT("EnableQsxVarBound: {} = [{}, {}]", var, bounds.active_lower_bound(),
205 bounds.active_upper_bound());
206 }
207}
208
209void QsoptexTheorySolver::SetQsxVarCoeff(int qsx_row, const Variable &var, const mpq_class &value) {
210 const auto it = var_to_theory_col_.find(var.get_id());
211 // Variable is not present in the LP solver
212 if (it == var_to_theory_col_.end()) DLINEAR_RUNTIME_ERROR("Variable undefined: {}");
213 // Variable has the coefficients too large
214 if (value <= mpq_NINFTY || value >= mpq_INFTY) DLINEAR_RUNTIME_ERROR_FMT("LP coefficient too large: {}", value);
215
216 mpq_t c_value;
217 mpq_init(c_value);
218 mpq_set(c_value, value.get_mpq_t());
219 mpq_QSchange_coef(qsx_, qsx_row, it->second, c_value);
220 mpq_clear(c_value);
221}
222
223void QsoptexTheorySolver::SetQsxVarObjCoeff(const Variable &var, const mpq_class &value) {
224 const auto it = var_to_theory_col_.find(var.get_id());
225 // Variable is not present in the LP solver
226 if (it == var_to_theory_col_.end()) DLINEAR_RUNTIME_ERROR_FMT("Variable undefined: {}", var);
227
228 if (value <= mpq_NINFTY || value >= mpq_INFTY) DLINEAR_RUNTIME_ERROR_FMT("LP coefficient too large: {}", value);
229
230 mpq_t c_value;
231 mpq_init(c_value);
232 mpq_set(c_value, value.get_mpq_t());
233 mpq_QSchange_objcoef(qsx_, it->second, c_value);
234 mpq_clear(c_value);
235}
236
237extern "C" void QsoptexCheckSatPartialSolution(mpq_QSdata const * /*prob*/, mpq_t *const /*x*/, const mpq_t infeas,
238 const mpq_t /*delta*/, void *data) {
239 DLINEAR_DEBUG_FMT("QsoptexTheorySolver::QsoptexCheckSatPartialSolution called with infeasibility {}",
240 mpq_class(infeas));
241 auto *theory_solver = static_cast<QsoptexTheorySolver *>(data);
242 // mpq_get_d() rounds towards 0. This code guarantees infeas_gt > infeas.
243 double infeas_gt = nextafter(mpq_get_d(infeas), std::numeric_limits<double>::infinity());
244 std::cout << "PARTIAL: delta-sat with delta = " << infeas_gt << " ( > " << mpq_class{infeas} << ")";
245 if (theory_solver->config().with_timings()) {
246 std::cout << " after " << theory_solver->stats().timer().seconds() << " seconds";
247 }
248 std::cout << std::endl;
249}
250
251void QsoptexTheorySolver::UpdateExplanation(LiteralSet &explanation) {
252 // TODO: the ray is not minimal in the slightest. It should be possible to improve it
253 explanation.clear();
254 // For each row in the ray
255 for (int i = 0; i < static_cast<int>(ray_.size()); ++i) {
256 if (mpq_sgn(ray_[i]) == 0) continue; // The row did not participate in the conflict, ignore it
257 if (DLINEAR_TRACE_ENABLED) {
258 mpq_t temp;
259 mpq_init(temp);
260 mpq_QSget_bound(qsx_, i, 'L', &temp);
261 mpq_class l{temp};
262 mpq_QSget_bound(qsx_, i, 'U', &temp);
263 mpq_class u{temp};
264 mpq_clear(temp);
265 DLINEAR_TRACE_FMT("QsoptexTheorySolver::UpdateExplanation: ray[{}] = {} <= {} <= {}", i, l, mpq_class{ray_[i]},
266 u);
267 }
268 const Literal &lit = theory_row_to_lit_[i];
269 // Insert the conflicting row literal to the explanation. Use the latest truth value from the SAT solver
270 explanation.insert(lit);
271 }
272 mpq_class col_violation{0};
273 for (int i = 0; i < static_cast<int>(theory_col_to_var_.size()); i++) {
274 col_violation = 0;
275 for (int j = 0; j < mpq_QSget_rowcount(qsx_); j++) {
276 if (mpq_sgn(ray_[j]) == 0) continue;
277 mpq_t temp;
278 mpq_init(temp);
279 mpq_QSget_coef(qsx_, j, i, &temp);
280 mpq_mul(temp, temp, ray_[j]);
281 mpq_add(col_violation.get_mpq_t(), col_violation.get_mpq_t(), temp);
282 mpq_clear(temp);
283 }
284 if (col_violation == 0) continue;
285 DLINEAR_TRACE_FMT("CompleteSoplexTheorySolver::UpdateExplanationInfeasible: {}[{}] = {}", theory_col_to_var_[i], i,
286 col_violation);
287 const BoundVector &bounds = preprocessor_.theory_bounds().at(theory_col_to_var_[i]);
288 bounds.GetActiveBounds(col_violation < 0 ? bounds.active_lower_bound() : bounds.active_upper_bound())
289 .explanation(explanation);
290 }
291}
292void QsoptexTheorySolver::DisableQsxRows() {
293 const int rowcount = mpq_QSget_rowcount(qsx_);
294 for (int theory_row = 0; theory_row < rowcount; theory_row++) {
295 if (!theory_rows_state_.at(theory_row)) {
296 mpq_QSchange_sense(qsx_, theory_row, 'G');
297 mpq_QSchange_rhscoef(qsx_, theory_row, mpq_NINFTY);
298 }
299 }
300}
301void QsoptexTheorySolver::EnableQsxRow(const int qsx_row) {
302 const auto &[var, truth] = theory_row_to_lit_[qsx_row];
303 EnableQsxRow(qsx_row, truth);
304}
305
306} // namespace dlinear
Data structure to store the LP solver bounds in a sorted vector to determine the active lower and upp...
Definition BoundVector.h:48
BoundIterator GetActiveBounds() const
Return a BoundIterator containing a set of bounds enclosing the interval [active_lower_bound_,...
const mpq_class & active_lower_bound() const
Get read-only access to the active_lower_bound of the BoundVector.
const mpq_class & active_upper_bound() const
Get read-only access to the active_upper_bound of the BoundVector.
Collection of variables with associated intervals.
Definition Box.h:31
bool has_variable(const Variable &var) const
Checks if this box contains var.
Definition Box.cpp:117
QSopt_ex is an exact LP solver written in C.
Represents a symbolic form of an expression.
Represents a symbolic form of a first-order logic formula.
Represents a symbolic variable.
Global namespace for the dlinear library.
std::set< Formula > map(const std::set< Formula > &formulas, const std::function< Formula(const Formula &)> &func)
Given formulas = {f₁, ..., fₙ} and a func : Formula → Formula, map(formulas, func) returns a set {fun...
Definition symbolic.cpp:47
std::set< Literal > LiteralSet
A set of literals.
Definition literal.h:28
A literal is a variable with an associated truth value, indicating whether it is true or false.
Definition literal.h:24