LINE Solver (C++)
Templated C++ port of the LINE queueing solver
Loading...
Searching...
No Matches
simplex.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2012-2026, QORE Lab, Imperial College London
3 * All rights reserved.
4 */
5#ifndef LINE_UTIL_SIMPLEX_H
6#define LINE_UTIL_SIMPLEX_H
7
8/**
9 * @file
10 * @ingroup line_util
11 * Templated primal simplex with Bland's rule.
12 *
13 * Solves
14 * max (or min) c'x
15 * subject to A x <= b, Aeq x = beq, l <= x <= u
16 * where l and u are per-variable and either bound may be absent.
17 *
18 * Why this exists: the mapqn quadratic-reduction bounds are linear programs
19 * whose optimum IS the bound being reported. MATLAB reaches it with
20 * interior-point linprog and lands a few digits short (see the MATLAB accuracy
21 * note in matlab/lib/qrf/mapqn_bnd_qr_ld.m); the JAR reaches it with Apache
22 * Commons SimplexSolver in double precision. Instantiated at line::Rational
23 * this solver is EXACT: rational data implies a rational optimum, every pivot
24 * is a field operation on rationals, and the reported bound is the vertex
25 * value with no rounding anywhere. That is the point of the port, so the
26 * pivoting rule must not depend on a tolerance.
27 *
28 * Bland's rule is what makes that possible. It selects the lowest-index
29 * column with a strictly favourable reduced cost and, among the rows attaining
30 * the minimum ratio, the one whose basic variable has the lowest index. That
31 * pair of rules is enough to prove finite termination with no anti-cycling
32 * perturbation and no tolerance, which is why the solver is NOT gated on
33 * num_traits<T>::has_transcendental: it uses only +, -, *, / and comparison,
34 * all of which Rational supports exactly. At inexact T a small tolerance is
35 * used for the sign tests, purely so that round-off does not report a
36 * favourable reduced cost that is really zero; at exact T the tolerance is
37 * exactly zero and no such fudge exists.
38 *
39 * Variable bounds are handled by the solver itself, not by the caller:
40 * - l_j == u_j the variable is substituted out (fixed), which is
41 * what makes the mapqn assembly tractable, since its
42 * ZERO1/2/3 families fix the majority of variables
43 * at zero;
44 * - l_j finite x_j = l_j + y_j with y_j >= 0, and a finite u_j
45 * becomes one extra row y_j <= u_j - l_j;
46 * - l_j absent, u_j given x_j = u_j - y_j with y_j >= 0;
47 * - both absent x_j = y_j^+ - y_j^- with both parts >= 0.
48 * Callers therefore never need to add explicit 0 <= x <= 1 rows the way the
49 * JAR must for Apache SimplexSolver (see the mapqn note in _kb/03-api-layer.md);
50 * they call set_bounds and the rows appear internally only where a finite
51 * upper bound actually needs one.
52 *
53 * Assembly is sparse. LpModel accumulates a row in a dense scratch vector with
54 * a touched-index list and emits only the nonzeros, so building the mapqn LPs
55 * costs O(nnz) memory rather than the O(rows*cols) a dense builder would need.
56 * Repeated add() on the same column accumulates, matching the
57 * `row(idx) = row(idx) + v` idiom the MATLAB reference uses.
58 *
59 * The tableau itself is dense: it holds B^{-1}[A I] explicitly. This is a
60 * deliberate scope choice -- the port targets small and medium instances where
61 * exactness is the objective, not the large blocking instances that need a
62 * sparse revised simplex with LU updates.
63 */
64
65#include <cstddef>
66#include <string>
67#include <vector>
68
69#include "line/num/number.h"
70#include "line/util/error.h"
71
72namespace line {
73namespace lp {
74
75/** Outcome of a solve. */
76enum class LpStatus {
77 Optimal, ///< an optimal vertex was reached
78 Infeasible, ///< phase 1 ended with residual artificial mass
79 Unbounded, ///< an improving column has no blocking row
80 IterationLimit ///< the iteration cap was hit (cannot happen under Bland's rule
81 ///< with exact arithmetic; a guard for inexact T)
82};
83
84inline const char* lp_status_name(LpStatus s) {
85 switch (s) {
86 case LpStatus::Optimal: return "Optimal";
87 case LpStatus::Infeasible: return "Infeasible";
88 case LpStatus::Unbounded: return "Unbounded";
89 default: return "IterationLimit";
90 }
91}
92
93template <class T>
94struct LpSolution {
96 std::vector<T> x; ///< primal solution in the ORIGINAL variable space
97 T objective = T(); ///< c'x, in the sense requested (max or min)
98 std::size_t iterations = 0;
99 bool ok() const { return status == LpStatus::Optimal; }
100};
101
102/** Row relation. */
103enum class LpSense { LE, EQ, GE };
104
105/**
106 * Sparse LP in the natural form, with per-variable bounds.
107 *
108 * Default bounds are x_j >= 0 with no upper bound, matching linprog's
109 * convention when lb is given as zeros and ub is omitted.
110 */
111template <class T>
112class LpModel {
113public:
114 explicit LpModel(std::size_t nvars)
115 : n_(nvars),
116 lb_(nvars, T()),
117 ub_(nvars, T()),
118 lb_inf_(nvars, 0),
119 ub_inf_(nvars, 1),
120 c_(nvars, T()),
121 scratch_(nvars, T()),
122 touched_flag_(nvars, 0) {}
123
124 std::size_t num_vars() const { return n_; }
125 std::size_t num_rows() const { return rhs_.size(); }
126 std::size_t num_nonzeros() const { return cols_.size(); }
127
128 // ---------------------------------------------------------------- bounds
129 void set_lower(std::size_t j, const T& v) {
130 check(j);
131 lb_[j] = v;
132 lb_inf_[j] = 0;
133 }
134 void set_upper(std::size_t j, const T& v) {
135 check(j);
136 ub_[j] = v;
137 ub_inf_[j] = 0;
138 }
139 void set_bounds(std::size_t j, const T& lo, const T& hi) {
140 set_lower(j, lo);
141 set_upper(j, hi);
142 }
143 void set_free_lower(std::size_t j) {
144 check(j);
145 lb_inf_[j] = 1;
146 }
147 void set_free_upper(std::size_t j) {
148 check(j);
149 ub_inf_[j] = 1;
150 }
151 void set_free(std::size_t j) {
154 }
155 /** Pin a variable to a value; it is substituted out of the tableau. */
156 void fix(std::size_t j, const T& v) { set_bounds(j, v, v); }
157
158 const T& lower(std::size_t j) const { return lb_[j]; }
159 const T& upper(std::size_t j) const { return ub_[j]; }
160 bool lower_is_free(std::size_t j) const { return lb_inf_[j] != 0; }
161 bool upper_is_free(std::size_t j) const { return ub_inf_[j] != 0; }
162
163 // ------------------------------------------------------------- objective
164 void set_cost(std::size_t j, const T& v) {
165 check(j);
166 c_[j] = v;
167 }
168 void add_cost(std::size_t j, const T& v) {
169 check(j);
170 c_[j] += v;
171 }
172 const std::vector<T>& costs() const { return c_; }
173 /** true to maximize c'x (the default), false to minimize. */
174 void set_maximize(bool m) { maximize_ = m; }
175 bool maximize() const { return maximize_; }
176
177 // ---------------------------------------------------------- row assembly
178 /** Discard whatever the row accumulator holds. */
179 void row_clear() {
180 for (std::size_t k = 0; k < touched_.size(); ++k) {
181 scratch_[touched_[k]] = T();
182 touched_flag_[touched_[k]] = 0;
183 }
184 touched_.clear();
185 }
186
187 /** row(j) += v, the accumulation the MATLAB reference performs. */
188 void row_add(std::size_t j, const T& v) {
189 check(j);
190 if (!touched_flag_[j]) {
191 touched_flag_[j] = 1;
192 touched_.push_back(j);
193 }
194 scratch_[j] += v;
195 }
196
197 void row_add_int(std::size_t j, long v) { row_add(j, num_traits<T>::from_int(v)); }
198
199 /** Emit the accumulated row with the given relation and right-hand side. */
200 void emit(LpSense sense, const T& rhs) {
201 const T zero = T();
202 std::size_t nnz = 0;
203 for (std::size_t k = 0; k < touched_.size(); ++k) {
204 const std::size_t j = touched_[k];
205 if (scratch_[j] != zero) {
206 cols_.push_back(j);
207 vals_.push_back(scratch_[j]);
208 ++nnz;
209 }
210 }
211 row_start_.push_back(row_start_.back() + nnz);
212 rhs_.push_back(rhs);
213 sense_.push_back(sense);
214 row_clear();
215 }
216 void emit_le(const T& rhs) { emit(LpSense::LE, rhs); }
217 void emit_eq(const T& rhs) { emit(LpSense::EQ, rhs); }
218 void emit_ge(const T& rhs) { emit(LpSense::GE, rhs); }
222
223 // --------------------------------------------------------- row inspection
224 std::size_t row_begin(std::size_t i) const { return row_start_[i]; }
225 std::size_t row_end(std::size_t i) const { return row_start_[i + 1]; }
226 std::size_t col_at(std::size_t k) const { return cols_[k]; }
227 const T& val_at(std::size_t k) const { return vals_[k]; }
228 const T& rhs(std::size_t i) const { return rhs_[i]; }
229 LpSense sense(std::size_t i) const { return sense_[i]; }
230
231private:
232 void check(std::size_t j) const {
233 if (j >= n_) throw InputError("LpModel: variable index out of range");
234 }
235
236 std::size_t n_;
237 std::vector<T> lb_, ub_;
238 std::vector<char> lb_inf_, ub_inf_;
239 std::vector<T> c_;
240 bool maximize_ = true;
241
242 std::vector<std::size_t> row_start_ = std::vector<std::size_t>(1, 0);
243 std::vector<std::size_t> cols_;
244 std::vector<T> vals_;
245 std::vector<T> rhs_;
246 std::vector<LpSense> sense_;
247
248 std::vector<T> scratch_;
249 std::vector<std::size_t> touched_;
250 std::vector<char> touched_flag_;
251};
252
253// ---------------------------------------------------------------------------
254// Sign tests. Exactly zero tolerance when T is exact, which is the whole point.
255// ---------------------------------------------------------------------------
256
257template <class T>
260}
261
262namespace detail {
263
264/** Internal standard-form column: how an original variable maps into y >= 0. */
265enum class VarKind {
266 Fixed, ///< l == u, substituted out
267 ShiftUp, ///< x = l + y
268 ShiftDown, ///< x = u - y (no lower bound, finite upper bound)
269 Free ///< x = y+ - y-
270};
271
272struct VarMap {
273 VarKind kind = VarKind::ShiftUp;
274 std::size_t pos = 0; ///< column of y (or y+)
275 std::size_t neg = 0; ///< column of y- when Free
276};
277
278} // namespace detail
279
280/**
281 * Solve the model. See the header comment for the bound handling; the returned
282 * x is in the caller's variable space and satisfies the bounds exactly when T
283 * is exact.
284 */
285template <class T>
286LpSolution<T> simplex_solve(const LpModel<T>& model, std::size_t max_iterations = 0) {
287 using detail::VarKind;
288 using detail::VarMap;
289
290 const T zero = T();
291 const T one = num_traits<T>::from_int(1);
292 const T tol = simplex_tolerance<T>();
293 const std::size_t n = model.num_vars();
294
295 // ---- variable mapping -------------------------------------------------
296 std::vector<VarMap> vmap(n);
297 std::vector<T> fixed_value(n, zero);
298 std::vector<T> offset(n, zero); // the constant part of x_j
299 std::size_t ny = 0;
300 for (std::size_t j = 0; j < n; ++j) {
301 const bool lf = model.lower_is_free(j), uf = model.upper_is_free(j);
302 if (!lf && !uf && model.lower(j) == model.upper(j)) {
303 vmap[j].kind = VarKind::Fixed;
304 fixed_value[j] = model.lower(j);
305 offset[j] = model.lower(j);
306 } else if (!lf && !uf && model.upper(j) < model.lower(j)) {
309 return s;
310 } else if (!lf) {
311 vmap[j].kind = VarKind::ShiftUp;
312 vmap[j].pos = ny++;
313 offset[j] = model.lower(j);
314 } else if (!uf) {
315 vmap[j].kind = VarKind::ShiftDown;
316 vmap[j].pos = ny++;
317 offset[j] = model.upper(j);
318 } else {
319 vmap[j].kind = VarKind::Free;
320 vmap[j].pos = ny++;
321 vmap[j].neg = ny++;
322 offset[j] = zero;
323 }
324 }
325
326 // ---- standard-form rows: G y <= g and H y = h --------------------------
327 // Each stored as (cols, vals, rhs). Inequalities first, then equalities.
328 std::vector<std::vector<std::size_t>> le_cols, eq_cols;
329 std::vector<std::vector<T>> le_vals, eq_vals;
330 std::vector<T> le_rhs, eq_rhs;
331
332 for (std::size_t i = 0; i < model.num_rows(); ++i) {
333 const LpSense sn = model.sense(i);
334 const bool negate = (sn == LpSense::GE);
335 std::vector<std::size_t> cs;
336 std::vector<T> vs;
337 T r = model.rhs(i);
338 for (std::size_t k = model.row_begin(i); k < model.row_end(i); ++k) {
339 const std::size_t j = model.col_at(k);
340 const T a = model.val_at(k);
341 if (vmap[j].kind == VarKind::Fixed) {
342 const T contrib = a * fixed_value[j];
343 r -= contrib;
344 continue;
345 }
346 if (offset[j] != zero) {
347 const T contrib = a * offset[j];
348 r -= contrib;
349 }
350 if (vmap[j].kind == VarKind::ShiftUp) {
351 cs.push_back(vmap[j].pos);
352 vs.push_back(a);
353 } else if (vmap[j].kind == VarKind::ShiftDown) {
354 cs.push_back(vmap[j].pos);
355 const T na = -a;
356 vs.push_back(na);
357 } else {
358 cs.push_back(vmap[j].pos);
359 vs.push_back(a);
360 cs.push_back(vmap[j].neg);
361 const T na = -a;
362 vs.push_back(na);
363 }
364 }
365 if (negate) {
366 for (std::size_t k = 0; k < vs.size(); ++k) {
367 const T nv = -vs[k];
368 vs[k] = nv;
369 }
370 const T nr = -r;
371 r = nr;
372 }
373 if (sn == LpSense::EQ) {
374 eq_cols.push_back(cs);
375 eq_vals.push_back(vs);
376 eq_rhs.push_back(r);
377 } else {
378 le_cols.push_back(cs);
379 le_vals.push_back(vs);
380 le_rhs.push_back(r);
381 }
382 }
383
384 // Finite upper bounds on shifted variables become one row each.
385 for (std::size_t j = 0; j < n; ++j) {
386 if (vmap[j].kind == VarKind::ShiftUp && !model.upper_is_free(j)) {
387 const T span = model.upper(j) - model.lower(j);
388 le_cols.push_back(std::vector<std::size_t>(1, vmap[j].pos));
389 le_vals.push_back(std::vector<T>(1, one));
390 le_rhs.push_back(span);
391 } else if (vmap[j].kind == VarKind::ShiftDown && !model.lower_is_free(j)) {
392 const T span = model.upper(j) - model.lower(j);
393 le_cols.push_back(std::vector<std::size_t>(1, vmap[j].pos));
394 le_vals.push_back(std::vector<T>(1, one));
395 le_rhs.push_back(span);
396 }
397 }
398
399 const std::size_t n_le = le_rhs.size();
400 const std::size_t n_eq = eq_rhs.size();
401 std::size_t m = n_le + n_eq;
402 const std::size_t n_struct = ny + n_le; // structural + slack columns
403
404 // ---- objective in y space ---------------------------------------------
405 std::vector<T> cy(n_struct, zero);
406 T const_obj = zero;
407 for (std::size_t j = 0; j < n; ++j) {
408 const T cj = model.costs()[j];
409 if (cj == zero) continue;
410 if (vmap[j].kind == VarKind::Fixed) {
411 const T contrib = cj * fixed_value[j];
412 const_obj += contrib;
413 continue;
414 }
415 if (offset[j] != zero) {
416 const T contrib = cj * offset[j];
417 const_obj += contrib;
418 }
419 if (vmap[j].kind == VarKind::ShiftUp) {
420 cy[vmap[j].pos] += cj;
421 } else if (vmap[j].kind == VarKind::ShiftDown) {
422 cy[vmap[j].pos] -= cj;
423 } else {
424 cy[vmap[j].pos] += cj;
425 cy[vmap[j].neg] -= cj;
426 }
427 }
428 if (!model.maximize()) {
429 for (std::size_t k = 0; k < cy.size(); ++k) {
430 const T nv = -cy[k];
431 cy[k] = nv;
432 }
433 }
434
435 // tableau layout ([y | slacks | artificials]): see _kb/14-cpp-multiprecision.md
436 const std::size_t ncol1 = n_struct + m;
437 std::vector<T> tab(m * (ncol1 + 1), zero);
438 const std::size_t stride1 = ncol1 + 1;
439 for (std::size_t i = 0; i < n_le; ++i) {
440 for (std::size_t k = 0; k < le_cols[i].size(); ++k)
441 tab[i * stride1 + le_cols[i][k]] += le_vals[i][k];
442 tab[i * stride1 + ny + i] = one; // slack
443 tab[i * stride1 + ncol1] = le_rhs[i];
444 }
445 for (std::size_t i = 0; i < n_eq; ++i) {
446 const std::size_t r = n_le + i;
447 for (std::size_t k = 0; k < eq_cols[i].size(); ++k)
448 tab[r * stride1 + eq_cols[i][k]] += eq_vals[i][k];
449 tab[r * stride1 + ncol1] = eq_rhs[i];
450 }
451 // Nonnegative right-hand sides, then an artificial basis.
452 for (std::size_t i = 0; i < m; ++i) {
453 if (tab[i * stride1 + ncol1] < zero) {
454 for (std::size_t j = 0; j <= ncol1; ++j) {
455 const T nv = -tab[i * stride1 + j];
456 tab[i * stride1 + j] = nv;
457 }
458 }
459 tab[i * stride1 + n_struct + i] = one;
460 }
461
462 std::vector<std::size_t> basis(m);
463 for (std::size_t i = 0; i < m; ++i) basis[i] = n_struct + i;
464
465 std::size_t iter_cap = max_iterations;
466 if (iter_cap == 0) {
467 const std::size_t base = (m + 1) * (ncol1 + 1);
468 iter_cap = base < 100000 ? 100000 : base * 20;
469 }
470 std::size_t iters = 0;
471
472 // ---- shared pivot loop (Bland's rule) ---------------------------------
473 // cost points at a vector of length ncols; entering columns are restricted
474 // to [0, ncols_allowed).
475 struct Pivot {
476 static void apply(std::vector<T>& tb, std::size_t stride, std::size_t rows, std::size_t row,
477 std::size_t col) {
478 const T piv = tb[row * stride + col];
479 const T inv = num_traits<T>::from_int(1) / piv;
480 for (std::size_t j = 0; j < stride; ++j) {
481 const T nv = tb[row * stride + j] * inv;
482 tb[row * stride + j] = nv;
483 }
484 tb[row * stride + col] = num_traits<T>::from_int(1);
485 for (std::size_t i = 0; i < rows; ++i) {
486 if (i == row) continue;
487 const T f = tb[i * stride + col];
488 if (f == T()) continue;
489 for (std::size_t j = 0; j < stride; ++j) {
490 const T nv = tb[i * stride + j] - f * tb[row * stride + j];
491 tb[i * stride + j] = nv;
492 }
493 tb[i * stride + col] = T();
494 }
495 }
496 };
497
498 // Phase 1: maximize -(sum of artificials).
499 {
500 std::vector<T> c1(ncol1, zero);
501 for (std::size_t i = 0; i < m; ++i) c1[n_struct + i] = -one;
502 bool unbounded = false;
503 while (true) {
504 if (++iters > iter_cap) {
507 return s;
508 }
509 // reduced costs d_j = c_j - cB' * col_j
510 std::size_t enter = ncol1;
511 for (std::size_t j = 0; j < ncol1; ++j) {
512 T d = c1[j];
513 for (std::size_t i = 0; i < m; ++i) {
514 const T cb = c1[basis[i]];
515 if (cb == zero) continue;
516 const T contrib = cb * tab[i * stride1 + j];
517 d -= contrib;
518 }
519 if (d > tol) {
520 enter = j;
521 break; // Bland: lowest index
522 }
523 }
524 if (enter == ncol1) break; // phase-1 optimum
525 std::size_t leave = m;
526 T best_num = zero, best_den = one;
527 for (std::size_t i = 0; i < m; ++i) {
528 const T a = tab[i * stride1 + enter];
529 if (!(a > tol)) continue;
530 const T rr = tab[i * stride1 + ncol1];
531 if (leave == m) {
532 leave = i;
533 best_num = rr;
534 best_den = a;
535 } else {
536 const T lhs = rr * best_den;
537 const T rhs2 = best_num * a;
538 if (lhs < rhs2 || (lhs == rhs2 && basis[i] < basis[leave])) {
539 leave = i;
540 best_num = rr;
541 best_den = a;
542 }
543 }
544 }
545 if (leave == m) {
546 unbounded = true; // cannot happen: phase-1 objective is bounded
547 break;
548 }
549 Pivot::apply(tab, stride1, m, leave, enter);
550 basis[leave] = enter;
551 }
552 if (unbounded) {
555 return s;
556 }
557 // residual artificial mass -> infeasible
558 T infeas = zero;
559 for (std::size_t i = 0; i < m; ++i)
560 if (basis[i] >= n_struct) infeas += tab[i * stride1 + ncol1];
561 if (infeas > tol) {
564 return s;
565 }
566 // Drive artificials out of the basis; rows with no pivot are redundant.
567 std::vector<char> drop(m, 0);
568 for (std::size_t i = 0; i < m; ++i) {
569 if (basis[i] < n_struct) continue;
570 std::size_t piv = n_struct;
571 for (std::size_t j = 0; j < n_struct; ++j) {
572 const T a = tab[i * stride1 + j];
573 if (a > tol || a < -tol) {
574 piv = j;
575 break;
576 }
577 }
578 if (piv == n_struct) {
579 drop[i] = 1;
580 } else {
581 Pivot::apply(tab, stride1, m, i, piv);
582 basis[i] = piv;
583 }
584 }
585 // Rebuild the tableau without artificial columns and dropped rows.
586 const std::size_t stride2 = n_struct + 1;
587 std::vector<T> tab2;
588 std::vector<std::size_t> basis2;
589 tab2.reserve(m * stride2);
590 for (std::size_t i = 0; i < m; ++i) {
591 if (drop[i]) continue;
592 for (std::size_t j = 0; j < n_struct; ++j) tab2.push_back(tab[i * stride1 + j]);
593 tab2.push_back(tab[i * stride1 + ncol1]);
594 basis2.push_back(basis[i]);
595 }
596 tab.swap(tab2);
597 basis.swap(basis2);
598 m = basis.size();
599 }
600
601 // Phase 2.
602 const std::size_t stride = n_struct + 1;
603 bool unbounded = false;
604 while (true) {
605 if (++iters > iter_cap) {
608 return s;
609 }
610 std::size_t enter = n_struct;
611 for (std::size_t j = 0; j < n_struct; ++j) {
612 T d = cy[j];
613 for (std::size_t i = 0; i < m; ++i) {
614 const T cb = cy[basis[i]];
615 if (cb == zero) continue;
616 const T contrib = cb * tab[i * stride + j];
617 d -= contrib;
618 }
619 if (d > tol) {
620 enter = j;
621 break;
622 }
623 }
624 if (enter == n_struct) break;
625 std::size_t leave = m;
626 T best_num = zero, best_den = one;
627 for (std::size_t i = 0; i < m; ++i) {
628 const T a = tab[i * stride + enter];
629 if (!(a > tol)) continue;
630 const T rr = tab[i * stride + n_struct];
631 if (leave == m) {
632 leave = i;
633 best_num = rr;
634 best_den = a;
635 } else {
636 const T lhs = rr * best_den;
637 const T rhs2 = best_num * a;
638 if (lhs < rhs2 || (lhs == rhs2 && basis[i] < basis[leave])) {
639 leave = i;
640 best_num = rr;
641 best_den = a;
642 }
643 }
644 }
645 if (leave == m) {
646 unbounded = true;
647 break;
648 }
649 Pivot::apply(tab, stride, m, leave, enter);
650 basis[leave] = enter;
651 }
652
653 LpSolution<T> sol;
654 sol.iterations = iters;
655 if (unbounded) {
657 return sol;
658 }
660
661 std::vector<T> y(n_struct, zero);
662 for (std::size_t i = 0; i < m; ++i)
663 if (basis[i] < n_struct) y[basis[i]] = tab[i * stride + n_struct];
664
665 sol.x.assign(n, zero);
666 for (std::size_t j = 0; j < n; ++j) {
667 switch (vmap[j].kind) {
668 case VarKind::Fixed: sol.x[j] = fixed_value[j]; break;
669 case VarKind::ShiftUp: sol.x[j] = offset[j] + y[vmap[j].pos]; break;
670 case VarKind::ShiftDown: sol.x[j] = offset[j] - y[vmap[j].pos]; break;
671 default: sol.x[j] = y[vmap[j].pos] - y[vmap[j].neg]; break;
672 }
673 }
674 T obj = zero;
675 for (std::size_t j = 0; j < n; ++j) {
676 const T cj = model.costs()[j];
677 if (cj == zero) continue;
678 const T contrib = cj * sol.x[j];
679 obj += contrib;
680 }
681 sol.objective = obj;
682 (void)const_obj;
683 return sol;
684}
685
686} // namespace lp
687} // namespace line
688
689#endif // LINE_UTIL_SIMPLEX_H
InputError(const std::string &what)
Definition error.h:39
Sparse LP in the natural form, with per-variable bounds.
Definition simplex.h:112
const std::vector< T > & costs() const
Definition simplex.h:172
void emit_eq(const T &rhs)
Definition simplex.h:217
void emit_ge_int(long rhs)
Definition simplex.h:221
LpModel(std::size_t nvars)
Definition simplex.h:114
void set_maximize(bool m)
true to maximize c'x (the default), false to minimize.
Definition simplex.h:174
const T & val_at(std::size_t k) const
Definition simplex.h:227
void add_cost(std::size_t j, const T &v)
Definition simplex.h:168
bool lower_is_free(std::size_t j) const
Definition simplex.h:160
void emit(LpSense sense, const T &rhs)
Emit the accumulated row with the given relation and right-hand side.
Definition simplex.h:200
bool upper_is_free(std::size_t j) const
Definition simplex.h:161
std::size_t num_rows() const
Definition simplex.h:125
void emit_eq_int(long rhs)
Definition simplex.h:220
void emit_le(const T &rhs)
Definition simplex.h:216
void emit_ge(const T &rhs)
Definition simplex.h:218
std::size_t row_begin(std::size_t i) const
Definition simplex.h:224
void row_add_int(std::size_t j, long v)
Definition simplex.h:197
void set_free_upper(std::size_t j)
Definition simplex.h:147
bool maximize() const
Definition simplex.h:175
const T & rhs(std::size_t i) const
Definition simplex.h:228
std::size_t num_nonzeros() const
Definition simplex.h:126
void set_cost(std::size_t j, const T &v)
Definition simplex.h:164
const T & lower(std::size_t j) const
Definition simplex.h:158
std::size_t num_vars() const
Definition simplex.h:124
std::size_t row_end(std::size_t i) const
Definition simplex.h:225
void set_lower(std::size_t j, const T &v)
Definition simplex.h:129
void row_clear()
Discard whatever the row accumulator holds.
Definition simplex.h:179
void fix(std::size_t j, const T &v)
Pin a variable to a value; it is substituted out of the tableau.
Definition simplex.h:156
void set_free(std::size_t j)
Definition simplex.h:151
void set_upper(std::size_t j, const T &v)
Definition simplex.h:134
const T & upper(std::size_t j) const
Definition simplex.h:159
void set_bounds(std::size_t j, const T &lo, const T &hi)
Definition simplex.h:139
void row_add(std::size_t j, const T &v)
row(j) += v, the accumulation the MATLAB reference performs.
Definition simplex.h:188
void set_free_lower(std::size_t j)
Definition simplex.h:143
LpSense sense(std::size_t i) const
Definition simplex.h:229
void emit_le_int(long rhs)
Definition simplex.h:219
std::size_t col_at(std::size_t k) const
Definition simplex.h:226
The exception types the port throws.
T simplex_tolerance()
Definition simplex.h:258
LpSense
Row relation.
Definition simplex.h:103
const char * lp_status_name(LpStatus s)
Definition simplex.h:84
LpSolution< T > simplex_solve(const LpModel< T > &model, std::size_t max_iterations=0)
Solve the model.
Definition simplex.h:286
LpStatus
Outcome of a solve.
Definition simplex.h:76
@ Infeasible
phase 1 ended with residual artificial mass
Definition simplex.h:78
@ Unbounded
an improving column has no blocking row
Definition simplex.h:79
@ Optimal
an optimal vertex was reached
Definition simplex.h:77
@ IterationLimit
the iteration cap was hit (cannot happen under Bland's rule with exact arithmetic; a guard for inexac...
Definition simplex.h:80
Number-type abstraction for the templated API port.
std::size_t iterations
Definition simplex.h:98
T objective
c'x, in the sense requested (max or min)
Definition simplex.h:97
bool ok() const
Definition simplex.h:99
std::vector< T > x
primal solution in the ORIGINAL variable space
Definition simplex.h:96