12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328 |
- """
- 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 _clean_inputs(c, A_ub=None, b_ub=None, A_eq=None, b_eq=None, bounds=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.
- 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).
- """
- try:
- if c is None:
- raise TypeError
- try:
- c = np.asarray(c, dtype=float).copy().squeeze()
- except BaseException: # typically a ValueError and shouldn't be, IMO
- raise TypeError
- 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 should be a 1D array; it 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")
- except TypeError:
- raise TypeError(
- "Invalid input for linprog: c must be a 1D array of numerical "
- "coefficients")
- try:
- try:
- if sps.issparse(A_eq) or sps.issparse(A_ub):
- A_ub = sps.coo_matrix(
- (0, n_x), dtype=float) if A_ub is None else sps.coo_matrix(
- A_ub, dtype=float).copy()
- else:
- A_ub = np.zeros(
- (0, n_x), dtype=float) if A_ub is None else np.asarray(
- A_ub, dtype=float).copy()
- except BaseException:
- raise TypeError
- n_ub = A_ub.shape[0]
- if len(A_ub.shape) != 2 or A_ub.shape[1] != len(c):
- 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")
- except TypeError:
- raise TypeError(
- "Invalid input for linprog: A_ub must be a numerical 2D array "
- "with each row representing an upper bound inequality constraint")
- try:
- try:
- b_ub = np.array(
- [], dtype=float) if b_ub is None else np.asarray(
- b_ub, dtype=float).copy().squeeze()
- except BaseException:
- raise TypeError
- if b_ub.size == 1:
- b_ub = b_ub.reshape((-1))
- if len(b_ub.shape) != 1:
- raise ValueError(
- "Invalid input for linprog: b_ub should be a 1D array; it "
- "must not have more than one non-singleton dimension")
- if len(b_ub) != n_ub:
- raise ValueError(
- "Invalid input for linprog: The number of rows in A_ub must "
- "be equal to 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")
- except TypeError:
- 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")
- try:
- try:
- if sps.issparse(A_eq) or sps.issparse(A_ub):
- A_eq = sps.coo_matrix(
- (0, n_x), dtype=float) if A_eq is None else sps.coo_matrix(
- A_eq, dtype=float).copy()
- else:
- A_eq = np.zeros(
- (0, n_x), dtype=float) if A_eq is None else np.asarray(
- A_eq, dtype=float).copy()
- except BaseException:
- raise TypeError
- n_eq = A_eq.shape[0]
- if len(A_eq.shape) != 2 or A_eq.shape[1] != len(c):
- 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")
- except TypeError:
- raise TypeError(
- "Invalid input for linprog: A_eq must be a 2D array with each "
- "row representing an equality constraint")
- try:
- try:
- b_eq = np.array(
- [], dtype=float) if b_eq is None else np.asarray(
- b_eq, dtype=float).copy().squeeze()
- except BaseException:
- raise TypeError
- if b_eq.size == 1:
- b_eq = b_eq.reshape((-1))
- if len(b_eq.shape) != 1:
- raise ValueError(
- "Invalid input for linprog: b_eq should be a 1D array; it "
- "must not have more than one non-singleton dimension")
- if len(b_eq) != n_eq:
- raise ValueError(
- "Invalid input for linprog: the number of rows in A_eq "
- "must be equal to 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")
- except TypeError:
- raise TypeError(
- "Invalid input for linprog: b_eq must be a 1D array of "
- "numerical values, each representing the right hand side of an "
- "equality constraints (row) in A_eq")
- # "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 "
- "upper bound must be specified for each element of x")
- clean_bounds = [] # also creates a copy so user's object isn't changed
- for i, b in enumerate(bounds):
- if b[0] is not None and b[1] is not None and b[0] > b[1]:
- raise ValueError(
- "Invalid input for linprog, bound " +
- str(i) +
- " " +
- str(b) +
- ": a lower bound must be less than or equal to the "
- "corresponding upper bound")
- if b[0] == np.inf:
- raise ValueError(
- "Invalid input for linprog, bound " +
- str(i) +
- " " +
- str(b) +
- ": infinity is not a valid lower bound")
- if b[1] == -np.inf:
- raise ValueError(
- "Invalid input for linprog, bound " +
- str(i) +
- " " +
- str(b) +
- ": negative infinity is not a valid upper bound")
- lb = float(b[0]) if b[0] is not None and b[0] != -np.inf else None
- ub = float(b[1]) if b[1] is not None and b[1] != np.inf else None
- clean_bounds.append((lb, ub))
- bounds = clean_bounds
- except ValueError as e:
- if "could not convert string to float" in e.args[0]:
- raise TypeError
- else:
- raise e
- except TypeError as e:
- print(e)
- raise TypeError(
- "Invalid input for linprog: bounds must be a sequence of "
- "(min,max) pairs, each defining bounds on an element of x ")
- return c, A_ub, b_ub, A_eq, b_eq, bounds
- def _presolve(c, A_ub, b_ub, A_eq, b_eq, bounds, rr, tol=1e-9):
- """
- Given inputs for a linear programming problem in preferred format,
- presolve the problem: identify trivial infeasibilities, redundancies,
- and unboundedness, tighten bounds where possible, and eliminate fixed
- variables.
- 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 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.
- rr : bool
- If ``True`` attempts to eliminate any redundant rows in ``A_eq``.
- Set False if ``A_eq`` is known to be of full row rank, or if you are
- looking for a potential speedup (at the expense of reliability).
- tol : float
- The tolerance which determines when a solution is "close enough" to
- zero in Phase 1 to be considered a basic feasible solution or close
- enough to positive to serve as an optimal solution.
- Returns
- -------
- c : 1D array
- Coefficients of the linear objective function to be minimized.
- c0 : 1D array
- Constant term in objective function due to fixed (and eliminated)
- variables.
- 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. Bounds have been
- tightened where possible.
- x : 1D array
- Solution vector (when the solution is trivial and can be determined
- in presolve)
- undo: list of tuples
- (index, value) pairs that record the original index and fixed value
- for each variable removed from the problem
- complete: bool
- Whether the solution is complete (solved or determined to be infeasible
- or unbounded in presolve)
- status : int
- An integer representing the exit status of the optimization::
- 0 : Optimization terminated successfully
- 1 : Iteration limit reached
- 2 : Problem appears to be infeasible
- 3 : Problem appears to be unbounded
- 4 : Serious numerical difficulties encountered
- message : str
- A string descriptor of the exit status of the optimization.
- References
- ----------
- .. [5] Andersen, Erling D. "Finding all linearly dependent rows in
- large-scale linear programming." Optimization Methods and Software
- 6.3 (1995): 219-227.
- .. [8] Andersen, Erling D., and Knud D. Andersen. "Presolving in linear
- programming." Mathematical Programming 71.2 (1995): 221-245.
- """
- # ideas from Reference [5] by Andersen and Andersen
- # however, unlike the reference, this is performed before converting
- # problem to standard form
- # There are a few advantages:
- # * artificial variables have not been added, so matrices are smaller
- # * bounds have not been converted to constraints yet. (It is better to
- # do that after presolve because presolve may adjust the simple bounds.)
- # There are many improvements that can be made, namely:
- # * implement remaining checks from [5]
- # * loop presolve until no additional changes are made
- # * implement additional efficiency improvements in redundancy removal [2]
- undo = [] # record of variables eliminated from problem
- # constant term in cost function may be added if variables are eliminated
- c0 = 0
- complete = False # complete is True if detected infeasible/unbounded
- x = np.zeros(c.shape) # this is solution vector if completed in presolve
- status = 0 # all OK unless determined otherwise
- message = ""
- # Standard form for bounds (from _clean_inputs) is list of tuples
- # but numpy array is more convenient here
- # In retrospect, numpy array should have been the standard
- bounds = np.array(bounds)
- lb = bounds[:, 0]
- ub = bounds[:, 1]
- lb[np.equal(lb, None)] = -np.inf
- ub[np.equal(ub, None)] = np.inf
- bounds = bounds.astype(float)
- lb = lb.astype(float)
- ub = ub.astype(float)
- m_eq, n = A_eq.shape
- m_ub, n = A_ub.shape
- if (sps.issparse(A_eq)):
- A_eq = A_eq.tolil()
- A_ub = A_ub.tolil()
- def where(A):
- return A.nonzero()
- vstack = sps.vstack
- else:
- where = np.where
- vstack = np.vstack
- # zero row in equality constraints
- zero_row = np.array(np.sum(A_eq != 0, axis=1) == 0).flatten()
- if np.any(zero_row):
- if np.any(
- np.logical_and(
- zero_row,
- np.abs(b_eq) > tol)): # test_zero_row_1
- # infeasible if RHS is not zero
- status = 2
- message = ("The problem is (trivially) infeasible due to a row "
- "of zeros in the equality constraint matrix with a "
- "nonzero corresponding constraint value.")
- complete = True
- return (c, c0, A_ub, b_ub, A_eq, b_eq, bounds,
- x, undo, complete, status, message)
- else: # test_zero_row_2
- # if RHS is zero, we can eliminate this equation entirely
- A_eq = A_eq[np.logical_not(zero_row), :]
- b_eq = b_eq[np.logical_not(zero_row)]
- # zero row in inequality constraints
- zero_row = np.array(np.sum(A_ub != 0, axis=1) == 0).flatten()
- if np.any(zero_row):
- if np.any(np.logical_and(zero_row, b_ub < -tol)): # test_zero_row_1
- # infeasible if RHS is less than zero (because LHS is zero)
- status = 2
- message = ("The problem is (trivially) infeasible due to a row "
- "of zeros in the equality constraint matrix with a "
- "nonzero corresponding constraint value.")
- complete = True
- return (c, c0, A_ub, b_ub, A_eq, b_eq, bounds,
- x, undo, complete, status, message)
- else: # test_zero_row_2
- # if LHS is >= 0, we can eliminate this constraint entirely
- A_ub = A_ub[np.logical_not(zero_row), :]
- b_ub = b_ub[np.logical_not(zero_row)]
- # zero column in (both) constraints
- # this indicates that a variable isn't constrained and can be removed
- A = vstack((A_eq, A_ub))
- if A.shape[0] > 0:
- zero_col = np.array(np.sum(A != 0, axis=0) == 0).flatten()
- # variable will be at upper or lower bound, depending on objective
- x[np.logical_and(zero_col, c < 0)] = ub[
- np.logical_and(zero_col, c < 0)]
- x[np.logical_and(zero_col, c > 0)] = lb[
- np.logical_and(zero_col, c > 0)]
- if np.any(np.isinf(x)): # if an unconstrained variable has no bound
- status = 3
- message = ("If feasible, the problem is (trivially) unbounded "
- "due to a zero column in the constraint matrices. If "
- "you wish to check whether the problem is infeasible, "
- "turn presolve off.")
- complete = True
- return (c, c0, A_ub, b_ub, A_eq, b_eq, bounds,
- x, undo, complete, status, message)
- # variables will equal upper/lower bounds will be removed later
- lb[np.logical_and(zero_col, c < 0)] = ub[
- np.logical_and(zero_col, c < 0)]
- ub[np.logical_and(zero_col, c > 0)] = lb[
- np.logical_and(zero_col, c > 0)]
- # row singleton in equality constraints
- # this fixes a variable and removes the constraint
- singleton_row = np.array(np.sum(A_eq != 0, axis=1) == 1).flatten()
- rows = where(singleton_row)[0]
- cols = where(A_eq[rows, :])[1]
- if len(rows) > 0:
- for row, col in zip(rows, cols):
- val = b_eq[row] / A_eq[row, col]
- if not lb[col] - tol <= val <= ub[col] + tol:
- # infeasible if fixed value is not within bounds
- status = 2
- message = ("The problem is (trivially) infeasible because a "
- "singleton row in the equality constraints is "
- "inconsistent with the bounds.")
- complete = True
- return (c, c0, A_ub, b_ub, A_eq, b_eq, bounds,
- x, undo, complete, status, message)
- else:
- # sets upper and lower bounds at that fixed value - variable
- # will be removed later
- lb[col] = val
- ub[col] = val
- A_eq = A_eq[np.logical_not(singleton_row), :]
- b_eq = b_eq[np.logical_not(singleton_row)]
- # row singleton in inequality constraints
- # this indicates a simple bound and the constraint can be removed
- # simple bounds may be adjusted here
- # After all of the simple bound information is combined here, get_Abc will
- # turn the simple bounds into constraints
- singleton_row = np.array(np.sum(A_ub != 0, axis=1) == 1).flatten()
- cols = where(A_ub[singleton_row, :])[1]
- rows = where(singleton_row)[0]
- if len(rows) > 0:
- for row, col in zip(rows, cols):
- val = b_ub[row] / A_ub[row, col]
- if A_ub[row, col] > 0: # upper bound
- if val < lb[col] - tol: # infeasible
- complete = True
- elif val < ub[col]: # new upper bound
- ub[col] = val
- else: # lower bound
- if val > ub[col] + tol: # infeasible
- complete = True
- elif val > lb[col]: # new lower bound
- lb[col] = val
- if complete:
- status = 2
- message = ("The problem is (trivially) infeasible because a "
- "singleton row in the upper bound constraints is "
- "inconsistent with the bounds.")
- return (c, c0, A_ub, b_ub, A_eq, b_eq, bounds,
- x, undo, complete, status, message)
- A_ub = A_ub[np.logical_not(singleton_row), :]
- b_ub = b_ub[np.logical_not(singleton_row)]
- # identical bounds indicate that variable can be removed
- i_f = np.abs(lb - ub) < tol # indices of "fixed" variables
- i_nf = np.logical_not(i_f) # indices of "not fixed" variables
- # test_bounds_equal_but_infeasible
- if np.all(i_f): # if bounds define solution, check for consistency
- residual = b_eq - A_eq.dot(lb)
- slack = b_ub - A_ub.dot(lb)
- if ((A_ub.size > 0 and np.any(slack < 0)) or
- (A_eq.size > 0 and not np.allclose(residual, 0))):
- status = 2
- message = ("The problem is (trivially) infeasible because the "
- "bounds fix all variables to values inconsistent with "
- "the constraints")
- complete = True
- return (c, c0, A_ub, b_ub, A_eq, b_eq, bounds,
- x, undo, complete, status, message)
- ub_mod = ub
- lb_mod = lb
- if np.any(i_f):
- c0 += c[i_f].dot(lb[i_f])
- b_eq = b_eq - A_eq[:, i_f].dot(lb[i_f])
- b_ub = b_ub - A_ub[:, i_f].dot(lb[i_f])
- c = c[i_nf]
- x = x[i_nf]
- A_eq = A_eq[:, i_nf]
- A_ub = A_ub[:, i_nf]
- # record of variables to be added back in
- undo = [np.nonzero(i_f)[0], lb[i_f]]
- # don't remove these entries from bounds; they'll be used later.
- # but we _also_ need a version of the bounds with these removed
- lb_mod = lb[i_nf]
- ub_mod = ub[i_nf]
- # no constraints indicates that problem is trivial
- if A_eq.size == 0 and A_ub.size == 0:
- b_eq = np.array([])
- b_ub = np.array([])
- # test_empty_constraint_1
- if c.size == 0:
- status = 0
- message = ("The solution was determined in presolve as there are "
- "no non-trivial constraints.")
- elif (np.any(np.logical_and(c < 0, ub_mod == np.inf)) or
- np.any(np.logical_and(c > 0, lb_mod == -np.inf))):
- # test_no_constraints()
- # test_unbounded_no_nontrivial_constraints_1
- # test_unbounded_no_nontrivial_constraints_2
- status = 3
- message = ("The problem is (trivially) unbounded "
- "because there are no non-trivial constraints and "
- "a) at least one decision variable is unbounded "
- "above and its corresponding cost is negative, or "
- "b) at least one decision variable is unbounded below "
- "and its corresponding cost is positive. ")
- else: # test_empty_constraint_2
- status = 0
- message = ("The solution was determined in presolve as there are "
- "no non-trivial constraints.")
- complete = True
- x[c < 0] = ub_mod[c < 0]
- x[c > 0] = lb_mod[c > 0]
- # where c is zero, set x to a finite bound or zero
- x_zero_c = ub_mod[c == 0]
- x_zero_c[np.isinf(x_zero_c)] = ub_mod[c == 0][np.isinf(x_zero_c)]
- x_zero_c[np.isinf(x_zero_c)] = 0
- x[c == 0] = x_zero_c
- # if this is not the last step of presolve, should convert bounds back
- # to array and return here
- # *sigh* - convert bounds back to their standard form (list of tuples)
- # again, in retrospect, numpy array would be standard form
- lb[np.equal(lb, -np.inf)] = None
- ub[np.equal(ub, np.inf)] = None
- bounds = np.hstack((lb[:, np.newaxis], ub[:, np.newaxis]))
- bounds = bounds.tolist()
- for i, row in enumerate(bounds):
- for j, col in enumerate(row):
- if str(
- col) == "nan": # comparing col to float("nan") and
- # np.nan doesn't work. should use np.isnan
- bounds[i][j] = None
- # remove redundant (linearly dependent) rows from equality constraints
- n_rows_A = A_eq.shape[0]
- redundancy_warning = ("A_eq does not appear to be of full row rank. To "
- "improve performance, check the problem formulation "
- "for redundant equality constraints.")
- if (sps.issparse(A_eq)):
- if rr and A_eq.size > 0: # TODO: Fast sparse rank check?
- A_eq, b_eq, status, message = _remove_redundancy_sparse(A_eq, b_eq)
- if A_eq.shape[0] < n_rows_A:
- warn(redundancy_warning, OptimizeWarning)
- if status != 0:
- complete = True
- return (c, c0, A_ub, b_ub, A_eq, b_eq, bounds,
- x, undo, complete, status, message)
- # This is a wild guess for which redundancy removal algorithm will be
- # faster. More testing would be good.
- small_nullspace = 5
- if rr and A_eq.size > 0:
- try: # TODO: instead use results of first SVD in _remove_redundancy
- rank = np.linalg.matrix_rank(A_eq)
- except Exception: # oh well, we'll have to go with _remove_redundancy_dense
- rank = 0
- if rr and A_eq.size > 0 and rank < A_eq.shape[0]:
- warn(redundancy_warning, OptimizeWarning)
- dim_row_nullspace = A_eq.shape[0]-rank
- if dim_row_nullspace <= small_nullspace:
- A_eq, b_eq, status, message = _remove_redundancy(A_eq, b_eq)
- if dim_row_nullspace > small_nullspace or status == 4:
- A_eq, b_eq, status, message = _remove_redundancy_dense(A_eq, b_eq)
- if A_eq.shape[0] < rank:
- message = ("Due to numerical issues, redundant equality "
- "constraints could not be removed automatically. "
- "Try providing your constraint matrices as sparse "
- "matrices to activate sparse presolve, try turning "
- "off redundancy removal, or try turning off presolve "
- "altogether.")
- status = 4
- if status != 0:
- complete = True
- return (c, c0, A_ub, b_ub, A_eq, b_eq, bounds,
- x, undo, complete, status, message)
- def _parse_linprog(c, A_ub, b_ub, A_eq, b_eq, bounds, options):
- """
- Parse the provided linear programming problem
- ``_parse_linprog`` employs two main steps ``_check_sparse_inputs`` and
- ``_clean_inputs``. ``_check_sparse_inputs`` checks for sparsity in the
- provided constraints (``A_ub`` and ``A_eq) and if these match the provided
- sparsity optional values.
- ``_clean inputs`` checks of the provided inputs. If no violations are
- identified the objective vector, upper bound constraints, equality
- constraints, and simple bounds are returned in the expected 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
- ``(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.
- 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
- -------
- 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.
- options : dict, optional
- 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')`.
- """
- if options is None:
- options = {}
- solver_options = {k: v for k, v in options.items()}
- solver_options, A_ub, A_eq = _check_sparse_inputs(solver_options, A_ub, A_eq)
- # Convert lists to numpy arrays, etc...
- c, A_ub, b_ub, A_eq, b_eq, bounds = _clean_inputs(
- c, A_ub, b_ub, A_eq, b_eq, bounds)
- return c, A_ub, b_ub, A_eq, b_eq, bounds, solver_options
- def _get_Abc(c, c0=0, A_ub=None, b_ub=None, A_eq=None, b_eq=None, bounds=None,
- undo=[]):
- """
- Given a linear programming problem of the form:
- Minimize::
- c @ x
- Subject to::
- A_ub @ x <= b_ub
- A_eq @ x == b_eq
- lb <= x <= ub
- where ``lb = 0`` and ``ub = None`` unless set in ``bounds``.
- Return the problem in standard form:
- Minimize::
- c @ x
- Subject to::
- A @ x == b
- x >= 0
- by adding slack variables and making variable substitutions as necessary.
- Parameters
- ----------
- c : 1D array
- Coefficients of the linear objective function to be minimized.
- Components corresponding with fixed variables have been eliminated.
- c0 : float
- Constant term in objective function due to fixed (and eliminated)
- variables.
- 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. Bounds have been
- tightened where possible.
- undo: list of tuples
- (`index`, `value`) pairs that record the original index and fixed value
- for each variable removed from the problem
- Returns
- -------
- A : 2D array
- 2D array such that ``A`` @ ``x``, gives the values of the equality
- constraints at ``x``.
- b : 1D array
- 1D array of values representing the RHS of each equality constraint
- (row) in A (for standard form problem).
- c : 1D array
- Coefficients of the linear objective function to be minimized (for
- standard form problem).
- c0 : float
- Constant term in objective function due to fixed (and eliminated)
- variables.
- References
- ----------
- .. [9] Bertsimas, Dimitris, and J. Tsitsiklis. "Introduction to linear
- programming." Athena Scientific 1 (1997): 997.
- """
- if sps.issparse(A_eq):
- sparse = True
- A_eq = sps.lil_matrix(A_eq)
- A_ub = sps.lil_matrix(A_ub)
- def hstack(blocks):
- return sps.hstack(blocks, format="lil")
- def vstack(blocks):
- return sps.vstack(blocks, format="lil")
- zeros = sps.lil_matrix
- eye = sps.eye
- else:
- sparse = False
- hstack = np.hstack
- vstack = np.vstack
- zeros = np.zeros
- eye = np.eye
- fixed_x = set()
- if len(undo) > 0:
- # these are indices of variables removed from the problem
- # however, their bounds are still part of the bounds list
- fixed_x = set(undo[0])
- # they are needed elsewhere, but not here
- bounds = [bounds[i] for i in range(len(bounds)) if i not in fixed_x]
- # in retrospect, the standard form of bounds should have been an n x 2
- # array. maybe change it someday.
- # modify problem such that all variables have only non-negativity bounds
- bounds = np.array(bounds)
- lbs = bounds[:, 0]
- ubs = bounds[:, 1]
- m_ub, n_ub = A_ub.shape
- lb_none = np.equal(lbs, None)
- ub_none = np.equal(ubs, None)
- lb_some = np.logical_not(lb_none)
- ub_some = np.logical_not(ub_none)
- # if preprocessing is on, lb == ub can't happen
- # if preprocessing is off, then it would be best to convert that
- # to an equality constraint, but it's tricky to make the other
- # required modifications from inside here.
- # unbounded below: substitute xi = -xi' (unbounded above)
- l_nolb_someub = np.logical_and(lb_none, ub_some)
- i_nolb = np.nonzero(l_nolb_someub)[0]
- lbs[l_nolb_someub], ubs[l_nolb_someub] = (
- -ubs[l_nolb_someub], lbs[l_nolb_someub])
- lb_none = np.equal(lbs, None)
- ub_none = np.equal(ubs, None)
- lb_some = np.logical_not(lb_none)
- ub_some = np.logical_not(ub_none)
- c[i_nolb] *= -1
- if len(i_nolb) > 0:
- if A_ub.shape[0] > 0: # sometimes needed for sparse arrays... weird
- A_ub[:, i_nolb] *= -1
- if A_eq.shape[0] > 0:
- A_eq[:, i_nolb] *= -1
- # upper bound: add inequality constraint
- i_newub = np.nonzero(ub_some)[0]
- ub_newub = ubs[ub_some]
- n_bounds = np.count_nonzero(ub_some)
- A_ub = vstack((A_ub, zeros((n_bounds, A_ub.shape[1]))))
- b_ub = np.concatenate((b_ub, np.zeros(n_bounds)))
- A_ub[range(m_ub, A_ub.shape[0]), i_newub] = 1
- b_ub[m_ub:] = ub_newub
- A1 = vstack((A_ub, A_eq))
- b = np.concatenate((b_ub, b_eq))
- c = np.concatenate((c, np.zeros((A_ub.shape[0],))))
- # unbounded: substitute xi = xi+ + xi-
- l_free = np.logical_and(lb_none, ub_none)
- i_free = np.nonzero(l_free)[0]
- n_free = len(i_free)
- A1 = hstack((A1, zeros((A1.shape[0], n_free))))
- c = np.concatenate((c, np.zeros(n_free)))
- A1[:, range(n_ub, A1.shape[1])] = -A1[:, i_free]
- c[np.arange(n_ub, A1.shape[1])] = -c[i_free]
- # add slack variables
- A2 = vstack([eye(A_ub.shape[0]), zeros((A_eq.shape[0], A_ub.shape[0]))])
- A = hstack([A1, A2])
- # lower bound: substitute xi = xi' + lb
- # now there is a constant term in objective
- i_shift = np.nonzero(lb_some)[0]
- lb_shift = lbs[lb_some].astype(float)
- c0 += np.sum(lb_shift * c[i_shift])
- if sparse:
- b = b.reshape(-1, 1)
- A = A.tocsc()
- b -= (A[:, i_shift] * sps.diags(lb_shift)).sum(axis=1)
- b = b.ravel()
- else:
- b -= (A[:, i_shift] * lb_shift).sum(axis=1)
- return A, b, c, c0
- def _display_summary(message, status, fun, iteration):
- """
- Print the termination summary of the linear program
- Parameters
- ----------
- message : str
- A string descriptor of the exit status of the optimization.
- status : int
- An integer representing the exit status of the optimization::
- 0 : Optimization terminated successfully
- 1 : Iteration limit reached
- 2 : Problem appears to be infeasible
- 3 : Problem appears to be unbounded
- 4 : Serious numerical difficulties encountered
- fun : float
- Value of the objective function.
- iteration : iteration
- The number of iterations performed.
- """
- print(message)
- if status in (0, 1):
- print(" Current function value: {0: <12.6f}".format(fun))
- print(" Iterations: {0:d}".format(iteration))
- def _postsolve(x, c, A_ub=None, b_ub=None, A_eq=None, b_eq=None, bounds=None,
- complete=False, undo=[], tol=1e-8):
- """
- Given solution x to presolved, standard form linear program x, add
- fixed variables back into the problem and undo the variable substitutions
- to get solution to original linear program. Also, calculate the objective
- function value, slack in original upper bound constraints, and residuals
- in original equality constraints.
- Parameters
- ----------
- x : 1D array
- Solution vector to the standard-form problem.
- c : 1D array
- Original 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
- Bounds, as modified in presolve
- complete : bool
- Whether the solution is was determined in presolve (``True`` if so)
- undo: list of tuples
- (`index`, `value`) pairs that record the original index and fixed value
- for each variable removed from the problem
- tol : float
- Termination tolerance; see [1]_ Section 4.5.
- Returns
- -------
- x : 1D array
- Solution vector to original linear programming problem
- fun: float
- optimal objective value for original problem
- slack : 1D array
- The (non-negative) slack in the upper bound constraints, that is,
- ``b_ub - A_ub @ x``
- con : 1D array
- The (nominally zero) residuals of the equality constraints, that is,
- ``b - A_eq @ x``
- lb : 1D array
- The lower bound constraints on the original variables
- ub: 1D array
- The upper bound constraints on the original variables
- """
- # note that all the inputs are the ORIGINAL, unmodified versions
- # no rows, columns have been removed
- # the only exception is bounds; it has been modified
- # we need these modified values to undo the variable substitutions
- # in retrospect, perhaps this could have been simplified if the "undo"
- # variable also contained information for undoing variable substitutions
- n_x = len(c)
- # we don't have to undo variable substitutions for fixed variables that
- # were removed from the problem
- no_adjust = set()
- # if there were variables removed from the problem, add them back into the
- # solution vector
- if len(undo) > 0:
- no_adjust = set(undo[0])
- x = x.tolist()
- for i, val in zip(undo[0], undo[1]):
- x.insert(i, val)
- x = np.array(x)
- # now undo variable substitutions
- # if "complete", problem was solved in presolve; don't do anything here
- if not complete and bounds is not None: # bounds are never none, probably
- n_unbounded = 0
- for i, b in enumerate(bounds):
- if i in no_adjust:
- continue
- lb, ub = b
- if lb is None and ub is None:
- n_unbounded += 1
- x[i] = x[i] - x[n_x + n_unbounded - 1]
- else:
- if lb is None:
- x[i] = ub - x[i]
- else:
- x[i] += lb
- n_x = len(c)
- x = x[:n_x] # all the rest of the variables were artificial
- fun = x.dot(c)
- slack = b_ub - A_ub.dot(x) # report slack for ORIGINAL UB constraints
- # report residuals of ORIGINAL EQ constraints
- con = b_eq - A_eq.dot(x)
- # Patch for bug #8664. Detecting this sort of issue earlier
- # (via abnormalities in the indicators) would be better.
- bounds = np.array(bounds) # again, this should have been the standard form
- lb = bounds[:, 0]
- ub = bounds[:, 1]
- lb[np.equal(lb, None)] = -np.inf
- ub[np.equal(ub, None)] = np.inf
- return x, fun, slack, con, lb, ub
- def _check_result(x, fun, status, slack, con, lb, ub, tol, message):
- """
- Check the validity of the provided solution.
- A valid (optimal) solution satisfies all bounds, all slack variables are
- negative and all equality constraint residuals are strictly non-zero.
- Further, the lower-bounds, upper-bounds, slack and residuals contain
- no nan values.
- Parameters
- ----------
- x : 1D array
- Solution vector to original linear programming problem
- fun: float
- optimal objective value for original problem
- status : int
- An integer representing the exit status of the optimization::
- 0 : Optimization terminated successfully
- 1 : Iteration limit reached
- 2 : Problem appears to be infeasible
- 3 : Problem appears to be unbounded
- 4 : Serious numerical difficulties encountered
- slack : 1D array
- The (non-negative) slack in the upper bound constraints, that is,
- ``b_ub - A_ub @ x``
- con : 1D array
- The (nominally zero) residuals of the equality constraints, that is,
- ``b - A_eq @ x``
- lb : 1D array
- The lower bound constraints on the original variables
- ub: 1D array
- The upper bound constraints on the original variables
- message : str
- A string descriptor of the exit status of the optimization.
- tol : float
- Termination tolerance; see [1]_ Section 4.5.
- Returns
- -------
- status : int
- An integer representing the exit status of the optimization::
- 0 : Optimization terminated successfully
- 1 : Iteration limit reached
- 2 : Problem appears to be infeasible
- 3 : Problem appears to be unbounded
- 4 : Serious numerical difficulties encountered
- message : str
- A string descriptor of the exit status of the optimization.
- """
- # Somewhat arbitrary, but status 5 is very unusual
- tol = np.sqrt(tol) * 10
- contains_nans = (
- np.isnan(x).any()
- or np.isnan(fun)
- or np.isnan(slack).any()
- or np.isnan(con).any()
- )
- if contains_nans:
- is_feasible = False
- else:
- invalid_bounds = (x < lb - tol).any() or (x > ub + tol).any()
- invalid_slack = status != 3 and (slack < -tol).any()
- invalid_con = status != 3 and (np.abs(con) > tol).any()
- is_feasible = not (invalid_bounds or invalid_slack or invalid_con)
- if status == 0 and not is_feasible:
- status = 4
- message = ("The solution does not satisfy the constraints, yet "
- "no errors were raised and there is no certificate of "
- "infeasibility or unboundedness. This is known to occur "
- "if the `presolve` option is False and the problem is "
- "infeasible. If you encounter this under different "
- "circumstances, please submit a bug report. Otherwise, "
- "please enable presolve.")
- elif status == 0 and contains_nans:
- status = 4
- message = ("Numerical difficulties were encountered but no errors "
- "were raised. This is known to occur if the 'presolve' "
- "option is False, 'sparse' is True, and A_eq includes "
- "redundant rows. If you encounter this under different "
- "circumstances, please submit a bug report. Otherwise, "
- "remove linearly dependent equations from your equality "
- "constraints or enable presolve.")
- elif status == 2 and is_feasible:
- # Occurs if the simplex method exits after phase one with a very
- # nearly basic feasible solution. Postsolving can make the solution
- #basic, however, this solution is NOT optimal
- raise ValueError(message)
- return status, message
- def _postprocess(x, c, A_ub=None, b_ub=None, A_eq=None, b_eq=None, bounds=None,
- complete=False, undo=[], status=0, message="", tol=1e-8,
- iteration=None, disp=False):
- """
- Given solution x to presolved, standard form linear program x, add
- fixed variables back into the problem and undo the variable substitutions
- to get solution to original linear program. Also, calculate the objective
- function value, slack in original upper bound constraints, and residuals
- in original equality constraints.
- Parameters
- ----------
- x : 1D array
- Solution vector to the standard-form problem.
- c : 1D array
- Original 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
- Bounds, as modified in presolve
- complete : bool
- Whether the solution is was determined in presolve (``True`` if so)
- undo: list of tuples
- (`index`, `value`) pairs that record the original index and fixed value
- for each variable removed from the problem
- status : int
- An integer representing the exit status of the optimization::
- 0 : Optimization terminated successfully
- 1 : Iteration limit reached
- 2 : Problem appears to be infeasible
- 3 : Problem appears to be unbounded
- 4 : Serious numerical difficulties encountered
- message : str
- A string descriptor of the exit status of the optimization.
- tol : float
- Termination tolerance; see [1]_ Section 4.5.
- Returns
- -------
- x : 1D array
- Solution vector to original linear programming problem
- fun: float
- optimal objective value for original problem
- slack : 1D array
- The (non-negative) slack in the upper bound constraints, that is,
- ``b_ub - A_ub @ x``
- con : 1D array
- The (nominally zero) residuals of the equality constraints, that is,
- ``b - A_eq @ x``
- status : int
- An integer representing the exit status of the optimization::
- 0 : Optimization terminated successfully
- 1 : Iteration limit reached
- 2 : Problem appears to be infeasible
- 3 : Problem appears to be unbounded
- 4 : Serious numerical difficulties encountered
- message : str
- A string descriptor of the exit status of the optimization.
- """
- x, fun, slack, con, lb, ub = _postsolve(
- x, c, A_ub, b_ub, A_eq, b_eq,
- bounds, complete, undo, tol
- )
- status, message = _check_result(
- x, fun, status, slack, con,
- lb, ub, tol, message
- )
- if disp:
- _display_summary(message, status, fun, iteration)
- return x, fun, slack, con, status, message
|