6#include "delpi/solver/QsoptexLpSolver.h"
13#include <unordered_map>
14#include <unordered_set>
18#include "delpi/util/error.h"
19#include "delpi/util/logging.h"
25extern "C" void QsoptexPartialSolutionCb(mpq_QSdata
const* ,
const mpq_t* x,
const mpq_t*
const y,
26 const mpq_t obj_lb,
const mpq_t obj_up,
const mpq_t ,
27 const mpq_t ,
const unsigned int precision,
void* data) {
28 DELPI_DEBUG_FMT(
"QsoptexLpSolver::QsoptexPartialSolutionCb called with objective value in [{}, {}]",
29 mpq_class{obj_lb}, mpq_class{obj_up});
32 if (lp_solver.partial_solve_cb())
34 gmp::ToMpqVector(y, lp_solver.num_rows()), mpq_class{obj_lb}, mpq_class{obj_up});
37QsoptexLpSolver::QsoptexLpSolver(
Config config,
const std::string& class_name)
38 :
LpSolver{0, 0, std::move(config), class_name},
40 basis_{.nstruct = 0, .nrows = 0, .cstat = nullptr, .rstat = nullptr},
44 ninfinity_ = mpq_class{mpq_NINFTY};
45 infinity_ = mpq_class{mpq_INFTY};
47 qsx_ = mpq_QScreate_prob(
nullptr, QS_MIN);
48 DELPI_ASSERT(qsx_ !=
nullptr,
"Failed to create QSopt_ex problem");
49 if (config_.verbose_simplex() > 3) {
50 DELPI_RUNTIME_ERROR(
"With --lp-solver qsoptex, maximum value for --verbose-simplex is 3");
52 [[maybe_unused]]
const int status = mpq_QSset_param(qsx_, QS_PARAM_SIMPLEX_DISPLAY, config_.verbose_simplex());
53 DELPI_ASSERT(!status,
"Invalid status");
54 DELPI_DEBUG_FMT(
"QsoptexTheorySolver::QsoptexTheorySolver: delta = {}", config_.delta());
57QsoptexLpSolver::~QsoptexLpSolver() {
58 mpq_QSfree_prob(qsx_);
59 qsopt_ex::QSXFinish();
62int QsoptexLpSolver::num_columns()
const {
return mpq_QSget_colcount(qsx_); }
63int QsoptexLpSolver::num_rows()
const {
return mpq_QSget_rowcount(qsx_); }
66 DELPI_ASSERT(column_idx < num_columns(),
"Column index out of bounds");
69 [[maybe_unused]]
const int status =
70 mpq_QSget_columns_list(
qsx_, 1, &column_idx,
nullptr,
nullptr,
nullptr,
nullptr, obj, lb, ub,
nullptr);
71 DELPI_ASSERT(!status,
"Invalid status");
76 if (!mpq_equal(lb[0], mpq_NINFTY))
column.lb = std::move(gmp::ToMpqClass(lb[0]));
77 if (!mpq_equal(ub[0], mpq_INFTY))
column.ub = std::move(gmp::ToMpqClass(ub[0]));
78 if (gmp::ToMpqClass(obj[0]) != 0)
column.obj = std::move(gmp::ToMpqClass(obj[0]));
83 DELPI_ASSERT(row_idx < num_rows(),
"Row index out of bounds");
85 int *row_cnt =
nullptr, *row_ind =
nullptr;
86 char* sense =
nullptr;
88 [[maybe_unused]]
const int status =
89 mpq_QSget_rows_list(
qsx_, 1, &row_idx, &row_cnt,
nullptr, &row_ind, row_val, rhs, &sense,
nullptr);
90 DELPI_ASSERT(!status,
"Invalid status");
93 const int non_zero_coefficient_count = row_cnt[0];
94 for (
int i = 0; i < non_zero_coefficient_count; i++) {
95 row.addends.emplace_back(
col_to_var_.at(row_ind[i]), std::move(gmp::ToMpqClass(row_val[i])));
96 DELPI_DEBUG_FMT(
"QsoptexTheorySolver::row: row[{}]({}) = {} * {}", row_idx, i,
row.addends.back().second,
97 row.addends.back().first);
102 row.lb = std::move(gmp::ToMpqClass(rhs[0]));
105 row.ub = std::move(gmp::ToMpqClass(rhs[0]));
108 row.lb = gmp::ToMpqClass(rhs[0]);
109 row.ub = std::move(gmp::ToMpqClass(rhs[0]));
123 const mpq_class& ub) {
124 DELPI_ASSERT(!
var_to_col_.contains(
var),
"Variable already exists in the LP.");
125 const int column_idx = num_columns();
128 [[maybe_unused]]
const int status = mpq_QSnew_col(
qsx_, obj.get_mpq_t(), lb.get_mpq_t(), ub.get_mpq_t(),
nullptr);
129 DELPI_ASSERT(!status,
"Invalid status");
132LpSolver::RowIndex QsoptexLpSolver::AddRow(
const std::vector<Expression::Addend>& addends,
const mpq_class& lb,
133 const mpq_class& ub) {
135 if (lb == ub)
return AddRow(addends,
'E', lb);
138 if (!mpq_equal(lb.get_mpq_t(), mpq_NINFTY)) AddRow(addends,
'G', lb);
139 if (!mpq_equal(ub.get_mpq_t(), mpq_INFTY)) AddRow(addends,
'L', ub);
140 return num_rows() - 1;
143LpSolver::RowIndex QsoptexLpSolver::AddRow(
const Expression::Addends& lhs,
const FormulaKind sense,
144 const mpq_class& rhs) {
160 return AddRow(lhs, qsoptex_sense, rhs);
164 [[maybe_unused]]
const int status = mpq_QSchange_bound(
qsx_,
var_to_col_.at(
var),
'B', lb.get_mpq_t());
165 DELPI_ASSERT(!status,
"Invalid status");
168 [[maybe_unused]]
const int status1 = mpq_QSchange_bound(
qsx_,
var_to_col_.at(
var),
'L', lb.get_mpq_t());
169 DELPI_ASSERT(!status1,
"Invalid status");
170 [[maybe_unused]]
const int status2 = mpq_QSchange_bound(
qsx_,
var_to_col_.at(
var),
'U', ub.get_mpq_t());
171 DELPI_ASSERT(!status2,
"Invalid status");
175 DELPI_ASSERT_FMT(
column < num_columns(),
"Column index out of bounds: {} >= {}",
column, num_columns());
176 [[maybe_unused]]
const int status = mpq_QSchange_objcoef(
qsx_,
column, mpq_class{value}.get_mpq_t());
177 DELPI_ASSERT(!status,
"Invalid status");
183 x_.Resize(num_columns());
184 ray_.Resize(num_rows());
186 unsigned int precision;
189 obj_ub_.get_mpq_t(), &
basis_, PRIMAL_SIMPLEX, &lp_status, &precision,
190 config_.continuous_output() ? QsoptexPartialSolutionCb :
nullptr,
this);
193 DELPI_RUNTIME_ERROR_FMT(
"QSopt_ex returned {}", status);
197 DELPI_DEBUG_FMT(
"DeltaQsoptexTheorySolver::CheckSat: QSopt_ex has returned with precision = {}", precision);
202 case QS_LP_DELTA_OPTIMAL:
205 case QS_LP_UNBOUNDED:
208 case QS_LP_INFEASIBLE:
210 if (store_solution) UpdateInfeasible();
214 DELPI_ERROR(
"DeltaQsoptexTheorySolver::CheckSat: QSopt_ex failed to return a result");
216 case QS_LP_ITER_LIMIT:
217 DELPI_ERROR(
"DeltaQsoptexTheorySolver::CheckSat: Iteration limit reached");
225 DELPI_ASSERT(
solution_.empty(),
"Solution must be empty");
226 DELPI_ASSERT(
dual_solution_.empty(),
"Dual solution must be empty");
228 const int colcount = num_columns();
229 const int rowcount = num_rows();
233 for (
int i = 0; i < colcount; i++)
solution_.emplace_back(
x_[i]);
239 stats_.precision = precision;
243void QsoptexLpSolver::UpdateInfeasible() {
244 DELPI_ASSERT(infeasible_rows_.empty(),
"Infeasible rows must be empty");
245 DELPI_ASSERT(infeasible_bounds_.empty(),
"Infeasible bounds must be empty");
247 const int rowcount = num_rows();
248 const int colcount = num_columns();
251 for (
int i = 0; i < rowcount; i++) {
252 if (mpq_sgn(ray_[i]) == 0)
continue;
253 DELPI_TRACE_FMT(
"QsoptexLpSolver::NotifyInfeasible: ray[{}] = {}", i, gmp::ToMpqClass(ray_[i]));
254 infeasible_rows_.emplace_back(i);
258 mpq_class col_violation{0};
261 for (
int i = 0; i < colcount; i++) {
263 for (
int j = 0; j < rowcount; j++) {
264 mpq_QSget_coef(qsx_, j, i, &row_coeff);
265 col_violation += gmp::ToMpqClass(ray_[j]) * gmp::ToMpqClass(row_coeff);
267 if (col_violation == 0)
continue;
268 DELPI_TRACE_FMT(
"QsoptexLpSolver::NotifyInfeasible: {}[{}] = {}", col_to_var_.at(i), i, col_violation);
269 infeasible_bounds_.emplace_back(i, col_violation > 0);
271 mpq_clear(row_coeff);
275template <TypedIterable<std::pair<const Variable, mpq_
class>> T>
280template <TypedIterable<std::pair<const Variable, mpq_
class>> T>
281int QsoptexLpSolver::AddRow(
const T& lhs,
const char sense,
const mpq_class& rhs) {
282 DELPI_TRACE_FMT(
"QsoptexLpSolver::AddRow(#{}, {}, {})", lhs.size(), sense, rhs);
283 DELPI_ASSERT(sense ==
'L' || sense ==
'G' || sense ==
'E',
"Invalid sense");
284 std::vector<int> row_indices;
285 row_indices.reserve(lhs.size());
287 for (
auto& [var, coeff] : lhs) {
288 const int column_idx = var_to_col_.at(var);
289 mpq_set(values[row_indices.size()], coeff.get_mpq_t());
290 row_indices.emplace_back(column_idx);
294 mpq_set(c_rhs, rhs.get_mpq_t());
296 [[maybe_unused]]
const int status =
297 mpq_QSadd_row(qsx_,
static_cast<int>(lhs.size()), row_indices.data(), values, &c_rhs, sense,
nullptr);
298 DELPI_ASSERT(!status,
"Invalid status");
301 return num_rows() - 1;
305 DELPI_ASSERT_FMT(
var_to_col_.contains(
var),
"Variable {} not found in the LP. Did you add it before?",
var);
308 if (value <= ninfinity_ || value >=
infinity_) DELPI_RUNTIME_ERROR_FMT(
"LP coefficient too large: {}", value);
310 [[maybe_unused]]
const int status = mpq_QSchange_coef(
qsx_,
row,
column, mpq_class{value}.get_mpq_t());
311 DELPI_ASSERT(!status,
"Invalid status");
315void QsoptexLpSolver::Dump() {
316 mpq_QSdump_prob(qsx_);
317 mpq_QSdump_basis(qsx_);
318 mpq_QSdump_bfeas(qsx_);
323 DELPI_ASSERT_FMT(
row < num_rows(),
"Row index out of bounds: {} >= {}",
row, num_rows());
324 DELPI_ASSERT_FMT(
column < num_columns(),
"Column index out of bounds: {} >= {}",
column, num_columns());
325 DELPI_ASSERT_FMT(value <= infinity_ && value >=
ninfinity_,
"LP coefficient too large: {}", value);
327 [[maybe_unused]]
const int status = mpq_QSchange_coef(
qsx_,
row,
column, mpq_class{value}.get_mpq_t());
328 DELPI_ASSERT(!status,
"Invalid status");
338template int QsoptexLpSolver::AddRow(
const std::vector<std::pair<const Variable, mpq_class>>&,
char,
const mpq_class&);
339template int QsoptexLpSolver::AddRow(
const std::set<std::pair<const Variable, mpq_class>>&,
char,
const mpq_class&);
340template int QsoptexLpSolver::AddRow(
const std::unordered_set<std::pair<const Variable, mpq_class>>&,
char,
342template int QsoptexLpSolver::AddRow(
const std::span<std::pair<const Variable, mpq_class>>&,
char,
const mpq_class&);
343template int QsoptexLpSolver::AddRow(
const std::map<Variable, mpq_class>&,
char,
const mpq_class&);
344template int QsoptexLpSolver::AddRow(
const std::unordered_map<Variable, mpq_class>&,
char,
const mpq_class&);
Simple dataclass used to store the configuration of the program.
Facade class that hides the underlying LP solver used by delpi.
const Variable & var(const int column) const
Shorthand notation to get the real variable linked with column column.
mpq_class infinity_
Infinity threshold value.
bool is_min_
Whether this is a minimization or maximization LP problem.
mpq_class ninfinity_
Negative infinity threshold value.
Config config_
Configuration to use.
std::vector< mpq_class > solution_
Solution vector.
std::unordered_map< Variable, int > var_to_col_
Theory column ⇔ Variable.
mpq_class obj_ub_
Upper bound on the objective value, if any.
std::vector< mpq_class > dual_solution_
Dual solution vector.
mpq_class obj_lb_
Lower bound on the objective value, if any.
std::vector< Variable > col_to_var_
Literal ⇔ lp row.
LpStats stats_
Statistics of the solver.
Linear programming solver using QSopt_ex.
void SetObjective(int column, const mpq_class &value) override
The the objective coefficient of the given column to the given value.
void SetRowCoeff(int row, const T &literal_monomials)
Parse a sequence of literal_monomials and set the coefficient for each decisional variable appearing ...
void UpdateFeasible()
Use the result from the lp solver to update the solution vector and objective value.
qsopt_ex::MpqArray ray_
Ray of the last infeasible solution.
void UpdateStats(unsigned int precision)
Update the lp stats from the QSopt_ex solver.
void SetVarCoeff(int row, const Variable &var, const mpq_class &value) const
Set the coefficients to apply to var on a specific row.
void EnsureSenseCore() override
Make sure the LP solvers are aware of the sense of the LP problem (minimisation or maximisation).
ColumnIndex AddColumn(const Variable &var, const mpq_class &obj, const mpq_class &lb, const mpq_class &ub) override
Add a new bounded column to the LP problem, ensuring that the variable var is in the range and has t...
void SetBound(Variable var, const mpq_class &lb, const mpq_class &ub) override
Set the bounds of a var in the LP problem to the given lb and ub.
void SetCoefficient(RowIndex row, ColumnIndex column, const mpq_class &value) override
Set the coefficient of the row constraint to apply at the column decisional variable.
QSbasis basis_
Last basis.
qsopt_ex::MpqArray x_
Solution vector.
mpq_QSprob qsx_
QSopt_ex LP solver.
Column column(int column_idx) const override
Get the column at the given column_idx index.
LpResult SolveCore() override
Internal method that optimises the LP problem with the given delta.
Row row(int row_idx) const override
Get the row at the given row_idx index.
A wrapper around an array of mpq_t elements.
Global namespace for the delpi library.
LpResult
Possible outcomes of the LP solver.
@ INFEASIBLE
The problem is infeasible.
@ DELTA_OPTIMAL
The delta-relaxation of the problem is optimal.
@ UNBOUNDED
The problem is unbounded.
@ ERROR
An error occurred.
@ OPTIMAL
The problem is optimal.
FormulaKind
Kinds of symbolic formulas.
Convenient structure representing a column in the LP solver.
Structure representing a row in the LP solver in the form of a linear combination of variables.