delpi  0.0.1
DElta-complete LP solver
Loading...
Searching...
No Matches
DelpiLpSolver.cpp
1
6#include "delpi/solver/DelpiLpSolver.h"
7
8#include <iostream>
9#include <ostream>
10#include <string>
11#include <unordered_map>
12#include <unordered_set>
13#include <utility>
14#include <vector>
15
16#include "delpi/util/error.h"
17#include "internal/Basis.h"
18#include "internal/BgLinearSystemSolver.h"
19
20namespace delpi {
21
23 int precision;
24 double tolerance;
25};
26
27namespace {
28std::array precisions{SolveConfiguration{64, 1e-6}, SolveConfiguration{0, 0}};
29}
30
31DelpiLpSolver::DelpiLpSolver(Config config, const std::string& class_name)
32 : LpSolver{mpq_class{mpz_class{0}, 0}, mpq_class{mpz_class{0}, 0}, std::move(config), class_name} {}
33
34int DelpiLpSolver::num_columns() const { return static_cast<int>(problem_.num_columns()); }
35int DelpiLpSolver::num_rows() const { return static_cast<int>(problem_.num_rows()); }
36
37Column DelpiLpSolver::column(const ColumnIndex column_idx) const {
38 DELPI_ASSERT(column_idx < num_columns(), "Column index out of bounds");
39 const auto [lb, ub, obj] = problem_.column(column_idx);
40 return {col_to_var_.at(column_idx), lb, ub, obj};
41}
42Row DelpiLpSolver::row(const RowIndex row_idx) const {
43 DELPI_ASSERT(row_idx < num_rows(), "Row index out of bounds");
44 const Index columns = num_columns();
45 const auto [lp_addends, lb, ub]{problem_.row(row_idx)};
46 std::vector<std::pair<Variable, mpq_class>> addends;
47 addends.reserve(columns);
48 for (const auto& [col, coeff] : lp_addends) addends.emplace_back(col_to_var_.at(col), coeff);
49 return {addends, lb.has_value() ? lb.value() : ninfinity_, ub.has_value() ? ub.value() : infinity_};
50}
51
52void DelpiLpSolver::ReserveColumns(const int num_columns) {
53 LpSolver::ReserveColumns(num_columns);
54 problem_.Reserve(-1, num_columns);
55}
56void DelpiLpSolver::ReserveRows(const int num_rows) {
57 LpSolver::ReserveRows(num_rows);
58 problem_.Reserve(num_rows, -1);
59}
60LpSolver::ColumnIndex DelpiLpSolver::AddColumn(const Variable& var, const mpq_class& obj, const mpq_class& lb,
61 const mpq_class& ub) {
62 DELPI_ASSERT_FMT(!var_to_col_.contains(var), "Variable '{}' already exists in the LP.", var);
63 const ColumnIndex column_idx = num_columns();
64 var_to_col_.emplace(var, column_idx);
65 col_to_var_.emplace_back(var);
66 problem_.AddColumn(obj, lb, ub);
67 return column_idx;
68}
69LpSolver::RowIndex DelpiLpSolver::AddRow(const std::vector<Expression::Addend>& addends, const mpq_class& lb,
70 const mpq_class& ub) {
71 if (gmp::IsInfinity(lb) && gmp::IsInfinity(ub)) {
72 DELPI_WARN_FMT("Ignoring unbounded row with addends: {}", addends);
73 return -1;
74 }
75 std::unordered_map<Index, mpq_class> row_lhs;
76 row_lhs.reserve(addends.size());
77 for (const auto& [var, coeff] : addends) row_lhs.emplace(var_to_col_.at(var), coeff);
78 problem_.AddRow(row_lhs, lb, ub); // TODO(tend): Implement AddRow
79 return num_rows() - 1;
80}
81LpSolver::RowIndex DelpiLpSolver::AddRow(const Expression::Addends& lhs, const FormulaKind sense,
82 const mpq_class& rhs) {
83 DELPI_ASSERT(sense == FormulaKind::Eq || sense == FormulaKind::Leq || sense == FormulaKind::Geq,
84 "Only equality, less than or equal, and greater than or equal constraints are supported");
85 std::unordered_map<Index, mpq_class> row_lhs;
86 row_lhs.reserve(lhs.size());
87 for (const auto& [var, coeff] : lhs) row_lhs.emplace(var_to_col_.at(var), coeff);
88 problem_.AddRow(row_lhs, sense == FormulaKind::Leq ? ninfinity_ : rhs, sense == FormulaKind::Geq ? infinity_ : rhs);
89 return num_rows() - 1;
90}
91void DelpiLpSolver::SetBound([[maybe_unused]] Variable var, [[maybe_unused]] const mpq_class& lb,
92 [[maybe_unused]] const mpq_class& ub) {
93 DELPI_TRACE_FMT("DelpiLpSolver::SetBound({}, {}, {})", var, lb, ub);
94 DELPI_ASSERT(var_to_col_.contains(var), "Variable not found in the LP");
95 // TODO(tend): Consider upper and lower bounds
96 // problem_.SetColumnBound(0, lb, ub);
97}
98void DelpiLpSolver::SetCoefficient([[maybe_unused]] const RowIndex row, [[maybe_unused]] const ColumnIndex column,
99 [[maybe_unused]] const mpq_class& value) {
100 DELPI_TRACE_FMT("DelpiLpSolver::SetCoefficient({}, {}, {})", row, column, value);
101 DELPI_ASSERT(row < num_rows(), "Row index out of bounds");
102 DELPI_ASSERT(column < num_columns(), "Column index out of bounds");
103 // problem_.SetCoefficient(row, column, value);
104}
105void DelpiLpSolver::SetObjective(const int column, const mpq_class& value) {
106 DELPI_TRACE_FMT("DelpiLpSolver::SetObjective({}, {})", column, value);
107 DELPI_ASSERT(column < num_columns(), "Column index out of bounds");
108 problem_.SetObjective(column, value);
109}
110
111#ifndef NDEBUG
112void DelpiLpSolver::Dump() {
113 std::cout << "DelpiLpSolver{ num_columns: " << num_columns() << ", num_rows: " << num_rows() << "\n"
114 << problem_ << "}\n";
115}
116#endif
118 DELPI_DEBUG("DelpiLpSolver::SolveCore()");
119 Matrix<mpq_class> slack_A;
120 Vector<mpq_class> slack_c;
121 Vector<mpq_class> slack_b;
122 problem_.SlackForm(slack_A, slack_b, slack_c);
123 internal::Basis<mpq_class> slack_basis(slack_A);
124 DELPI_DEV("About to check feasibility");
125
126 const LpResult feasibility_check = FeasibilitySolve(slack_A, slack_b, slack_basis);
127 DELPI_DEV_FMT("Feasibility check: {}", feasibility_check);
128 if (feasibility_check == LpResult::INFEASIBLE) return feasibility_check;
129 DELPI_ASSERT(feasibility_check == LpResult::OPTIMAL, "Feasibility check must be optimal");
130 DELPI_ASSERT(slack_basis.basis_vectors().determinant() != 0, "Basis matrix must be non-singular");
131
132 const LpResult optimality_check = OptimalitySolve(slack_A, slack_b, slack_c, slack_basis);
133
134 DELPI_DEV_FMT("Result: {}. x: {}, c: {}", optimality_check, x_, problem_.c());
135 solution_ = std::vector<mpq_class>{x_.data(), x_.data() + x_.size()};
136 return optimality_check;
137}
138
139template <IsAnyOf<double, mpq_class> T>
140LpResult DelpiLpSolver::InternalSolve(const Matrix<T>& A, const Vector<T>& b, const Vector<T>& c, const T& tolerance,
141 internal::Basis<T>& basis, Vector<T>* const x, T* const obj) {
142 if constexpr (std::is_same_v<T, mpq_class>) {
143 DELPI_ASSERT(tolerance == 0, "Tolerance must be 0 for exact arithmetic");
144 }
145
146 DELPI_ASSERT(A.rows() == b.size(), "Inconsistent number of rows in A and b");
147 DELPI_ASSERT(A.cols() == c.size(), "Inconsistent number of columns in A and c");
148 DELPI_ASSERT(basis.size() == A.rows(), "Inconsistent number of rows in A and basis");
149 DELPI_ASSERT(&A == &basis.A(), "Basis must be built from matrix A");
150 DELPI_ASSERT(basis.basis_vectors().determinant() != 0, "Basis matrix must be non-singular");
151
152 // TODO(tend): Use a more sophisticated maximum number of iterations
153 constexpr int max_iterations = 1000;
154 for (int i = 0; i < max_iterations; ++i) {
155 internal::BgLinearSystemSolver<T> solver{config_};
156 solver.Factorise(basis);
157 Vector<T> zb{solver.Solve(b)};
158 DELPI_ASSERT((zb.array() >= -tolerance).all(), "All values must be non-negative (feasible)");
159 Vector<T> y = solver.TransposeSolve(c(basis.basis_idxs()));
160 // fmt::println("cb: {}\ny: {}", c(basis.basis_idxs()), y);
161 // Compute the reduced costs to determine the entering variable or optimality
162 Vector<T> r{c - A.transpose() * y};
163 // fmt::println("c: {}\nrd: {}", c, A.transpose() * y);
164 int r_idx = 0;
165 for (; r_idx < r.size(); ++r_idx) {
166 if (r(r_idx) < -tolerance) break;
167 }
168 if (r_idx == r.size()) {
169 if (nullptr != x || nullptr != obj) {
170 Vector<T> _x = Eigen::VectorX<T>::Zero(A.cols());
171 _x(basis.basis_idxs()) = solver.Solve(b);
172 if (x != nullptr) *x = _x;
173 if (obj != nullptr) *obj = c.transpose() * _x;
174 }
175 // fmt::println("b: {}, index: {}, x_: {}", b, basis.basis_idxs(), x_);
176 return LpResult::OPTIMAL;
177 }
178 // fmt::println("Reduced costs: r: {}\nr [{}] = {}", r, r_idx, r(r_idx));
179
180 // Compute the entering variable or unboundedness
181 Vector<T> d{solver.Solve(A.col(r_idx))};
182 mpq_class min_ratio = -1;
183 int min_idx = -1;
184 for (int d_idx = 0; d_idx < d.size(); ++d_idx) {
185 if (d(d_idx) <= tolerance) continue;
186 const mpq_class ratio = zb(d_idx) / d(d_idx);
187 if (min_ratio == -1 || ratio < min_ratio) {
188 min_ratio = ratio;
189 min_idx = d_idx;
190 }
191 }
192 // fmt::println("zb: {}\nd: {}\n", zb, d);
193 if (min_idx == -1) return LpResult::UNBOUNDED;
194 // fmt::println("Min ratio: [{}] = {}", min_idx, d(min_idx));
195 //
196 // fmt::println("Updating\n{}\n with leaving = {} from basis.col({}), entering = {} from A.col({})", basis,
197 // basis.basis_vectors().col(min_idx), min_idx, A.col(r_idx), r_idx);
198 fmt::println("Leaving = {}, entering = {}", min_idx, r_idx);
199
200 // Update the basis
201 basis.Update(min_idx, r_idx);
202 // DELPI_ASSERT(basis.basis_vectors().determinant() != 0, "Basis matrix must be non-singular");
203 }
204 DELPI_RUNTIME_ERROR("Maximum number of iterations reached");
205}
206
207LpResult DelpiLpSolver::FeasibilitySolve(Matrix<mpq_class>& slack_A, Vector<mpq_class>& slack_b,
208 internal::Basis<mpq_class>& slack_basis) {
209 DELPI_TRACE("DelpiLpSolver::FeasibilitySolve()");
210 DELPI_ASSERT(&slack_A == &slack_basis.A(), "Basis must be built from matrix A");
211 DELPI_ASSERT(slack_A.rows() == slack_b.size(), "Inconsistent number of rows in A and b");
212
213 // Initial feasible basis by adding auxiliary variables
214 Matrix<mpq_class> aux_A{};
215 Vector<mpq_class> aux_c{};
216 std::vector<Index> aux_columns{};
217 internal::Basis<mpq_class> aux_basis{AuxForm(slack_A, slack_b, aux_A, aux_c, aux_columns)};
218
219 DELPI_ASSERT(!aux_basis.basis_idxs().empty(), "Auxiliary basis must have at least one index");
220 if (*std::ranges::max_element(aux_basis.basis_idxs()) < slack_A.cols()) {
221 // TODO(tend): we can just not do this assignment and return the status directly
222 slack_basis = internal::Basis<mpq_class>{slack_A};
223 return LpResult::OPTIMAL;
224 }
225
226 // TODO(tend): Solve the feasible problem in increasing precision
227 for (const auto& [precision, tolerance] : precisions) {
228 LpResult feas_result;
229 double feas_obj;
230 internal::Basis<mpq_class> feas_basis{aux_A, aux_basis};
231
232 if (precision == 0) {
233 DELPI_UNREACHABLE();
234 DELPI_DEBUG("Feasibility check with precision=0 (rational)");
235 mpq_class obj;
236 feas_result = InternalSolve(aux_A, slack_b, aux_c, mpq_class{0}, feas_basis,
237 static_cast<Vector<mpq_class>*>(nullptr), &obj);
238 feas_obj = obj.get_d();
239 } else if (precision == 64) { // double precision
240 DELPI_DEBUG("Feasibility check with precision=64 (double)");
241 Matrix<double> aux_A_d = aux_A.cast<double>();
242 Vector<double> slack_b_d = slack_b.cast<double>();
243 Vector<double> aux_c_d = aux_c.cast<double>();
244 // DELPI_DEV_FMT("A:\n{}\nb:\n{}\nc:\n{}", aux_A_d, slack_b_d, aux_c_d);
245 internal::Basis<double> aux_basis_d{aux_A_d, aux_basis};
246 feas_result = InternalSolve(aux_A_d, slack_b_d, aux_c_d, 1e-6, aux_basis_d, static_cast<Vector<double>*>(nullptr),
247 &feas_obj);
248 feas_basis = aux_basis_d;
249 } else {
250 DELPI_UNREACHABLE();
251 }
252
253 DELPI_DEV_FMT("Feasibility check: result={}, obj={}, tolerance={}", feas_result, feas_obj, tolerance);
254 // The auxiliary problem is always feasible and bounded. We need to investigate further
255 if (feas_result != LpResult::OPTIMAL) continue;
256 // The objective value is less than the tolerance. We can say that the problem is feasible
257 if (feas_obj <= tolerance) {
258 RemoveAuxiliaryColumns(feas_basis, aux_columns, slack_A, slack_b, slack_basis);
259 return LpResult::OPTIMAL;
260 }
261
262 // The floating point simplex returned optimal with an obj value > tolerance.
263 // We need to certify the feasibility of the problem with exact arithmetic
264 const LpResult feasibility = FeasibilityCheck(aux_A, slack_b, aux_c, feas_basis);
265 if (feasibility == LpResult::INFEASIBLE) return LpResult::INFEASIBLE;
266 // We have found a feasible solution, remove the auxiliary columns and return the result
267 if (feasibility == LpResult::OPTIMAL) {
268 RemoveAuxiliaryColumns(feas_basis, aux_columns, slack_A, slack_b, slack_basis);
269 return LpResult::OPTIMAL;
270 }
271 }
272 throw DelpiLpSolverException("Could not prove feasibility with the provided precisions");
273}
274LpResult DelpiLpSolver::OptimalitySolve(const Matrix<mpq_class>& slack_A, const Vector<mpq_class>& slack_b,
275 const Vector<mpq_class>& slack_c, internal::Basis<mpq_class>& slack_basis) {
276 DELPI_TRACE("DelpiLpSolver::OptimalitySolve()");
277 DELPI_ASSERT(&slack_A == &slack_basis.A(), "Basis must be built from matrix A");
278 DELPI_ASSERT(slack_A.rows() == slack_b.size(), "Inconsistent number of rows in A and b");
279
280 // TODO(tend): Solve the feasible problem in increasing precision
281 for (const auto& [precision, tolerance] : precisions) {
282 LpResult opt_result;
283 internal::Basis<mpq_class> opt_basis{slack_A, slack_basis};
284
285 if (precision == 0) {
286 DELPI_UNREACHABLE();
287 DELPI_DEBUG("Feasibility check with precision=0 (rational)");
288 mpq_class obj;
289 opt_result = InternalSolve(slack_A, slack_b, slack_c, mpq_class{0}, opt_basis);
290 } else if (precision == 64) { // double precision
291 DELPI_DEBUG("Feasibility check with precision=64 (double)");
292 Matrix<double> slack_A_d = slack_A.cast<double>();
293 Vector<double> slack_b_d = slack_b.cast<double>();
294 Vector<double> slack_c_d = slack_c.cast<double>();
295 // DELPI_DEV_FMT("A:\n{}\nb:\n{}\nc:\n{}", slack_A_d, slack_b_d, slack_c_d);
296 internal::Basis<double> aux_basis_d{slack_A_d, slack_basis};
297 opt_result = InternalSolve(slack_A_d, slack_b_d, slack_c_d, 1e-6, aux_basis_d);
298 opt_basis = aux_basis_d;
299 } else {
300 DELPI_UNREACHABLE();
301 }
302
303 DELPI_DEV_FMT("Optimality check: result={} tolerance={}", opt_result, tolerance);
304 // The optimality problem is always feasible. We need to investigate further
305 if (opt_result == LpResult::INFEASIBLE) continue;
306
307 // The floating point simplex returned unbounded. We need to certify the unboundedness of the problem
308 if (opt_result == LpResult::UNBOUNDED) {
309 if (UnboundednessCheck(slack_A, slack_b, slack_c, opt_basis) == LpResult::UNBOUNDED) {
310 slack_basis = opt_basis;
311 return LpResult::UNBOUNDED;
312 }
313 }
314
315 // The floating point simplex returned optimal. We need to certify the optimality of the problem
316 if (opt_result == LpResult::OPTIMAL) {
317 if (OptimalityCheck(slack_A, slack_b, slack_c, opt_basis) == LpResult::OPTIMAL) {
318 slack_basis = opt_basis;
319 return LpResult::OPTIMAL;
320 }
321 }
322 }
323 throw DelpiLpSolverException("Could not find an optimal solution with the provided precisions");
324}
325
326LpResult DelpiLpSolver::FeasibilityCheck(const Matrix<mpq_class>& aux_A, const Vector<mpq_class>& slack_b,
327 const Vector<mpq_class>& aux_c,
328 const internal::Basis<mpq_class>& feas_basis) const {
329 DELPI_ASSERT(aux_A.rows() == slack_b.size(), "Inconsistent number of rows in A and b");
330 DELPI_ASSERT(&aux_A == &feas_basis.A(), "Basis must be built from matrix A");
331 DELPI_ASSERT(feas_basis.basis_vectors().determinant() != 0, "Basis matrix must be non-singular");
332
333 DELPI_DEV("Feasibility check: auxiliary problem is feasible and bounded");
334 if (feas_basis.basis_vectors().determinant() == 0) return LpResult::ERROR;
335 internal::BgLinearSystemSolver<mpq_class> solver{config_};
336 solver.Factorise(feas_basis);
337 const Vector<mpq_class> zb{solver.Solve(slack_b)};
338 const mpq_class obj{aux_c(feas_basis.basis_idxs()).transpose() * zb};
339 const Vector<mpq_class> y{solver.TransposeSolve(aux_c(feas_basis.basis_idxs()))};
340 const Vector<mpq_class> r{aux_c - aux_A.transpose() * y};
341 DELPI_DEV_FMT("Feasibility check: zb>=0 ? {} | r>=0 ? {} | obj={}", (zb.array() >= 0).all(), (r.array() >= 0).all(),
342 obj);
343 if ((zb.array() >= 0).all() && (r.array() >= 0).all() && obj > 0) return LpResult::INFEASIBLE;
344 return LpResult::OPTIMAL; // TODO(tend): return ERROR?
345}
346LpResult DelpiLpSolver::OptimalityCheck(const Matrix<mpq_class>& slack_A, const Vector<mpq_class>& slack_b,
347 const Vector<mpq_class>& slack_c, const internal::Basis<mpq_class>& basis) {
348 DELPI_TRACE("DelpiLpSolver::OptimalityCheck()");
349 if (basis.basis_vectors().determinant() == 0) return LpResult::ERROR;
350 internal::BgLinearSystemSolver<mpq_class> solver{config_};
351 solver.Factorise(basis);
352 const Vector<mpq_class> zb{solver.Solve(slack_b)};
353 // Primal infeasible
354 if ((zb.array() < 0).any()) return LpResult::ERROR;
355 const Vector<mpq_class> y{solver.TransposeSolve(slack_c(basis.basis_idxs()))};
356 const Vector<mpq_class> r{slack_c - slack_A.transpose() * y};
357 // Dual infeasible
358 if ((r.array() < 0).any()) return LpResult::ERROR;
359
360 obj_lb_ = slack_b.transpose() * y;
361 obj_ub_ = slack_c(basis.basis_idxs()).transpose() * zb;
363 if (delta_ > config_.delta()) return LpResult::ERROR;
364
365 // Compute the original problem solution
366 x_ = Vector<mpq_class>::Zero(slack_A.cols());
367 x_(basis.basis_idxs()) = zb;
368 problem_.FixSolution(x_);
369 obj_ub_ = slack_c.head(problem_.num_columns()).transpose() * x_;
371
372 return LpResult::OPTIMAL;
373}
374LpResult DelpiLpSolver::UnboundednessCheck(const Matrix<mpq_class>& slack_A, const Vector<mpq_class>& slack_b,
375 const Vector<mpq_class>& slack_c,
376 const internal::Basis<mpq_class>& basis) const {
377 internal::BgLinearSystemSolver<mpq_class> solver{config_};
378 solver.Factorise(basis);
379 const Vector<mpq_class> zb{solver.Solve(slack_b)};
380 if ((zb.array() < 0).any()) return LpResult::ERROR;
381 const auto y{solver.TransposeSolve(slack_c(basis.basis_idxs()))};
382 const Vector<mpq_class> r{slack_c - slack_A.transpose() * y};
383 for (Index i = 0; i < r.size(); ++i) {
384 if (r(i) < 0) {
385 const Vector<mpq_class> d{solver.Solve(slack_A.col(i))};
386 if ((d.array() <= 0).all()) return LpResult::UNBOUNDED;
387 }
388 }
389 return LpResult::ERROR;
390}
391void DelpiLpSolver::RemoveAuxiliaryColumns(const internal::Basis<mpq_class>& feas_basis,
392 const std::vector<Index>& aux_columns, Matrix<mpq_class>& slack_A,
393 Vector<mpq_class>& slack_b, internal::Basis<mpq_class>& slack_basis) const {
394 DELPI_TRACE("DelpiLpSolver::RemoveAuxiliaryColumns()");
395 DELPI_ASSERT(&slack_A == &slack_basis.A(), "The basis must be built from the same matrix A");
396
397 std::vector<Index> rows_to_remove{};
398 std::vector<std::size_t> columns_to_remove{};
399 for (std::size_t i = 0; i < feas_basis.basis_idxs().size(); ++i) {
400 if (feas_basis.basis_idxs()[i] < slack_A.cols()) continue; // Valid non-auxiliary index. Keep it
401
402 // Remove the row from the coefficient matrix
403 Index row = aux_columns.at(feas_basis.basis_idxs()[i] - slack_A.cols());
404 rows_to_remove.emplace_back(row);
405 columns_to_remove.emplace_back(i);
406 }
407
408 // TODO(tend): we may need to keep the order of rows fixed. But for now let's take the more efficient approach
409 for (const Index i : rows_to_remove) {
410 DELPI_DEV_FMT("Removing row {}", i);
411 slack_A.row(i) = slack_A.row(slack_A.rows() - 1).eval();
412 slack_A.conservativeResize(slack_A.rows() - 1, Eigen::NoChange);
413 slack_b.row(i) = slack_b.row(slack_b.size() - 1).eval();
414 slack_b.conservativeResize(slack_b.size() - 1);
415 }
416
417 slack_basis.FromBasis(feas_basis, columns_to_remove);
418 DELPI_ASSERT(slack_basis.basis_vectors().determinant() != 0, "Basis matrix must be non-singular");
419}
420internal::Basis<mpq_class> DelpiLpSolver::AuxForm(const Matrix<mpq_class>& slack_A, const Vector<mpq_class>& slack_b,
421 Matrix<mpq_class>& aux_A, Vector<mpq_class>& aux_c,
422 std::vector<Index>& aux_columns) const {
423 DELPI_TRACE("DelpiLpSolver::StdForm()");
424 DELPI_ASSERT(aux_columns.empty(), "Auxiliary columns must be empty");
425 DELPI_ASSERT(slack_A.rows() == slack_b.size(), "Inconsistent number of rows in A and b");
426
427 for (Index i = 0; i < slack_A.rows(); ++i) {
428 aux_columns.emplace_back(i);
429 }
430
431 aux_A = Matrix<mpq_class>{slack_A.rows(), slack_A.cols() + slack_b.size()};
432 aux_A.leftCols(slack_A.cols()) = slack_A;
433 aux_A.rightCols(slack_b.size()).setZero();
434
435 std::vector<Index> aux_basis_idx{};
436 aux_basis_idx.reserve(slack_A.rows());
437
438 for (Index i = 0; i < slack_b.size(); ++i) {
439 if (slack_A(i, slack_A.cols() - slack_A.rows() + i) != 0 &&
440 (slack_b(i) < 0) == (slack_A(i, slack_A.cols() - slack_A.rows() + i) < 0)) {
441 aux_basis_idx.emplace_back(slack_A.cols() - slack_A.rows() + i);
442 } else {
443 aux_A(i, slack_A.cols() + i) = slack_b(i) < 0 ? -1 : 1;
444 aux_basis_idx.emplace_back(slack_A.cols() + i);
445 }
446 }
447
448 aux_c = Vector<mpq_class>::Zero(slack_A.cols() + slack_b.size());
449 aux_c.tail(slack_b.size()).setConstant(1);
450
451 // More efficient implementation which only adds the necessary aux columns
452#if 0
453 aux_columns.clear();
454 std::vector<Index> aux_basis_idx{};
455 aux_basis_idx.reserve(slack_A.rows());
456 aux_columns.reserve(slack_b.size());
457 for (Index i = 0; i < slack_b.size(); ++i) {
458 if ((slack_b(i) < 0) != slack_A(i, slack_A.cols() - slack_A.rows()) < 0) {
459 aux_columns.emplace_back(i);
460 } else {
461 aux_basis_idx.emplace_back(i); // Add the slack columns which we know are trivially feasible to the basis
462 }
463 }
464
465 // Initial feasible basis mapping to the columns of A that either contain an aux variable or an active slack variable
466 aux_A = Matrix<mpq_class>{slack_A.rows(), slack_A.cols() + static_cast<Index>(aux_columns.size())};
467 aux_A.leftCols(slack_A.cols()) = slack_A;
468 aux_A.rightCols(aux_columns.size()).setZero();
469 for (Index i = 0; i < static_cast<Index>(aux_columns.size()); ++i) {
470 aux_A.col(slack_A.cols() + i).setZero();
471 aux_A(aux_columns[i], slack_A.cols() + i) = -1;
472 aux_basis_idx.emplace_back(slack_A.cols() + i); // Add the auxiliary columns to the basis
473 }
474
475 aux_c = Vector<mpq_class>::Zero(slack_A.cols() + static_cast<Index>(aux_columns.size()));
476 aux_c.tail(static_cast<Index>(aux_columns.size())).setConstant(1);
477
478 DELPI_ASSERT(static_cast<Index>(aux_basis_idx.size()) == slack_A.rows(),
479 "Inconsistent number of rows in A and basis");
480 DELPI_ASSERT(static_cast<Index>(aux_basis_idx.size()) <= slack_A.rows(),
481 "Inconsistent number of rows in A and aux columns");
482#endif
483
484 return {aux_A, std::move(aux_basis_idx)};
485}
486
488
489std::ostream& operator<<(std::ostream& os, const DelpiLpSolver& solver) {
490 return os << "DelpiLpSolver{ num_columns: " << solver.num_columns() << ", num_rows: " << solver.num_rows() << "\n"
491 << solver.problem() << "}\n";
492}
493
494template LpResult DelpiLpSolver::InternalSolve(const Matrix<double>&, const Vector<double>&, const Vector<double>&,
495 const double&, internal::Basis<double>&, Vector<double>*, double*);
496template LpResult DelpiLpSolver::InternalSolve(const Matrix<mpq_class>&, const Vector<mpq_class>&,
497 const Vector<mpq_class>&, const mpq_class&, internal::Basis<mpq_class>&,
498 Vector<mpq_class>*, mpq_class*);
499
500} // namespace delpi
Simple dataclass used to store the configuration of the program.
Definition Config.h:36
Linear programming solver using a custom implementation of the Simplex algorithm.
internal::LpProblem problem_
Linear programming problem.
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 RemoveAuxiliaryColumns(const internal::Basis< mpq_class > &feas_basis, const std::vector< Index > &aux_columns, Matrix< mpq_class > &slack_A, Vector< mpq_class > &slack_b, internal::Basis< mpq_class > &slack_basis) const
Use aux_basis to update basis with a set of columns we know feasible.
LpResult InternalSolve(const Matrix< T > &A, const Vector< T > &b, const Vector< T > &c, const T &tolerance, internal::Basis< T > &basis, Vector< T > *x=nullptr, T *obj=nullptr)
Solve the LP problem using the Simplex algorithm.
Vector< mpq_class > x_
Solution vector.
void ReserveRows(int num_rows) override
Reserve space for the given number of rows.
void EnsureSenseCore() override
Make sure the LP solvers are aware of the sense of the LP problem (minimisation or maximisation).
LpResult SolveCore() override
Internal method that optimises the LP problem with the given delta.
void ReserveColumns(int num_columns) override
Reserve space for the given number of columns and rows.
Row row(RowIndex row_idx) const override
Get the row at the given row_idx index.
Column column(ColumnIndex column_idx) const override
Get the column at the given column_idx index.
mpq_class delta_
Precision for the optimality check.
internal::Basis< mpq_class > AuxForm(const Matrix< mpq_class > &slack_A, const Vector< mpq_class > &slack_b, Matrix< mpq_class > &aux_A, Vector< mpq_class > &aux_c, std::vector< Index > &aux_columns) const
Convert a standard form LP problem into a feasible and bounded auxiliary problem.
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 SetObjective(int column, const mpq_class &value) override
The the objective coefficient of the given column to the given value.
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.
Facade class that hides the underlying LP solver used by delpi.
Definition LpSolver.h:60
virtual void ReserveColumns(int size)
Reserve space for the given number of columns and rows.
Definition LpSolver.cpp:159
const Variable & var(const int column) const
Shorthand notation to get the real variable linked with column column.
Definition LpSolver.h:205
mpq_class infinity_
Infinity threshold value.
Definition LpSolver.h:553
mpq_class ninfinity_
Negative infinity threshold value.
Definition LpSolver.h:552
Config config_
Configuration to use.
Definition LpSolver.h:533
std::vector< mpq_class > solution_
Solution vector.
Definition LpSolver.h:543
std::unordered_map< Variable, int > var_to_col_
Theory column ⇔ Variable.
Definition LpSolver.h:537
mpq_class obj_ub_
Upper bound on the objective value, if any.
Definition LpSolver.h:546
mpq_class obj_lb_
Lower bound on the objective value, if any.
Definition LpSolver.h:545
std::vector< Variable > col_to_var_
Literal ⇔ lp row.
Definition LpSolver.h:540
virtual void ReserveRows(int size)
Reserve space for the given number of rows.
Definition LpSolver.cpp:162
Real symbolic variable.
Definition Variable.h:20
Global namespace for the delpi library.
LpResult
Possible outcomes of the LP solver.
Definition LpResult.h:14
@ INFEASIBLE
The problem is infeasible.
Definition LpResult.h:19
@ UNBOUNDED
The problem is unbounded.
Definition LpResult.h:18
@ ERROR
An error occurred.
Definition LpResult.h:20
@ OPTIMAL
The problem is optimal.
Definition LpResult.h:16
FormulaKind
Kinds of symbolic formulas.
Definition FormulaKind.h:14
Convenient structure representing a column in the LP solver.
Definition Column.h:23
Structure representing a row in the LP solver in the form of a linear combination of variables.
Definition Row.h:24