"""
Method agnostic utility functions for linear progamming
"""
import numpy as np
import scipy.sparse as sps
from warnings import warn
from .optimize import OptimizeWarning
from scipy.optimize._remove_redundancy import (
_remove_redundancy, _remove_redundancy_sparse, _remove_redundancy_dense
)
def _check_sparse_inputs(options, A_ub, A_eq):
"""
Check the provided ``A_ub`` and ``A_eq`` matrices conform to the specified
optional sparsity variables.
Parameters
----------
A_ub : 2D array, optional
2D array such that ``A_ub @ x`` gives the values of the upper-bound
inequality constraints at ``x``.
A_eq : 2D array, optional
2D array such that ``A_eq @ x`` gives the values of the equality
constraints at ``x``.
options : dict
A dictionary of solver options. All methods accept the following
generic options:
maxiter : int
Maximum number of iterations to perform.
disp : bool
Set to True to print convergence messages.
For method-specific options, see :func:`show_options('linprog')`.
Returns
-------
A_ub : 2D array, optional
2D array such that ``A_ub @ x`` gives the values of the upper-bound
inequality constraints at ``x``.
A_eq : 2D array, optional
2D array such that ``A_eq @ x`` gives the values of the equality
constraints at ``x``.
options : dict
A dictionary of solver options. All methods accept the following
generic options:
maxiter : int
Maximum number of iterations to perform.
disp : bool
Set to True to print convergence messages.
For method-specific options, see :func:`show_options('linprog')`.
"""
# This is an undocumented option for unit testing sparse presolve
_sparse_presolve = options.pop('_sparse_presolve', False)
if _sparse_presolve and A_eq is not None:
A_eq = sps.coo_matrix(A_eq)
if _sparse_presolve and A_ub is not None:
A_ub = sps.coo_matrix(A_ub)
sparse = options.get('sparse', False)
if not sparse and (sps.issparse(A_eq) or sps.issparse(A_ub)):
options['sparse'] = True
warn("Sparse constraint matrix detected; setting 'sparse':True.",
OptimizeWarning)
return options, A_ub, A_eq
def _format_A_constraints(A, n_x, sparse_lhs=False):
"""Format the left hand side of the constraints to a 2D array
Parameters
----------
A : 2D array
2D array such that ``A @ x`` gives the values of the upper-bound
(in)equality constraints at ``x``.
n_x : int
The number of variables in the linear programming problem.
sparse_lhs : bool
Whether either of `A_ub` or `A_eq` are sparse. If true return a
coo_matrix instead of a numpy array.
Returns
-------
np.ndarray or sparse.coo_matrix
2D array such that ``A @ x`` gives the values of the upper-bound
(in)equality constraints at ``x``.
"""
if sparse_lhs:
return sps.coo_matrix(
(0, n_x) if A is None else A, dtype=float, copy=True
)
elif A is None:
return np.zeros((0, n_x), dtype=float)
else:
return np.array(A, dtype=float, copy=True)
def _format_b_constraints(b):
"""Format the upper bounds of the constraints to a 1D array
Parameters
----------
b : 1D array
1D array of values representing the upper-bound of each (in)equality
constraint (row) in ``A``.
Returns
-------
1D np.array
1D array of values representing the upper-bound of each (in)equality
constraint (row) in ``A``.
"""
if b is None:
return np.array([], dtype=float)
b = np.array(b, dtype=float, copy=True).squeeze()
return b if b.size != 1 else b.reshape((-1))
def _clean_inputs(c, A_ub=None, b_ub=None, A_eq=None, b_eq=None, bounds=None,
x0=None):
"""
Given user inputs for a linear programming problem, return the
objective vector, upper bound constraints, equality constraints,
and simple bounds in a preferred format.
Parameters
----------
c : 1D array
Coefficients of the linear objective function to be minimized.
A_ub : 2D array, optional
2D array such that ``A_ub @ x`` gives the values of the upper-bound
inequality constraints at ``x``.
b_ub : 1D array, optional
1D array of values representing the upper-bound of each inequality
constraint (row) in ``A_ub``.
A_eq : 2D array, optional
2D array such that ``A_eq @ x`` gives the values of the equality
constraints at ``x``.
b_eq : 1D array, optional
1D array of values representing the RHS of each equality constraint
(row) in ``A_eq``.
bounds : sequence, optional
``(min, max)`` pairs for each element in ``x``, defining
the bounds on that parameter. Use None for one of ``min`` or
``max`` when there is no bound in that direction. By default
bounds are ``(0, None)`` (non-negative).
If a sequence containing a single tuple is provided, then ``min`` and
``max`` will be applied to all variables in the problem.
x0 : 1D array, optional
Starting values of the independent variables, which will be refined by
the optimization algorithm.
Returns
-------
c : 1D array
Coefficients of the linear objective function to be minimized.
A_ub : 2D array, optional
2D array such that ``A_ub @ x`` gives the values of the upper-bound
inequality constraints at ``x``.
b_ub : 1D array, optional
1D array of values representing the upper-bound of each inequality
constraint (row) in ``A_ub``.
A_eq : 2D array, optional
2D array such that ``A_eq @ x`` gives the values of the equality
constraints at ``x``.
b_eq : 1D array, optional
1D array of values representing the RHS of each equality constraint
(row) in ``A_eq``.
bounds : sequence of tuples
``(min, max)`` pairs for each element in ``x``, defining
the bounds on that parameter. Use None for each of ``min`` or
``max`` when there is no bound in that direction. By default
bounds are ``(0, None)`` (non-negative).
x0 : 1D array, optional
Starting values of the independent variables, which will be refined by
the optimization algorithm.
"""
if c is None:
raise TypeError
try:
c = np.array(c, dtype=np.float, copy=True).squeeze()
except ValueError:
raise TypeError(
"Invalid input for linprog: c must be a 1D array of numerical "
"coefficients")
else:
# If c is a single value, convert it to a 1D array.
if c.size == 1:
c = c.reshape((-1))
n_x = len(c)
if n_x == 0 or len(c.shape) != 1:
raise ValueError(
"Invalid input for linprog: c must be a 1D array and must "
"not have more than one non-singleton dimension")
if not(np.isfinite(c).all()):
raise ValueError(
"Invalid input for linprog: c must not contain values "
"inf, nan, or None")
sparse_lhs = sps.issparse(A_eq) or sps.issparse(A_ub)
try:
A_ub = _format_A_constraints(A_ub, n_x, sparse_lhs=sparse_lhs)
except ValueError:
raise TypeError(
"Invalid input for linprog: A_ub must be a 2D array "
"of numerical values")
else:
n_ub = A_ub.shape[0]
if len(A_ub.shape) != 2 or A_ub.shape[1] != n_x:
raise ValueError(
"Invalid input for linprog: A_ub must have exactly two "
"dimensions, and the number of columns in A_ub must be "
"equal to the size of c")
if (sps.issparse(A_ub) and not np.isfinite(A_ub.data).all()
or not sps.issparse(A_ub) and not np.isfinite(A_ub).all()):
raise ValueError(
"Invalid input for linprog: A_ub must not contain values "
"inf, nan, or None")
try:
b_ub = _format_b_constraints(b_ub)
except ValueError:
raise TypeError(
"Invalid input for linprog: b_ub must be a 1D array of "
"numerical values, each representing the upper bound of an "
"inequality constraint (row) in A_ub")
else:
if b_ub.shape != (n_ub,):
raise ValueError(
"Invalid input for linprog: b_ub must be a 1D array; b_ub "
"must not have more than one non-singleton dimension and "
"the number of rows in A_ub must equal the number of values "
"in b_ub")
if not(np.isfinite(b_ub).all()):
raise ValueError(
"Invalid input for linprog: b_ub must not contain values "
"inf, nan, or None")
try:
A_eq = _format_A_constraints(A_eq, n_x, sparse_lhs=sparse_lhs)
except ValueError:
raise TypeError(
"Invalid input for linprog: A_eq must be a 2D array "
"of numerical values")
else:
n_eq = A_eq.shape[0]
if len(A_eq.shape) != 2 or A_eq.shape[1] != n_x:
raise ValueError(
"Invalid input for linprog: A_eq must have exactly two "
"dimensions, and the number of columns in A_eq must be "
"equal to the size of c")
if (sps.issparse(A_eq) and not np.isfinite(A_eq.data).all()
or not sps.issparse(A_eq) and not np.isfinite(A_eq).all()):
raise ValueError(
"Invalid input for linprog: A_eq must not contain values "
"inf, nan, or None")
try:
b_eq = _format_b_constraints(b_eq)
except ValueError:
raise TypeError(
"Invalid input for linprog: b_eq must be a 1D array of "
"numerical values, each representing the upper bound of an "
"inequality constraint (row) in A_eq")
else:
if b_eq.shape != (n_eq,):
raise ValueError(
"Invalid input for linprog: b_eq must be a 1D array; b_eq "
"must not have more than one non-singleton dimension and "
"the number of rows in A_eq must equal the number of values "
"in b_eq")
if not(np.isfinite(b_eq).all()):
raise ValueError(
"Invalid input for linprog: b_eq must not contain values "
"inf, nan, or None")
# x0 gives a (optional) starting solution to the solver. If x0 is None,
# skip the checks. Initial solution will be generated automatically.
if x0 is not None:
try:
x0 = np.array(x0, dtype=float, copy=True).squeeze()
except ValueError:
raise TypeError(
"Invalid input for linprog: x0 must be a 1D array of "
"numerical coefficients")
if x0.ndim == 0:
x0 = x0.reshape((-1))
if len(x0) == 0 or x0.ndim != 1:
raise ValueError(
"Invalid input for linprog: x0 should be a 1D array; it "
"must not have more than one non-singleton dimension")
if not x0.size == c.size:
raise ValueError(
"Invalid input for linprog: x0 and c should contain the "
"same number of elements")
if not np.isfinite(x0).all():
raise ValueError(
"Invalid input for linprog: x0 must not contain values "
"inf, nan, or None")
# "If a sequence containing a single tuple is provided, then min and max
# will be applied to all variables in the problem."
# linprog doesn't treat this right: it didn't accept a list with one tuple
# in it
try:
if isinstance(bounds, str):
raise TypeError
if bounds is None or len(bounds) == 0:
bounds = [(0, None)] * n_x
elif len(bounds) == 1:
b = bounds[0]
if len(b) != 2:
raise ValueError(
"Invalid input for linprog: exactly one lower bound and "
"one upper bound must be specified for each element of x")
bounds = [b] * n_x
elif len(bounds) == n_x:
try:
len(bounds[0])
except BaseException:
bounds = [(bounds[0], bounds[1])] * n_x
for i, b in enumerate(bounds):
if len(b) != 2:
raise ValueError(
"Invalid input for linprog, bound " +
str(i) +
" " +
str(b) +
": exactly one lower bound and one upper bound must "
"be specified for each element of x")
elif (len(bounds) == 2 and np.isreal(bounds[0])
and np.isreal(bounds[1])):
bounds = [(bounds[0], bounds[1])] * n_x
else:
raise ValueError(
"Invalid input for linprog: exactly one lower bound and one "
Loading ...