Linear programming
AI assistant
Build this with Claude Code, Cursor or Copilot
Copy a Tessero-tuned prompt for Linear programming, grounded in 16 real API signatures , into your IDE's AI. No chatbot, just exact context.
Tessero\Optimize\LinearProgramming solves
minimise c · x
subject to A_ub x ≤ b_ub
A_eq x = b_eq
lb ≤ x ≤ ub
(optionally) some x integer
with the conventions of scipy.optimize.linprog and scipy.optimize.milp.
The solve runs entirely in C: one PHP call per problem.
linprog
use Tessero\Optimize\LinearProgramming as LP; // Two products share two resources. // Product A earns 40 and uses 2 h machining + 1 h assembly; // product B earns 30 and uses 1 h + 1 h. 100 h machining, 80 h assembly. $r = LP::linprog( c: [-40, -30], // maximise profit = minimise its negative A_ub: [[2, 1], [1, 1]], b_ub: [100, 80], ); $r->success; // true $r->x; // [20.0, 60.0] $r->fun; // -2600.0 (profit 2600) $r->ineqlinMarginals; // [-10.0, -20.0] $r->slack([[2, 1], [1, 1]], [100, 80]); // [0.0, 0.0]: both resources used up
Marginals (shadow prices) are the change in the objective per unit
increase of each right-hand side, with SciPy's signs. Here one more hour of
assembly raises profit by 20, and one more hour of machining raises it by 10.
They tell you which constraint is worth relaxing. eqlinMarginals covers
equality rows and reducedCosts covers the variables.
Bounds
bounds: |
Meaning |
|---|---|
null (default) |
every variable in [0, +∞) |
[lo, hi] |
the same bounds for every variable |
[[lo0, hi0], [lo1, hi1], …] |
one pair per variable |
Use null or ±INF for an open side: [null, null] is a free variable.
Status
status |
success |
Meaning |
|---|---|---|
| 0 | true | optimal |
| 1 | false | iteration limit (maxiter); x holds the last point |
| 2 | false | infeasible |
| 3 | false | unbounded |
| 4 | false | numerical difficulties |
Malformed input is rejected before the solver runs: wrong shapes throw
ShapeError, and NaN or ±INF in c, A or b (NaN in bounds) throws
InvalidArgumentException (ValueError from the extension), as SciPy does.
Infinite bounds are allowed.
milp
// Facility location: open any of 3 sites (fixed cost), serve 3 customers. $fixed = [12, 10, 14]; $serve = [[4, 6, 9], [5, 4, 7], [6, 3, 4]]; // cost of serving customer j from site i // variables: open_0..2, then assign_ij (row-major) - 12 binaries $c = array_merge($fixed, ...$serve); $A_eq = $b_eq = $A_ub = $b_ub = []; for ($j = 0; $j < 3; $j++) { // each customer assigned once $row = array_fill(0, 12, 0); for ($i = 0; $i < 3; $i++) { $row[3 + 3 * $i + $j] = 1; } $A_eq[] = $row; $b_eq[] = 1; } for ($i = 0; $i < 3; $i++) { // only to open sites: assign_ij - open_i <= 0 for ($j = 0; $j < 3; $j++) { $row = array_fill(0, 12, 0); $row[3 + 3 * $i + $j] = 1; $row[$i] = -1; $A_ub[] = $row; $b_ub[] = 0; } } $r = LP::milp($c, integrality: true, A_ub: $A_ub, b_ub: $b_ub, A_eq: $A_eq, b_eq: $b_eq, bounds: [0, 1]); $r->x; // open_1 = 1 only; all customers served from site 1 $r->fun; // 26.0 $r->nodes; // branch-and-bound nodes explored $r->gap(); // relative gap between fun and bestBound (0 when proven optimal)
integrality is true (all variables integer), false, or a per-variable
list of 0/1 (SciPy's convention). nodeLimit caps the search; if it is hit
with a feasible solution in hand, status is 1 and x is the best solution
found so far. Check gap() to see how far from optimal it may be.
mipRelGap stops early once the gap is small enough.
Method and limits
- linprog: dense two-phase primal simplex on a full tableau. Pricing is Dantzig's rule, switching to Bland's rule after a run of degenerate pivots (no cycling). The ratio test is Harris's two-pass test (stable pivots). Bounds are handled by substitution.
- milp: depth-first branch and bound on that simplex, branching on the most fractional variable and exploring the nearer child first.
- Checked against SciPy's HiGHS on 14 LPs (optimal, unbounded, infeasible, equality-constrained, free and bounded variables) and 10 MILPs: identical objective to 1e-9, identical marginals where the dual is unique.
- Size: the tableau is dense,
(m+1) × (n+1)doubles. A 200 × 200 LP solves in about 10 ms. Beyond a few thousand rows or columns, memory and time grow quickly. Use a dedicated solver (HiGHS) for large sparse models; see the [roadmap(https://github.com/DMJ-CV-91913/tessero/blob/main/docs/project/roadmap.md). - Branch and bound has no cutting planes or presolve. It suits problems with tens to a few hundred integer variables.
Extension and Laravel
Tessero\Ext\Engine::linprog($c, $A_ub, $b_ub, $A_eq, $b_eq, $bounds); // array result Tessero\Ext\Engine::milp($c, true, $A_ub, $b_ub, $A_eq, $b_eq, $bounds); Tessero::linprog(...); // Laravel facade: same keys on either backend
The array result has the keys x, fun, success, status, message,
nit, ineqlin, eqlin and reduced_costs (nodes and best_bound for
milp).