6#include "QsoptexTheorySolver.h"
12#include "dlinear/util/Timer.h"
13#include "dlinear/util/exception.h"
14#include "dlinear/util/logging.h"
18QsoptexTheorySolver::QsoptexTheorySolver(PredicateAbstractor &predicate_abstractor,
const std::string &class_name)
19 : TheorySolver(predicate_abstractor, class_name), qsx_{nullptr}, ray_{0}, x_{0} {
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");
26 mpq_QSset_param(qsx_, QS_PARAM_SIMPLEX_DISPLAY, config_.verbose_simplex());
27 DLINEAR_DEBUG_FMT(
"QsoptexTheorySolver::QsoptexTheorySolver: precision = {}", config_.precision());
30QsoptexTheorySolver::~QsoptexTheorySolver() {
31 mpq_QSfree_prob(qsx_);
32 qsopt_ex::QSXFinish();
35void QsoptexTheorySolver::AddVariable(
const Variable &var) {
36 auto it = var_to_theory_col_.find(var.get_id());
38 if (it != var_to_theory_col_.end())
return;
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);
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();
55void QsoptexTheorySolver::Consolidate(
const Box &box) {
56 if (is_consolidated_)
return;
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");
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());
69 preprocessor_.Clear(fixed_preprocessor_);
71 TheorySolver::Consolidate(box);
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]};
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];
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(),
88 const int theory_col = &it - &theory_col_to_var_[0];
89 [[maybe_unused]] int res;
92 res = mpq_QSget_bound(qsx_, theory_col,
'L', &temp);
93 DLINEAR_ASSERT(!res,
"Invalid res");
95 res = mpq_QSget_bound(qsx_, theory_col,
'U', &temp);
96 DLINEAR_ASSERT(!res,
"Invalid res");
101 "All lower bounds must be <= upper bounds");
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");
112 res = mpq_QSget_bound(qsx_, theory_col,
'U', &temp);
113 DLINEAR_ASSERT(!res,
"Invalid res");
116 if (mpq_NINFTY < lb) {
118 }
else if (ub < mpq_INFTY) {
123 DLINEAR_ASSERT(model_[var].lb() <= val && val <= model_[var].ub(),
"Val must be in bounds");
129void QsoptexTheorySolver::ClearLinearObjective() {
130 const int qsx_colcount = mpq_QSget_colcount(qsx_);
133 mpq_set_d(c_value, 0);
135 for (
int i = 0; i < qsx_colcount; ++i) mpq_QSchange_objcoef(qsx_, i, c_value);
139void QsoptexTheorySolver::SetRowCoeff(
const Formula &formula,
const int qsx_row) {
140 DLINEAR_ASSERT_FMT(formula.IsFlattened(),
"The formula {} must be flattened", formula);
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");
147 qsx_rhs_.emplace_back(get_constant_value(rhs));
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);
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));
168 DLINEAR_RUNTIME_ERROR_FMT(
"Formula {} not supported", formula);
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());
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);
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);
188 for (
const auto &[var, coeff] :
map) {
189 if (!is_variable(var)) {
190 DLINEAR_RUNTIME_ERROR_FMT(
"Expression {} not supported in objective", expr);
192 SetQsxVarObjCoeff(get_variable(var), coeff);
195 DLINEAR_RUNTIME_ERROR_FMT(
"Expression {} not supported in objective", expr);
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());
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());
212 if (it == var_to_theory_col_.end()) DLINEAR_RUNTIME_ERROR(
"Variable undefined: {}");
214 if (value <= mpq_NINFTY || value >= mpq_INFTY) DLINEAR_RUNTIME_ERROR_FMT(
"LP coefficient too large: {}", value);
218 mpq_set(c_value, value.get_mpq_t());
219 mpq_QSchange_coef(qsx_, qsx_row, it->second, c_value);
223void QsoptexTheorySolver::SetQsxVarObjCoeff(
const Variable &var,
const mpq_class &value) {
224 const auto it = var_to_theory_col_.find(var.get_id());
226 if (it == var_to_theory_col_.end()) DLINEAR_RUNTIME_ERROR_FMT(
"Variable undefined: {}", var);
228 if (value <= mpq_NINFTY || value >= mpq_INFTY) DLINEAR_RUNTIME_ERROR_FMT(
"LP coefficient too large: {}", value);
232 mpq_set(c_value, value.get_mpq_t());
233 mpq_QSchange_objcoef(qsx_, it->second, c_value);
237extern "C" void QsoptexCheckSatPartialSolution(mpq_QSdata
const * , mpq_t *
const ,
const mpq_t infeas,
238 const mpq_t ,
void *data) {
239 DLINEAR_DEBUG_FMT(
"QsoptexTheorySolver::QsoptexCheckSatPartialSolution called with infeasibility {}",
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";
248 std::cout << std::endl;
251void QsoptexTheorySolver::UpdateExplanation(
LiteralSet &explanation) {
255 for (
int i = 0; i < static_cast<int>(ray_.size()); ++i) {
256 if (mpq_sgn(ray_[i]) == 0)
continue;
257 if (DLINEAR_TRACE_ENABLED) {
260 mpq_QSget_bound(qsx_, i,
'L', &temp);
262 mpq_QSget_bound(qsx_, i,
'U', &temp);
265 DLINEAR_TRACE_FMT(
"QsoptexTheorySolver::UpdateExplanation: ray[{}] = {} <= {} <= {}", i, l, mpq_class{ray_[i]},
268 const Literal &lit = theory_row_to_lit_[i];
270 explanation.insert(lit);
272 mpq_class col_violation{0};
273 for (
int i = 0; i < static_cast<int>(theory_col_to_var_.size()); i++) {
275 for (
int j = 0; j < mpq_QSget_rowcount(qsx_); j++) {
276 if (mpq_sgn(ray_[j]) == 0)
continue;
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);
284 if (col_violation == 0)
continue;
285 DLINEAR_TRACE_FMT(
"CompleteSoplexTheorySolver::UpdateExplanationInfeasible: {}[{}] = {}", theory_col_to_var_[i], i,
287 const BoundVector &bounds = preprocessor_.theory_bounds().at(theory_col_to_var_[i]);
289 .explanation(explanation);
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);
301void QsoptexTheorySolver::EnableQsxRow(
const int qsx_row) {
302 const auto &[var, truth] = theory_row_to_lit_[qsx_row];
303 EnableQsxRow(qsx_row, truth);
Data structure to store the LP solver bounds in a sorted vector to determine the active lower and upp...
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.
bool has_variable(const Variable &var) const
Checks if this box contains var.
QSopt_ex is an exact LP solver written in C.
Represents a symbolic form of an expression.
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...
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.