|
"""Tools for optimizing a linear function for a given simplex. |
|
|
|
For the linear objective function ``f`` with linear constraints |
|
expressed using `Le`, `Ge` or `Eq` can be found with ``lpmin`` or |
|
``lpmax``. The symbols are **unbounded** unless specifically |
|
constrained. |
|
|
|
As an alternative, the matrices describing the objective and the |
|
constraints, and an optional list of bounds can be passed to |
|
``linprog`` which will solve for the minimization of ``C*x`` |
|
under constraints ``A*x <= b`` and/or ``Aeq*x = beq``, and |
|
individual bounds for variables given as ``(lo, hi)``. The values |
|
returned are **nonnegative** unless bounds are provided that |
|
indicate otherwise. |
|
|
|
Errors that might be raised are UnboundedLPError when there is no |
|
finite solution for the system or InfeasibleLPError when the |
|
constraints represent impossible conditions (i.e. a non-existant |
|
simplex). |
|
|
|
Here is a simple 1-D system: minimize `x` given that ``x >= 1``. |
|
|
|
>>> from sympy.solvers.simplex import lpmin, linprog |
|
>>> from sympy.abc import x |
|
|
|
The function and a list with the constraint is passed directly |
|
to `lpmin`: |
|
|
|
>>> lpmin(x, [x >= 1]) |
|
(1, {x: 1}) |
|
|
|
For `linprog` the matrix for the objective is `[1]` and the |
|
uivariate constraint can be passed as a bound with None acting |
|
as infinity: |
|
|
|
>>> linprog([1], bounds=(1, None)) |
|
(1, [1]) |
|
|
|
Or the matrices, corresponding to ``x >= 1`` expressed as |
|
``-x <= -1`` as required by the routine, can be passed: |
|
|
|
>>> linprog([1], [-1], [-1]) |
|
(1, [1]) |
|
|
|
If there is no limit for the objective, an error is raised. |
|
In this case there is a valid region of interest (simplex) |
|
but no limit to how small ``x`` can be: |
|
|
|
>>> lpmin(x, []) |
|
Traceback (most recent call last): |
|
... |
|
sympy.solvers.simplex.UnboundedLPError: |
|
Objective function can assume arbitrarily large values! |
|
|
|
An error is raised if there is no possible solution: |
|
|
|
>>> lpmin(x,[x<=1,x>=2]) |
|
Traceback (most recent call last): |
|
... |
|
sympy.solvers.simplex.InfeasibleLPError: |
|
Inconsistent/False constraint |
|
""" |
|
|
|
from sympy.core import sympify |
|
from sympy.core.exprtools import factor_terms |
|
from sympy.core.relational import Le, Ge, Eq |
|
from sympy.core.singleton import S |
|
from sympy.core.symbol import Dummy |
|
from sympy.core.sorting import ordered |
|
from sympy.functions.elementary.complexes import sign |
|
from sympy.matrices.dense import Matrix, zeros |
|
from sympy.solvers.solveset import linear_eq_to_matrix |
|
from sympy.utilities.iterables import numbered_symbols |
|
from sympy.utilities.misc import filldedent |
|
|
|
|
|
class UnboundedLPError(Exception): |
|
""" |
|
A linear programing problem is said to be unbounded if its objective |
|
function can assume arbitrarily large values. |
|
|
|
Example |
|
======= |
|
|
|
Suppose you want to maximize |
|
2x |
|
subject to |
|
x >= 0 |
|
|
|
There's no upper limit that 2x can take. |
|
""" |
|
|
|
pass |
|
|
|
|
|
class InfeasibleLPError(Exception): |
|
""" |
|
A linear programing problem is considered infeasible if its |
|
constraint set is empty. That is, if the set of all vectors |
|
satisfying the contraints is empty, then the problem is infeasible. |
|
|
|
Example |
|
======= |
|
|
|
Suppose you want to maximize |
|
x |
|
subject to |
|
x >= 10 |
|
x <= 9 |
|
|
|
No x can satisfy those constraints. |
|
""" |
|
|
|
pass |
|
|
|
|
|
def _pivot(M, i, j): |
|
""" |
|
The pivot element `M[i, j]` is inverted and the rest of the matrix |
|
modified and returned as a new matrix; original is left unmodified. |
|
|
|
Example |
|
======= |
|
|
|
>>> from sympy.matrices.dense import Matrix |
|
>>> from sympy.solvers.simplex import _pivot |
|
>>> from sympy import var |
|
>>> Matrix(3, 3, var('a:i')) |
|
Matrix([ |
|
[a, b, c], |
|
[d, e, f], |
|
[g, h, i]]) |
|
>>> _pivot(_, 1, 0) |
|
Matrix([ |
|
[-a/d, -a*e/d + b, -a*f/d + c], |
|
[ 1/d, e/d, f/d], |
|
[-g/d, h - e*g/d, i - f*g/d]]) |
|
""" |
|
Mi, Mj, Mij = M[i, :], M[:, j], M[i, j] |
|
if Mij == 0: |
|
raise ZeroDivisionError( |
|
"Tried to pivot about zero-valued entry.") |
|
A = M - Mj * (Mi / Mij) |
|
A[i, :] = Mi / Mij |
|
A[:, j] = -Mj / Mij |
|
A[i, j] = 1 / Mij |
|
return A |
|
|
|
|
|
def _choose_pivot_row(A, B, candidate_rows, pivot_col, Y): |
|
|
|
|
|
return min(candidate_rows, key=lambda i: (B[i] / A[i, pivot_col], Y[i])) |
|
|
|
|
|
def _simplex(A, B, C, D=None, dual=False): |
|
"""Return ``(o, x, y)`` obtained from the two-phase simplex method |
|
using Bland's rule: ``o`` is the minimum value of primal, |
|
``Cx - D``, under constraints ``Ax <= B`` (with ``x >= 0``) and |
|
the maximum of the dual, ``y^{T}B - D``, under constraints |
|
``A^{T}*y >= C^{T}`` (with ``y >= 0``). To compute the dual of |
|
the system, pass `dual=True` and ``(o, y, x)`` will be returned. |
|
|
|
Note: the nonnegative constraints for ``x`` and ``y`` supercede |
|
any values of ``A`` and ``B`` that are inconsistent with that |
|
assumption, so if a constraint of ``x >= -1`` is represented |
|
in ``A`` and ``B``, no value will be obtained that is negative; if |
|
a constraint of ``x <= -1`` is represented, an error will be |
|
raised since no solution is possible. |
|
|
|
This routine relies on the ability of determining whether an |
|
expression is 0 or not. This is guaranteed if the input contains |
|
only Float or Rational entries. It will raise a TypeError if |
|
a relationship does not evaluate to True or False. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.solvers.simplex import _simplex |
|
>>> from sympy import Matrix |
|
|
|
Consider the simple minimization of ``f = x + y + 1`` under the |
|
constraint that ``y + 2*x >= 4``. This is the "standard form" of |
|
a minimization. |
|
|
|
In the nonnegative quadrant, this inequality describes a area above |
|
a triangle with vertices at (0, 4), (0, 0) and (2, 0). The minimum |
|
of ``f`` occurs at (2, 0). Define A, B, C, D for the standard |
|
minimization: |
|
|
|
>>> A = Matrix([[2, 1]]) |
|
>>> B = Matrix([4]) |
|
>>> C = Matrix([[1, 1]]) |
|
>>> D = Matrix([-1]) |
|
|
|
Confirm that this is the system of interest: |
|
|
|
>>> from sympy.abc import x, y |
|
>>> X = Matrix([x, y]) |
|
>>> (C*X - D)[0] |
|
x + y + 1 |
|
>>> [i >= j for i, j in zip(A*X, B)] |
|
[2*x + y >= 4] |
|
|
|
Since `_simplex` will do a minimization for constraints given as |
|
``A*x <= B``, the signs of ``A`` and ``B`` must be negated since |
|
the currently correspond to a greater-than inequality: |
|
|
|
>>> _simplex(-A, -B, C, D) |
|
(3, [2, 0], [1/2]) |
|
|
|
The dual of minimizing ``f`` is maximizing ``F = c*y - d`` for |
|
``a*y <= b`` where ``a``, ``b``, ``c``, ``d`` are derived from the |
|
transpose of the matrix representation of the standard minimization: |
|
|
|
>>> tr = lambda a, b, c, d: [i.T for i in (a, c, b, d)] |
|
>>> a, b, c, d = tr(A, B, C, D) |
|
|
|
This time ``a*x <= b`` is the expected inequality for the `_simplex` |
|
method, but to maximize ``F``, the sign of ``c`` and ``d`` must be |
|
changed (so that minimizing the negative will give the negative of |
|
the maximum of ``F``): |
|
|
|
>>> _simplex(a, b, -c, -d) |
|
(-3, [1/2], [2, 0]) |
|
|
|
The negative of ``F`` and the min of ``f`` are the same. The dual |
|
point `[1/2]` is the value of ``y`` that minimized ``F = c*y - d`` |
|
under constraints a*x <= b``: |
|
|
|
>>> y = Matrix(['y']) |
|
>>> (c*y - d)[0] |
|
4*y + 1 |
|
>>> [i <= j for i, j in zip(a*y,b)] |
|
[2*y <= 1, y <= 1] |
|
|
|
In this 1-dimensional dual system, the more restrictive contraint is |
|
the first which limits ``y`` between 0 and 1/2 and the maximum of |
|
``F`` is attained at the nonzero value, hence is ``4*(1/2) + 1 = 3``. |
|
|
|
In this case the values for ``x`` and ``y`` were the same when the |
|
dual representation was solved. This is not always the case (though |
|
the value of the function will be the same). |
|
|
|
>>> l = [[1, 1], [-1, 1], [0, 1], [-1, 0]], [5, 1, 2, -1], [[1, 1]], [-1] |
|
>>> A, B, C, D = [Matrix(i) for i in l] |
|
>>> _simplex(A, B, -C, -D) |
|
(-6, [3, 2], [1, 0, 0, 0]) |
|
>>> _simplex(A, B, -C, -D, dual=True) # [5, 0] != [3, 2] |
|
(-6, [1, 0, 0, 0], [5, 0]) |
|
|
|
In both cases the function has the same value: |
|
|
|
>>> Matrix(C)*Matrix([3, 2]) == Matrix(C)*Matrix([5, 0]) |
|
True |
|
|
|
See Also |
|
======== |
|
_lp - poses min/max problem in form compatible with _simplex |
|
lpmin - minimization which calls _lp |
|
lpmax - maximimzation which calls _lp |
|
|
|
References |
|
========== |
|
|
|
.. [1] Thomas S. Ferguson, LINEAR PROGRAMMING: A Concise Introduction |
|
web.tecnico.ulisboa.pt/mcasquilho/acad/or/ftp/FergusonUCLA_lp.pdf |
|
|
|
""" |
|
A, B, C, D = [Matrix(i) for i in (A, B, C, D or [0])] |
|
if dual: |
|
_o, d, p = _simplex(-A.T, C.T, B.T, -D) |
|
return -_o, d, p |
|
|
|
if A and B: |
|
M = Matrix([[A, B], [C, D]]) |
|
else: |
|
if A or B: |
|
raise ValueError("must give A and B") |
|
|
|
M = Matrix([[C, D]]) |
|
n = M.cols - 1 |
|
m = M.rows - 1 |
|
|
|
if not all(i.is_Float or i.is_Rational for i in M): |
|
|
|
|
|
raise TypeError(filldedent(""" |
|
Only rationals and floats are allowed. |
|
""" |
|
) |
|
) |
|
|
|
|
|
|
|
X = [(False, j) for j in range(n)] |
|
Y = [(True, i) for i in range(m)] |
|
|
|
|
|
|
|
|
|
last = None |
|
|
|
while True: |
|
B = M[:-1, -1] |
|
A = M[:-1, :-1] |
|
if all(B[i] >= 0 for i in range(B.rows)): |
|
|
|
break |
|
|
|
|
|
for k in range(B.rows): |
|
if B[k] < 0: |
|
break |
|
else: |
|
pass |
|
|
|
|
|
piv_cols = [_ for _ in range(A.cols) if A[k, _] < 0] |
|
if not piv_cols: |
|
raise InfeasibleLPError(filldedent(""" |
|
The constraint set is empty!""")) |
|
_, c = min((X[i], i) for i in piv_cols) |
|
|
|
|
|
piv_rows = [_ for _ in range(A.rows) if A[_, c] > 0 and B[_] > 0] |
|
piv_rows.append(k) |
|
r = _choose_pivot_row(A, B, piv_rows, c, Y) |
|
|
|
|
|
if (r, c) == last: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
last = True |
|
break |
|
last = r, c |
|
|
|
M = _pivot(M, r, c) |
|
X[c], Y[r] = Y[r], X[c] |
|
|
|
|
|
while True: |
|
B = M[:-1, -1] |
|
A = M[:-1, :-1] |
|
C = M[-1, :-1] |
|
|
|
|
|
piv_cols = [] |
|
piv_cols = [_ for _ in range(n) if C[_] < 0] |
|
if not piv_cols: |
|
break |
|
_, c = min((X[i], i) for i in piv_cols) |
|
|
|
|
|
piv_rows = [_ for _ in range(m) if A[_, c] > 0] |
|
if not piv_rows: |
|
raise UnboundedLPError(filldedent(""" |
|
Objective function can assume |
|
arbitrarily large values!""")) |
|
r = _choose_pivot_row(A, B, piv_rows, c, Y) |
|
|
|
M = _pivot(M, r, c) |
|
X[c], Y[r] = Y[r], X[c] |
|
|
|
argmax = [None] * n |
|
argmin_dual = [None] * m |
|
|
|
for i, (v, n) in enumerate(X): |
|
if v == False: |
|
argmax[n] = 0 |
|
else: |
|
argmin_dual[n] = M[-1, i] |
|
|
|
for i, (v, n) in enumerate(Y): |
|
if v == True: |
|
argmin_dual[n] = 0 |
|
else: |
|
argmax[n] = M[i, -1] |
|
|
|
if last and not all(i >= 0 for i in argmax + argmin_dual): |
|
raise InfeasibleLPError(filldedent(""" |
|
Oscillating system led to invalid solution. |
|
If you believe there was a valid solution, please |
|
report this as a bug.""")) |
|
return -M[-1, -1], argmax, argmin_dual |
|
|
|
|
|
|
|
|
|
|
|
def _abcd(M, list=False): |
|
"""return parts of M as matrices or lists |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import Matrix |
|
>>> from sympy.solvers.simplex import _abcd |
|
|
|
>>> m = Matrix(3, 3, range(9)); m |
|
Matrix([ |
|
[0, 1, 2], |
|
[3, 4, 5], |
|
[6, 7, 8]]) |
|
>>> a, b, c, d = _abcd(m) |
|
>>> a |
|
Matrix([ |
|
[0, 1], |
|
[3, 4]]) |
|
>>> b |
|
Matrix([ |
|
[2], |
|
[5]]) |
|
>>> c |
|
Matrix([[6, 7]]) |
|
>>> d |
|
Matrix([[8]]) |
|
|
|
The matrices can be returned as compact lists, too: |
|
|
|
>>> L = a, b, c, d = _abcd(m, list=True); L |
|
([[0, 1], [3, 4]], [2, 5], [[6, 7]], [8]) |
|
""" |
|
|
|
def aslist(i): |
|
l = i.tolist() |
|
if len(l[0]) == 1: |
|
return [i[0] for i in l] |
|
return l |
|
|
|
m = M[:-1, :-1], M[:-1, -1], M[-1, :-1], M[-1:, -1:] |
|
if not list: |
|
return m |
|
return tuple([aslist(i) for i in m]) |
|
|
|
|
|
def _m(a, b, c, d=None): |
|
"""return Matrix([[a, b], [c, d]]) from matrices |
|
in Matrix or list form. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import Matrix |
|
>>> from sympy.solvers.simplex import _abcd, _m |
|
>>> m = Matrix(3, 3, range(9)) |
|
>>> L = _abcd(m, list=True); L |
|
([[0, 1], [3, 4]], [2, 5], [[6, 7]], [8]) |
|
>>> _abcd(m) |
|
(Matrix([ |
|
[0, 1], |
|
[3, 4]]), Matrix([ |
|
[2], |
|
[5]]), Matrix([[6, 7]]), Matrix([[8]])) |
|
>>> assert m == _m(*L) == _m(*_) |
|
""" |
|
a, b, c, d = [Matrix(i) for i in (a, b, c, d or [0])] |
|
return Matrix([[a, b], [c, d]]) |
|
|
|
|
|
def _primal_dual(M, factor=True): |
|
"""return primal and dual function and constraints |
|
assuming that ``M = Matrix([[A, b], [c, d]])`` and the |
|
function ``c*x - d`` is being minimized with ``Ax >= b`` |
|
for nonnegative values of ``x``. The dual and its |
|
constraints will be for maximizing `b.T*y - d` subject |
|
to ``A.T*y <= c.T``. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.solvers.simplex import _primal_dual, lpmin, lpmax |
|
>>> from sympy import Matrix |
|
|
|
The following matrix represents the primal task of |
|
minimizing x + y + 7 for y >= x + 1 and y >= -2*x + 3. |
|
The dual task seeks to maximize x + 3*y + 7 with |
|
2*y - x <= 1 and and x + y <= 1: |
|
|
|
>>> M = Matrix([ |
|
... [-1, 1, 1], |
|
... [ 2, 1, 3], |
|
... [ 1, 1, -7]]) |
|
>>> p, d = _primal_dual(M) |
|
|
|
The minimum of the primal and maximum of the dual are the same |
|
(though they occur at different points): |
|
|
|
>>> lpmin(*p) |
|
(28/3, {x1: 2/3, x2: 5/3}) |
|
>>> lpmax(*d) |
|
(28/3, {y1: 1/3, y2: 2/3}) |
|
|
|
If the equivalent (but canonical) inequalities are |
|
desired, leave `factor=True`, otherwise the unmodified |
|
inequalities for M will be returned. |
|
|
|
>>> m = Matrix([ |
|
... [-3, -2, 4, -2], |
|
... [ 2, 0, 0, -2], |
|
... [ 0, 1, -3, 0]]) |
|
|
|
>>> _primal_dual(m, False) # last condition is 2*x1 >= -2 |
|
((x2 - 3*x3, |
|
[-3*x1 - 2*x2 + 4*x3 >= -2, 2*x1 >= -2]), |
|
(-2*y1 - 2*y2, |
|
[-3*y1 + 2*y2 <= 0, -2*y1 <= 1, 4*y1 <= -3])) |
|
|
|
>>> _primal_dual(m) # condition now x1 >= -1 |
|
((x2 - 3*x3, |
|
[-3*x1 - 2*x2 + 4*x3 >= -2, x1 >= -1]), |
|
(-2*y1 - 2*y2, |
|
[-3*y1 + 2*y2 <= 0, -2*y1 <= 1, 4*y1 <= -3])) |
|
|
|
If you pass the transpose of the matrix, the primal will be |
|
identified as the standard minimization problem and the |
|
dual as the standard maximization: |
|
|
|
>>> _primal_dual(m.T) |
|
((-2*x1 - 2*x2, |
|
[-3*x1 + 2*x2 >= 0, -2*x1 >= 1, 4*x1 >= -3]), |
|
(y2 - 3*y3, |
|
[-3*y1 - 2*y2 + 4*y3 <= -2, y1 <= -1])) |
|
|
|
A matrix must have some size or else None will be returned for |
|
the functions: |
|
|
|
>>> _primal_dual(Matrix([[1, 2]])) |
|
((x1 - 2, []), (-2, [])) |
|
|
|
>>> _primal_dual(Matrix([])) |
|
((None, []), (None, [])) |
|
|
|
References |
|
========== |
|
|
|
.. [1] David Galvin, Relations between Primal and Dual |
|
www3.nd.edu/~dgalvin1/30210/30210_F07/presentations/dual_opt.pdf |
|
""" |
|
if not M: |
|
return (None, []), (None, []) |
|
if not hasattr(M, "shape"): |
|
if len(M) not in (3, 4): |
|
raise ValueError("expecting Matrix or 3 or 4 lists") |
|
M = _m(*M) |
|
m, n = [i - 1 for i in M.shape] |
|
A, b, c, d = _abcd(M) |
|
d = d[0] |
|
_ = lambda x: numbered_symbols(x, start=1) |
|
x = Matrix([i for i, j in zip(_("x"), range(n))]) |
|
yT = Matrix([i for i, j in zip(_("y"), range(m))]).T |
|
|
|
def ineq(L, r, op): |
|
rv = [] |
|
for r in (op(i, j) for i, j in zip(L, r)): |
|
if r == True: |
|
continue |
|
elif r == False: |
|
return [False] |
|
if factor: |
|
f = factor_terms(r) |
|
if f.lhs.is_Mul and f.rhs % f.lhs.args[0] == 0: |
|
assert len(f.lhs.args) == 2, f.lhs |
|
k = f.lhs.args[0] |
|
r = r.func(sign(k) * f.lhs.args[1], f.rhs // abs(k)) |
|
rv.append(r) |
|
return rv |
|
|
|
eq = lambda x, d: x[0] - d if x else -d |
|
F = eq(c * x, d) |
|
f = eq(yT * b, d) |
|
return (F, ineq(A * x, b, Ge)), (f, ineq(yT * A, c, Le)) |
|
|
|
|
|
def _rel_as_nonpos(constr, syms): |
|
"""return `(np, d, aux)` where `np` is a list of nonpositive |
|
expressions that represent the given constraints (possibly |
|
rewritten in terms of auxilliary variables) expressible with |
|
nonnegative symbols, and `d` is a dictionary mapping a given |
|
symbols to an expression with an auxilliary variable. In some |
|
cases a symbol will be used as part of the change of variables, |
|
e.g. x: x - z1 instead of x: z1 - z2. |
|
|
|
If any constraint is False/empty, return None. All variables in |
|
``constr`` are assumed to be unbounded unless explicitly indicated |
|
otherwise with a univariate constraint, e.g. ``x >= 0`` will |
|
restrict ``x`` to nonnegative values. |
|
|
|
The ``syms`` must be included so all symbols can be given an |
|
unbounded assumption if they are not otherwise bound with |
|
univariate conditions like ``x <= 3``. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.solvers.simplex import _rel_as_nonpos |
|
>>> from sympy.abc import x, y |
|
>>> _rel_as_nonpos([x >= y, x >= 0, y >= 0], (x, y)) |
|
([-x + y], {}, []) |
|
>>> _rel_as_nonpos([x >= 3, x <= 5], [x]) |
|
([_z1 - 2], {x: _z1 + 3}, [_z1]) |
|
>>> _rel_as_nonpos([x <= 5], [x]) |
|
([], {x: 5 - _z1}, [_z1]) |
|
>>> _rel_as_nonpos([x >= 1], [x]) |
|
([], {x: _z1 + 1}, [_z1]) |
|
""" |
|
r = {} |
|
np = [] |
|
aux = [] |
|
ui = numbered_symbols("z", start=1, cls=Dummy) |
|
univariate = {} |
|
unbound = [] |
|
syms = set(syms) |
|
|
|
|
|
for i in constr: |
|
if i == True: |
|
continue |
|
if i == False: |
|
return |
|
if i.has(S.Infinity, S.NegativeInfinity): |
|
raise ValueError("only finite bounds are permitted") |
|
if isinstance(i, (Le, Ge)): |
|
i = i.lts - i.gts |
|
freei = i.free_symbols |
|
if freei - syms: |
|
raise ValueError( |
|
"unexpected symbol(s) in constraint: %s" % (freei - syms) |
|
) |
|
if len(freei) > 1: |
|
np.append(i) |
|
elif freei: |
|
x = freei.pop() |
|
if x in unbound: |
|
continue |
|
ivl = Le(i, 0, evaluate=False).as_set() |
|
if x not in univariate: |
|
univariate[x] = ivl |
|
else: |
|
univariate[x] &= ivl |
|
elif i: |
|
return False |
|
else: |
|
raise TypeError(filldedent(""" |
|
only equalities like Eq(x, y) or non-strict |
|
inequalities like x >= y are allowed in lp, not %s""" % i)) |
|
|
|
|
|
|
|
for x in syms: |
|
i = univariate.get(x, True) |
|
if not i: |
|
return None |
|
if i == True: |
|
unbound.append(x) |
|
continue |
|
a, b = i.inf, i.sup |
|
if a.is_infinite: |
|
u = next(ui) |
|
r[x] = b - u |
|
aux.append(u) |
|
elif b.is_infinite: |
|
if a: |
|
u = next(ui) |
|
r[x] = a + u |
|
aux.append(u) |
|
else: |
|
|
|
pass |
|
else: |
|
u = next(ui) |
|
aux.append(u) |
|
|
|
r[x] = u + a |
|
|
|
|
|
|
|
np.append(u - (b - a)) |
|
|
|
|
|
for x in unbound: |
|
u = next(ui) |
|
r[x] = u - x |
|
aux.append(u) |
|
|
|
return np, r, aux |
|
|
|
|
|
def _lp_matrices(objective, constraints): |
|
"""return A, B, C, D, r, x+X, X for maximizing |
|
objective = Cx - D with constraints Ax <= B, introducing |
|
introducing auxilliary variables, X, as necessary to make |
|
replacements of symbols as given in r, {xi: expression with Xj}, |
|
so all variables in x+X will take on nonnegative values. |
|
|
|
Every univariate condition creates a semi-infinite |
|
condition, e.g. a single ``x <= 3`` creates the |
|
interval ``[-oo, 3]`` while ``x <= 3`` and ``x >= 2`` |
|
create an interval ``[2, 3]``. Variables not in a univariate |
|
expression will take on nonnegative values. |
|
""" |
|
|
|
|
|
F = sympify(objective) |
|
np = [sympify(i) for i in constraints] |
|
syms = set.union(*[i.free_symbols for i in [F] + np], set()) |
|
|
|
|
|
for i in range(len(np)): |
|
if isinstance(np[i], Eq): |
|
np[i] = np[i].lhs - np[i].rhs <= 0 |
|
np.append(-np[i].lhs <= 0) |
|
|
|
|
|
_ = _rel_as_nonpos(np, syms) |
|
if _ is None: |
|
raise InfeasibleLPError(filldedent(""" |
|
Inconsistent/False constraint""")) |
|
np, r, aux = _ |
|
|
|
|
|
F = F.xreplace(r) |
|
np = [i.xreplace(r) for i in np] |
|
|
|
|
|
xx = list(ordered(syms)) + aux |
|
A, B = linear_eq_to_matrix(np, xx) |
|
C, D = linear_eq_to_matrix([F], xx) |
|
return A, B, C, D, r, xx, aux |
|
|
|
|
|
def _lp(min_max, f, constr): |
|
"""Return the optimization (min or max) of ``f`` with the given |
|
constraints. All variables are unbounded unless constrained. |
|
|
|
If `min_max` is 'max' then the results corresponding to the |
|
maximization of ``f`` will be returned, else the minimization. |
|
The constraints can be given as Le, Ge or Eq expressions. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.solvers.simplex import _lp as lp |
|
>>> from sympy import Eq |
|
>>> from sympy.abc import x, y, z |
|
>>> f = x + y - 2*z |
|
>>> c = [7*x + 4*y - 7*z <= 3, 3*x - y + 10*z <= 6] |
|
>>> c += [i >= 0 for i in (x, y, z)] |
|
>>> lp(min, f, c) |
|
(-6/5, {x: 0, y: 0, z: 3/5}) |
|
|
|
By passing max, the maximum value for f under the constraints |
|
is returned (if possible): |
|
|
|
>>> lp(max, f, c) |
|
(3/4, {x: 0, y: 3/4, z: 0}) |
|
|
|
Constraints that are equalities will require that the solution |
|
also satisfy them: |
|
|
|
>>> lp(max, f, c + [Eq(y - 9*x, 1)]) |
|
(5/7, {x: 0, y: 1, z: 1/7}) |
|
|
|
All symbols are reported, even if they are not in the objective |
|
function: |
|
|
|
>>> lp(min, x, [y + x >= 3, x >= 0]) |
|
(0, {x: 0, y: 3}) |
|
""" |
|
|
|
|
|
A, B, C, D, r, xx, aux = _lp_matrices(f, constr) |
|
|
|
how = str(min_max).lower() |
|
if "max" in how: |
|
|
|
|
|
|
|
_o, p, d = _simplex(A, B, -C, -D) |
|
o = -_o |
|
elif "min" in how: |
|
o, p, d = _simplex(A, B, C, D) |
|
else: |
|
raise ValueError("expecting min or max") |
|
|
|
|
|
p = dict(zip(xx, p)) |
|
if r: |
|
|
|
r = {k: v.xreplace(p) for k, v in r.items()} |
|
|
|
p.update(r) |
|
|
|
p = {k: p[k] for k in ordered(p) if k not in aux} |
|
|
|
|
|
|
|
return o, p |
|
|
|
|
|
def lpmin(f, constr): |
|
"""return minimum of linear equation ``f`` under |
|
linear constraints expressed using Ge, Le or Eq. |
|
|
|
All variables are unbounded unless constrained. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.solvers.simplex import lpmin |
|
>>> from sympy import Eq |
|
>>> from sympy.abc import x, y |
|
>>> lpmin(x, [2*x - 3*y >= -1, Eq(x + 3*y, 2), x <= 2*y]) |
|
(1/3, {x: 1/3, y: 5/9}) |
|
|
|
Negative values for variables are permitted unless explicitly |
|
exluding, so minimizing ``x`` for ``x <= 3`` is an |
|
unbounded problem while the following has a bounded solution: |
|
|
|
>>> lpmin(x, [x >= 0, x <= 3]) |
|
(0, {x: 0}) |
|
|
|
Without indicating that ``x`` is nonnegative, there |
|
is no minimum for this objective: |
|
|
|
>>> lpmin(x, [x <= 3]) |
|
Traceback (most recent call last): |
|
... |
|
sympy.solvers.simplex.UnboundedLPError: |
|
Objective function can assume arbitrarily large values! |
|
|
|
See Also |
|
======== |
|
linprog, lpmax |
|
""" |
|
return _lp(min, f, constr) |
|
|
|
|
|
def lpmax(f, constr): |
|
"""return maximum of linear equation ``f`` under |
|
linear constraints expressed using Ge, Le or Eq. |
|
|
|
All variables are unbounded unless constrained. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.solvers.simplex import lpmax |
|
>>> from sympy import Eq |
|
>>> from sympy.abc import x, y |
|
>>> lpmax(x, [2*x - 3*y >= -1, Eq(x+ 3*y,2), x <= 2*y]) |
|
(4/5, {x: 4/5, y: 2/5}) |
|
|
|
Negative values for variables are permitted unless explicitly |
|
exluding: |
|
|
|
>>> lpmax(x, [x <= -1]) |
|
(-1, {x: -1}) |
|
|
|
If a non-negative constraint is added for x, there is no |
|
possible solution: |
|
|
|
>>> lpmax(x, [x <= -1, x >= 0]) |
|
Traceback (most recent call last): |
|
... |
|
sympy.solvers.simplex.InfeasibleLPError: inconsistent/False constraint |
|
|
|
See Also |
|
======== |
|
linprog, lpmin |
|
""" |
|
return _lp(max, f, constr) |
|
|
|
|
|
def _handle_bounds(bounds): |
|
|
|
|
|
|
|
unbound = [] |
|
R = [0] * len(bounds) |
|
|
|
def n(): |
|
return len(R) - 1 |
|
|
|
def Arow(inc=1): |
|
R.extend([0] * inc) |
|
return R[:] |
|
|
|
row = [] |
|
for x, (a, b) in enumerate(bounds): |
|
if a is None and b is None: |
|
unbound.append(x) |
|
elif a is None: |
|
|
|
A = Arow() |
|
A[x] = 1 |
|
A[n()] = 1 |
|
B = [b] |
|
row.append((A, B)) |
|
A = [0] * len(A) |
|
A[x] = -1 |
|
A[n()] = -1 |
|
B = [-b] |
|
row.append((A, B)) |
|
elif b is None: |
|
if a: |
|
|
|
A = Arow() |
|
A[x] = 1 |
|
A[n()] = -1 |
|
B = [a] |
|
row.append((A, B)) |
|
A = [0] * len(A) |
|
A[x] = -1 |
|
A[n()] = 1 |
|
B = [-a] |
|
row.append((A, B)) |
|
else: |
|
|
|
pass |
|
else: |
|
|
|
A = Arow() |
|
A[x] = 1 |
|
A[n()] = -1 |
|
B = [a] |
|
row.append((A, B)) |
|
A = [0] * len(A) |
|
A[x] = -1 |
|
A[n()] = 1 |
|
B = [-a] |
|
row.append((A, B)) |
|
|
|
A = [0] * len(A) |
|
A[x] = 0 |
|
A[n()] = 1 |
|
B = [b - a] |
|
row.append((A, B)) |
|
|
|
|
|
for x in unbound: |
|
|
|
A = Arow(2) |
|
B = [0] |
|
A[x] = 1 |
|
A[n()] = 1 |
|
A[n() - 1] = -1 |
|
row.append((A, B)) |
|
A = [0] * len(A) |
|
A[x] = -1 |
|
A[n()] = -1 |
|
A[n() - 1] = 1 |
|
row.append((A, B)) |
|
|
|
return Matrix([r+[0]*(len(R) - len(r)) for r,_ in row] |
|
), Matrix([i[1] for i in row]) |
|
|
|
|
|
def linprog(c, A=None, b=None, A_eq=None, b_eq=None, bounds=None): |
|
"""Return the minimization of ``c*x`` with the given |
|
constraints ``A*x <= b`` and ``A_eq*x = b_eq``. Unless bounds |
|
are given, variables will have nonnegative values in the solution. |
|
|
|
If ``A`` is not given, then the dimension of the system will |
|
be determined by the length of ``C``. |
|
|
|
By default, all variables will be nonnegative. If ``bounds`` |
|
is given as a single tuple, ``(lo, hi)``, then all variables |
|
will be constrained to be between ``lo`` and ``hi``. Use |
|
None for a ``lo`` or ``hi`` if it is unconstrained in the |
|
negative or positive direction, respectively, e.g. |
|
``(None, 0)`` indicates nonpositive values. To set |
|
individual ranges, pass a list with length equal to the |
|
number of columns in ``A``, each element being a tuple; if |
|
only a few variables take on non-default values they can be |
|
passed as a dictionary with keys giving the corresponding |
|
column to which the variable is assigned, e.g. ``bounds={2: |
|
(1, 4)}`` would limit the 3rd variable to have a value in |
|
range ``[1, 4]``. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.solvers.simplex import linprog |
|
>>> from sympy import symbols, Eq, linear_eq_to_matrix as M, Matrix |
|
>>> x = x1, x2, x3, x4 = symbols('x1:5') |
|
>>> X = Matrix(x) |
|
>>> c, d = M(5*x2 + x3 + 4*x4 - x1, x) |
|
>>> a, b = M([5*x2 + 2*x3 + 5*x4 - (x1 + 5)], x) |
|
>>> aeq, beq = M([Eq(3*x2 + x4, 2), Eq(-x1 + x3 + 2*x4, 1)], x) |
|
>>> constr = [i <= j for i,j in zip(a*X, b)] |
|
>>> constr += [Eq(i, j) for i,j in zip(aeq*X, beq)] |
|
>>> linprog(c, a, b, aeq, beq) |
|
(9/2, [0, 1/2, 0, 1/2]) |
|
>>> assert all(i.subs(dict(zip(x, _[1]))) for i in constr) |
|
|
|
See Also |
|
======== |
|
lpmin, lpmax |
|
""" |
|
|
|
|
|
C = Matrix(c) |
|
if C.rows != 1 and C.cols == 1: |
|
C = C.T |
|
if C.rows != 1: |
|
raise ValueError("C must be a single row.") |
|
|
|
|
|
if not A: |
|
if b: |
|
raise ValueError("A and b must both be given") |
|
|
|
|
|
A, b = zeros(0, C.cols), zeros(C.cols, 1) |
|
else: |
|
A, b = [Matrix(i) for i in (A, b)] |
|
|
|
if A.cols != C.cols: |
|
raise ValueError("number of columns in A and C must match") |
|
|
|
|
|
if A_eq is None: |
|
if not b_eq is None: |
|
raise ValueError("A_eq and b_eq must both be given") |
|
else: |
|
A_eq, b_eq = [Matrix(i) for i in (A_eq, b_eq)] |
|
|
|
A = A.col_join(A_eq) |
|
A = A.col_join(-A_eq) |
|
b = b.col_join(b_eq) |
|
b = b.col_join(-b_eq) |
|
|
|
if not (bounds is None or bounds == {} or bounds == (0, None)): |
|
|
|
if type(bounds) is tuple and len(bounds) == 2: |
|
bounds = [bounds] * A.cols |
|
elif len(bounds) == A.cols and all( |
|
type(i) is tuple and len(i) == 2 for i in bounds): |
|
pass |
|
elif type(bounds) is dict and all( |
|
type(i) is tuple and len(i) == 2 |
|
for i in bounds.values()): |
|
|
|
db = bounds |
|
bounds = [(0, None)] * A.cols |
|
while db: |
|
i, j = db.popitem() |
|
bounds[i] = j |
|
else: |
|
raise ValueError("unexpected bounds %s" % bounds) |
|
A_, b_ = _handle_bounds(bounds) |
|
aux = A_.cols - A.cols |
|
if A: |
|
A = Matrix([[A, zeros(A.rows, aux)], [A_]]) |
|
b = b.col_join(b_) |
|
else: |
|
A = A_ |
|
b = b_ |
|
C = C.row_join(zeros(1, aux)) |
|
else: |
|
aux = -A.cols |
|
|
|
o, p, d = _simplex(A, b, C) |
|
return o, p[:-aux] |
|
|
|
def show_linprog(c, A=None, b=None, A_eq=None, b_eq=None, bounds=None): |
|
from sympy import symbols |
|
|
|
C = Matrix(c) |
|
if C.rows != 1 and C.cols == 1: |
|
C = C.T |
|
if C.rows != 1: |
|
raise ValueError("C must be a single row.") |
|
|
|
|
|
if not A: |
|
if b: |
|
raise ValueError("A and b must both be given") |
|
|
|
|
|
A, b = zeros(0, C.cols), zeros(C.cols, 1) |
|
else: |
|
A, b = [Matrix(i) for i in (A, b)] |
|
|
|
if A.cols != C.cols: |
|
raise ValueError("number of columns in A and C must match") |
|
|
|
|
|
if A_eq is None: |
|
if not b_eq is None: |
|
raise ValueError("A_eq and b_eq must both be given") |
|
else: |
|
A_eq, b_eq = [Matrix(i) for i in (A_eq, b_eq)] |
|
|
|
if not (bounds is None or bounds == {} or bounds == (0, None)): |
|
|
|
if type(bounds) is tuple and len(bounds) == 2: |
|
bounds = [bounds] * A.cols |
|
elif len(bounds) == A.cols and all( |
|
type(i) is tuple and len(i) == 2 for i in bounds): |
|
pass |
|
elif type(bounds) is dict and all( |
|
type(i) is tuple and len(i) == 2 |
|
for i in bounds.values()): |
|
|
|
db = bounds |
|
bounds = [(0, None)] * A.cols |
|
while db: |
|
i, j = db.popitem() |
|
bounds[i] = j |
|
else: |
|
raise ValueError("unexpected bounds %s" % bounds) |
|
|
|
x = Matrix(symbols('x1:%s' % (A.cols+1))) |
|
f,c = (C*x)[0], [i<=j for i,j in zip(A*x, b)] + [Eq(i,j) for i,j in zip(A_eq*x,b_eq)] |
|
for i, (lo, hi) in enumerate(bounds): |
|
if lo is None and hi is None: |
|
continue |
|
if lo is None: |
|
c.append(x[i]<=hi) |
|
elif hi is None: |
|
c.append(x[i]>=lo) |
|
else: |
|
c.append(x[i]>=lo) |
|
c.append(x[i]<=hi) |
|
return f,c |
|
|