Linear and integer programming
Exact linear and mixed-integer programming over the rationals. Every status is
returned with a certificate that LPResult.verify() re-checks exactly; no
step uses floating point. See the user-guide chapter Exact linear and integer
programming for the certificate conventions.
linprog(
c,
A_ub=None,
b_ub=None,
A_eq=None,
b_eq=None,
bounds=None,
sense="min",
method="auto",
basis=None
)
Solve a linear programme exactly over the rationals, with a certificate.
minimise (or, with sense="max", maximise) c @ x subject to
A_ub @ x <= b_ub, A_eq @ x == b_eq and bounds — the same
calling convention as scipy.optimize.linprog, but every coefficient
must be exact (int, Fraction, "p/q" or a rational Expr) and
every result is exact. bounds is one (lower, upper) pair for all
variables or one per variable; None (or ±inf) is no bound; the
default is (0, None).
method picks the simplex: "dense" — a two-phase tableau with
Bland's rule; "revised" — a revised simplex keeping A sparse with an
exact product-form factorisation of the basis (Dantzig pricing with a
Bland fallback, so it cannot cycle either), much faster on medium sparse
programmes; "auto" (default) — dense for at most 12 rows, revised
otherwise. basis warm-starts the revised method from a previous
result's LPResult.basis (or the result itself): a basis still
primal feasible goes straight to phase 2, one still dual feasible (after
a right-hand-side change or added rows) runs the dual simplex, anything
else falls back to a cold start (warm_started says which). Both honour
the active alkahest.context(budget=...) at every pivot.
LPResult—statuswith its certificate (dual multipliers,Farkas vector, or ray), already verified exactly, plus
basis,iterations,methodandwarm_started.
- LinprogError
E-LP-001malformed shapes,E-LP-002a float or other inexact coefficient,E-LP-006a certificate that failed its own check (a bug; the result is withheld).- BudgetExceededError
the active budget tripped (
E-BUDGET-*).
>>> r = alkahest.linprog([1, 1], A_ub=[[1, 2], [3, 1]], b_ub=[4, 6], sense="max")
>>> r.fun, r.x
(Fraction(14, 5), [Fraction(8, 5), Fraction(6, 5)])milp(
c,
A_ub=None,
b_ub=None,
A_eq=None,
b_eq=None,
bounds=None,
integrality=None,
sense="min",
max_nodes=...,
cut_rounds=...,
max_cuts=...,
warm_start=True
)
Solve a mixed-integer linear programme exactly by branch and bound.
Arguments as for linprog(), plus integrality — one flag per
variable (1/True integer, 0/False continuous, SciPy's
convention), or a single bool for all; the default makes every variable an
integer. Binary variables are integers with bounds=(0, 1).
Before branching, up to cut_rounds rounds of Gomory mixed-integer
cuts (at most max_cuts in all) strengthen the root relaxation
(cut-and-branch). The cuts are read off the exact optimal tableau, so each
one is valid: it never removes an integer-feasible point (they are listed
in MILPResult.cuts). Pass cut_rounds=0 to branch on the plain
relaxation. The search is then depth-first until a first integer point is
found, then best-bound; every node LP is solved exactly, warm-started from
its parent's basis (warm_start), and its certificate checked.
max_nodes caps the number of node LPs, and the active budget is
honoured at every node and pivot.
MILPResult—"optimal"withgap == 0,"infeasible", or"unbounded"(integer point plus integral ray).
- LinprogError
E-LP-005whenmax_nodesis reached — the error carriesincumbent(best integer point, unproven, orNone),incumbent_fun,bound(best open bound) andnodes; and the codes oflinprog().- BudgetExceededError
the budget tripped; carries
incumbent,incumbent_funandboundlikewise.
>>> r = alkahest.milp([10, 13, 7, 8], A_ub=[[4, 6, 3, 5]], b_ub=[12],
... bounds=(0, 1), sense="max")
>>> r.fun, r.x
(25, [1, 0, 1, 1])linprog_expr(
objective,
constraints,
vars,
sense="min",
integer=None,
max_nodes=...,
parameter=None,
parameter_range=None
)
Solve a linear (or mixed-integer) programme stated with expressions.
objective is an affine Expr in vars; constraints is a list
of relations built with pool.le, pool.ge or pool.pred_eq (or an
And of them) between affine expressions with rational coefficients.
The variables are free unless a constraint bounds them (write
pool.ge(x, 0) for x ≥ 0). Each <=/>= becomes one A_ub row
and each == one A_eq row, in the order given, so ineqlin and
eqlin line up with constraints (result.program() shows the rows).
integer — None for an LP; True to make every variable an
integer; or a list of the variables that must be integers (then the result
is a MILPResult, and max_nodes applies).
parameter — a symbol θ that may appear affinely either in the
objective's coefficients ((2 + θ)*x) or in the constraints'
constant terms (x + y <= 4 + θ); the result is then a
ParametricLPResult over parameter_range (a (lo, hi) pair,
None for an infinite end), as from linprog_parametric().
result.solution(solution_at(θ)for a parametric result) mapseach variable to its value.
- LinprogError
E-LP-003non-affine objective/constraint or a symbol that is not a decision variable;E-LP-002a float or irrational coefficient;E-LP-004a strict inequality or a non-relation;E-LP-007an unsupported parametric form (the parameter multiplying a variable inside a constraint, entering non-linearly, moving the objective and the right-hand side at once, or combined withinteger); and the codes oflinprog()/milp().
>>> p = alkahest.ExprPool(); x, y = p.symbol("x"), p.symbol("y")
>>> r = alkahest.linprog_expr(3*x + 2*y,
... [p.le(x + y, p.integer(4)), p.le(x + 3*y, p.integer(6)),
... p.ge(x, p.integer(0)), p.ge(y, p.integer(0)), p.le(x, p.integer(3))],
... [x, y], sense="max")
>>> r.fun
11linprog_parametric(
c,
A_ub=None,
b_ub=None,
A_eq=None,
b_eq=None,
bounds=None,
sense="min",
*,
c_theta=None,
b_ub_theta=None,
b_eq_theta=None,
theta_range=None
)
Solve a linear programme whose right-hand side or objective is
affine in one parameter θ, exactly, for every θ at once.
Arguments as for linprog(), plus the θ-direction: either
c_theta (the objective is (c + θ·c_theta) @ x) or b_ub_theta /
b_eq_theta (the right-hand sides are b_ub + θ·b_ub_theta and
b_eq + θ·b_eq_theta; a missing one is zero) — not both. theta_range
is a (lo, hi) pair, None (or ±inf) for an infinite end; the
default is the whole line.
The optimal value is piecewise affine in θ (convex or concave); the
result lists the exact breakpoints and, for each critical region, the
optimal basis, value, point and multipliers as affine functions of θ,
each region's certificate verified exactly for every θ in it.
Regions where the programme is infeasible (RHS case) or unbounded
(objective case) are reported as such.
- LinprogError
E-LP-007when bothc_thetaand a right-hand-side direction are given (a parameter in the constraint matrix cannot be stated here;linprog_expr()refuses it with the same code);E-LP-001a direction of the wrong length or an emptytheta_range; and the codes oflinprog().- BudgetExceededError
the active budget tripped.
>>> r = alkahest.linprog_parametric([1, 1], A_ub=[[1, 2], [3, 1]], b_ub=[4, 6],
... b_ub_theta=[1, 0], sense="max")
>>> r.breakpoints
[-4, -2, 8]
>>> r.value_at(0), r.value_at(10)
(Fraction(14, 5), 6)Certified result of linprog() / linprog_expr().
status is "optimal", "infeasible" or "unbounded"; every
number is an int or fractions.Fraction. The certificate for the
status is attached and verify() re-checks it exactly:
- optimal —
x,funand the dual multipliersineqlin,eqlin,lower,upper(SciPy'smarginalsconvention) withc = A_ub.T @ ineqlin + A_eq.T @ eqlin + lower + upper, complementary slackness, and dual objective equal tofun; - infeasible —
farkas: a dict of non-negative weights (ineqlin,lower,upper) and free weights (eqlin) combining the constraints into0 <= negative; - unbounded —
xa feasible point andrayan improving direction.
The optimal point, or the feasible base point of the ray when
unbounded; None when infeasible.
The Farkas certificate (infeasible only): a dict with keys
ineqlin, eqlin, lower, upper.
{variable: value} for linprog_expr() results (None
otherwise, or when there is no point).
The final simplex basis, a list of (kind, index) pairs — ("x", j)
variable j (measured from its finite bound, or the positive part of
a free variable), ("x-", j) the negative part of free variable
j, ("slack", i) the slack of A_ub row i, ("upper", j)
the slack of variable j's upper bound. Pass it (or this result) as
linprog(..., basis=...) to warm-start a related programme.
The programme in matrix form, as a dict (c, A_ub, b_ub,
A_eq, b_eq, bounds, sense) — for linprog_expr()
this shows how the relations were laid out as rows.
Result of milp().
status is "optimal" (the branch-and-bound tree closed: gap is
exactly 0 and bound == fun), "infeasible" (every leaf was
LP-infeasible with a verified Farkas vector), or "unbounded" (x is
an integer-feasible point and ray a direction, integral on the integer
variables, along which the objective improves without bound). A search that
hit max_nodes or the active budget raises instead, carrying the
incumbent.
The Gomory mixed-integer cuts added at the root, as (row, rhs)
pairs meaning row @ x <= rhs; each holds at every integer-feasible
point (optimal results only; [] when none were added).
{variable: value} for linprog_expr() results.
The programme in matrix form, as a dict (see LPResult.program()),
plus integrality.
Result of linprog_parametric() (or linprog_expr() with a
parameter): the critical regions of θ, left to right.
Each region is a dict: theta — (lo, hi) (None for an infinite
end); status — "optimal" (closed region, one basis optimal
throughout), "infeasible" or "unbounded" (open at the end shared
with an optimal region); and, when optimal, value, x,
ineqlin, eqlin, lower, upper as affine pairs (a, b)
meaning a + θ·b (value a pair of numbers, the rest pairs of
lists), and the region's basis. Every optimal region's certificate was
verified exactly for all θ in it (at its ends, and through the
certificate's slope for an infinite end).
The optimal value at theta; None where the programme is
infeasible or unbounded (see status_at()) or outside the range.
{variable: value} at theta for linprog_expr() results.
The programme at θ = 0 in matrix form (see
LPResult.program()), plus its θ-direction under
c_theta or b_ub_theta / b_eq_theta.