123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128 |
- """Interior-point method for linear programming
- The *interior-point* method uses the primal-dual path following algorithm
- outlined in [1]_. This algorithm supports sparse constraint matrices and
- is typically faster than the simplex methods, especially for large, sparse
- problems. Note, however, that the solution returned may be slightly less
- accurate than those of the simplex methods and will not, in general,
- correspond with a vertex of the polytope defined by the constraints.
- .. versionadded:: 1.0.0
- References
- ----------
- .. [1] Andersen, Erling D., and Knud D. Andersen. "The MOSEK interior point
- optimizer for linear programming: an implementation of the
- homogeneous algorithm." High performance optimization. Springer US,
- 2000. 197-232.
- """
- # Author: Matt Haberland
- import numpy as np
- import scipy as sp
- import scipy.sparse as sps
- from warnings import warn
- from scipy.linalg import LinAlgError
- from ._optimize import OptimizeWarning, OptimizeResult, _check_unknown_options
- from ._linprog_util import _postsolve
- has_umfpack = True
- has_cholmod = True
- try:
- import sksparse
- from sksparse.cholmod import cholesky as cholmod
- from sksparse.cholmod import analyze as cholmod_analyze
- except ImportError:
- has_cholmod = False
- try:
- import scikits.umfpack # test whether to use factorized
- except ImportError:
- has_umfpack = False
- def _get_solver(M, sparse=False, lstsq=False, sym_pos=True,
- cholesky=True, permc_spec='MMD_AT_PLUS_A'):
- """
- Given solver options, return a handle to the appropriate linear system
- solver.
- Parameters
- ----------
- M : 2-D array
- As defined in [4] Equation 8.31
- sparse : bool (default = False)
- True if the system to be solved is sparse. This is typically set
- True when the original ``A_ub`` and ``A_eq`` arrays are sparse.
- lstsq : bool (default = False)
- True if the system is ill-conditioned and/or (nearly) singular and
- thus a more robust least-squares solver is desired. This is sometimes
- needed as the solution is approached.
- sym_pos : bool (default = True)
- True if the system matrix is symmetric positive definite
- Sometimes this needs to be set false as the solution is approached,
- even when the system should be symmetric positive definite, due to
- numerical difficulties.
- cholesky : bool (default = True)
- True if the system is to be solved by Cholesky, rather than LU,
- decomposition. This is typically faster unless the problem is very
- small or prone to numerical difficulties.
- permc_spec : str (default = 'MMD_AT_PLUS_A')
- Sparsity preservation strategy used by SuperLU. Acceptable values are:
- - ``NATURAL``: natural ordering.
- - ``MMD_ATA``: minimum degree ordering on the structure of A^T A.
- - ``MMD_AT_PLUS_A``: minimum degree ordering on the structure of A^T+A.
- - ``COLAMD``: approximate minimum degree column ordering.
- See SuperLU documentation.
- Returns
- -------
- solve : function
- Handle to the appropriate solver function
- """
- try:
- if sparse:
- if lstsq:
- def solve(r, sym_pos=False):
- return sps.linalg.lsqr(M, r)[0]
- elif cholesky:
- try:
- # Will raise an exception in the first call,
- # or when the matrix changes due to a new problem
- _get_solver.cholmod_factor.cholesky_inplace(M)
- except Exception:
- _get_solver.cholmod_factor = cholmod_analyze(M)
- _get_solver.cholmod_factor.cholesky_inplace(M)
- solve = _get_solver.cholmod_factor
- else:
- if has_umfpack and sym_pos:
- solve = sps.linalg.factorized(M)
- else: # factorized doesn't pass permc_spec
- solve = sps.linalg.splu(M, permc_spec=permc_spec).solve
- else:
- if lstsq: # sometimes necessary as solution is approached
- def solve(r):
- return sp.linalg.lstsq(M, r)[0]
- elif cholesky:
- L = sp.linalg.cho_factor(M)
- def solve(r):
- return sp.linalg.cho_solve(L, r)
- else:
- # this seems to cache the matrix factorization, so solving
- # with multiple right hand sides is much faster
- def solve(r, sym_pos=sym_pos):
- if sym_pos:
- return sp.linalg.solve(M, r, assume_a="pos")
- else:
- return sp.linalg.solve(M, r)
- # There are many things that can go wrong here, and it's hard to say
- # what all of them are. It doesn't really matter: if the matrix can't be
- # factorized, return None. get_solver will be called again with different
- # inputs, and a new routine will try to factorize the matrix.
- except KeyboardInterrupt:
- raise
- except Exception:
- return None
- return solve
- def _get_delta(A, b, c, x, y, z, tau, kappa, gamma, eta, sparse=False,
- lstsq=False, sym_pos=True, cholesky=True, pc=True, ip=False,
- permc_spec='MMD_AT_PLUS_A'):
- """
- Given standard form problem defined by ``A``, ``b``, and ``c``;
- current variable estimates ``x``, ``y``, ``z``, ``tau``, and ``kappa``;
- algorithmic parameters ``gamma and ``eta;
- and options ``sparse``, ``lstsq``, ``sym_pos``, ``cholesky``, ``pc``
- (predictor-corrector), and ``ip`` (initial point improvement),
- get the search direction for increments to the variable estimates.
- Parameters
- ----------
- As defined in [4], except:
- sparse : bool
- True if the system to be solved is sparse. This is typically set
- True when the original ``A_ub`` and ``A_eq`` arrays are sparse.
- lstsq : bool
- True if the system is ill-conditioned and/or (nearly) singular and
- thus a more robust least-squares solver is desired. This is sometimes
- needed as the solution is approached.
- sym_pos : bool
- True if the system matrix is symmetric positive definite
- Sometimes this needs to be set false as the solution is approached,
- even when the system should be symmetric positive definite, due to
- numerical difficulties.
- cholesky : bool
- True if the system is to be solved by Cholesky, rather than LU,
- decomposition. This is typically faster unless the problem is very
- small or prone to numerical difficulties.
- pc : bool
- True if the predictor-corrector method of Mehrota is to be used. This
- is almost always (if not always) beneficial. Even though it requires
- the solution of an additional linear system, the factorization
- is typically (implicitly) reused so solution is efficient, and the
- number of algorithm iterations is typically reduced.
- ip : bool
- True if the improved initial point suggestion due to [4] section 4.3
- is desired. It's unclear whether this is beneficial.
- permc_spec : str (default = 'MMD_AT_PLUS_A')
- (Has effect only with ``sparse = True``, ``lstsq = False``, ``sym_pos =
- True``.) A matrix is factorized in each iteration of the algorithm.
- This option specifies how to permute the columns of the matrix for
- sparsity preservation. Acceptable values are:
- - ``NATURAL``: natural ordering.
- - ``MMD_ATA``: minimum degree ordering on the structure of A^T A.
- - ``MMD_AT_PLUS_A``: minimum degree ordering on the structure of A^T+A.
- - ``COLAMD``: approximate minimum degree column ordering.
- This option can impact the convergence of the
- interior point algorithm; test different values to determine which
- performs best for your problem. For more information, refer to
- ``scipy.sparse.linalg.splu``.
- Returns
- -------
- Search directions as defined in [4]
- References
- ----------
- .. [4] Andersen, Erling D., and Knud D. Andersen. "The MOSEK interior point
- optimizer for linear programming: an implementation of the
- homogeneous algorithm." High performance optimization. Springer US,
- 2000. 197-232.
- """
- if A.shape[0] == 0:
- # If there are no constraints, some solvers fail (understandably)
- # rather than returning empty solution. This gets the job done.
- sparse, lstsq, sym_pos, cholesky = False, False, True, False
- n_x = len(x)
- # [4] Equation 8.8
- r_P = b * tau - A.dot(x)
- r_D = c * tau - A.T.dot(y) - z
- r_G = c.dot(x) - b.transpose().dot(y) + kappa
- mu = (x.dot(z) + tau * kappa) / (n_x + 1)
- # Assemble M from [4] Equation 8.31
- Dinv = x / z
- if sparse:
- M = A.dot(sps.diags(Dinv, 0, format="csc").dot(A.T))
- else:
- M = A.dot(Dinv.reshape(-1, 1) * A.T)
- solve = _get_solver(M, sparse, lstsq, sym_pos, cholesky, permc_spec)
- # pc: "predictor-corrector" [4] Section 4.1
- # In development this option could be turned off
- # but it always seems to improve performance substantially
- n_corrections = 1 if pc else 0
- i = 0
- alpha, d_x, d_z, d_tau, d_kappa = 0, 0, 0, 0, 0
- while i <= n_corrections:
- # Reference [4] Eq. 8.6
- rhatp = eta(gamma) * r_P
- rhatd = eta(gamma) * r_D
- rhatg = eta(gamma) * r_G
- # Reference [4] Eq. 8.7
- rhatxs = gamma * mu - x * z
- rhattk = gamma * mu - tau * kappa
- if i == 1:
- if ip: # if the correction is to get "initial point"
- # Reference [4] Eq. 8.23
- rhatxs = ((1 - alpha) * gamma * mu -
- x * z - alpha**2 * d_x * d_z)
- rhattk = ((1 - alpha) * gamma * mu -
- tau * kappa -
- alpha**2 * d_tau * d_kappa)
- else: # if the correction is for "predictor-corrector"
- # Reference [4] Eq. 8.13
- rhatxs -= d_x * d_z
- rhattk -= d_tau * d_kappa
- # sometimes numerical difficulties arise as the solution is approached
- # this loop tries to solve the equations using a sequence of functions
- # for solve. For dense systems, the order is:
- # 1. scipy.linalg.cho_factor/scipy.linalg.cho_solve,
- # 2. scipy.linalg.solve w/ sym_pos = True,
- # 3. scipy.linalg.solve w/ sym_pos = False, and if all else fails
- # 4. scipy.linalg.lstsq
- # For sparse systems, the order is:
- # 1. sksparse.cholmod.cholesky (if available)
- # 2. scipy.sparse.linalg.factorized (if umfpack available)
- # 3. scipy.sparse.linalg.splu
- # 4. scipy.sparse.linalg.lsqr
- solved = False
- while not solved:
- try:
- # [4] Equation 8.28
- p, q = _sym_solve(Dinv, A, c, b, solve)
- # [4] Equation 8.29
- u, v = _sym_solve(Dinv, A, rhatd -
- (1 / x) * rhatxs, rhatp, solve)
- if np.any(np.isnan(p)) or np.any(np.isnan(q)):
- raise LinAlgError
- solved = True
- except (LinAlgError, ValueError, TypeError) as e:
- # Usually this doesn't happen. If it does, it happens when
- # there are redundant constraints or when approaching the
- # solution. If so, change solver.
- if cholesky:
- cholesky = False
- warn(
- "Solving system with option 'cholesky':True "
- "failed. It is normal for this to happen "
- "occasionally, especially as the solution is "
- "approached. However, if you see this frequently, "
- "consider setting option 'cholesky' to False.",
- OptimizeWarning, stacklevel=5)
- elif sym_pos:
- sym_pos = False
- warn(
- "Solving system with option 'sym_pos':True "
- "failed. It is normal for this to happen "
- "occasionally, especially as the solution is "
- "approached. However, if you see this frequently, "
- "consider setting option 'sym_pos' to False.",
- OptimizeWarning, stacklevel=5)
- elif not lstsq:
- lstsq = True
- warn(
- "Solving system with option 'sym_pos':False "
- "failed. This may happen occasionally, "
- "especially as the solution is "
- "approached. However, if you see this frequently, "
- "your problem may be numerically challenging. "
- "If you cannot improve the formulation, consider "
- "setting 'lstsq' to True. Consider also setting "
- "`presolve` to True, if it is not already.",
- OptimizeWarning, stacklevel=5)
- else:
- raise e
- solve = _get_solver(M, sparse, lstsq, sym_pos,
- cholesky, permc_spec)
- # [4] Results after 8.29
- d_tau = ((rhatg + 1 / tau * rhattk - (-c.dot(u) + b.dot(v))) /
- (1 / tau * kappa + (-c.dot(p) + b.dot(q))))
- d_x = u + p * d_tau
- d_y = v + q * d_tau
- # [4] Relations between after 8.25 and 8.26
- d_z = (1 / x) * (rhatxs - z * d_x)
- d_kappa = 1 / tau * (rhattk - kappa * d_tau)
- # [4] 8.12 and "Let alpha be the maximal possible step..." before 8.23
- alpha = _get_step(x, d_x, z, d_z, tau, d_tau, kappa, d_kappa, 1)
- if ip: # initial point - see [4] 4.4
- gamma = 10
- else: # predictor-corrector, [4] definition after 8.12
- beta1 = 0.1 # [4] pg. 220 (Table 8.1)
- gamma = (1 - alpha)**2 * min(beta1, (1 - alpha))
- i += 1
- return d_x, d_y, d_z, d_tau, d_kappa
- def _sym_solve(Dinv, A, r1, r2, solve):
- """
- An implementation of [4] equation 8.31 and 8.32
- References
- ----------
- .. [4] Andersen, Erling D., and Knud D. Andersen. "The MOSEK interior point
- optimizer for linear programming: an implementation of the
- homogeneous algorithm." High performance optimization. Springer US,
- 2000. 197-232.
- """
- # [4] 8.31
- r = r2 + A.dot(Dinv * r1)
- v = solve(r)
- # [4] 8.32
- u = Dinv * (A.T.dot(v) - r1)
- return u, v
- def _get_step(x, d_x, z, d_z, tau, d_tau, kappa, d_kappa, alpha0):
- """
- An implementation of [4] equation 8.21
- References
- ----------
- .. [4] Andersen, Erling D., and Knud D. Andersen. "The MOSEK interior point
- optimizer for linear programming: an implementation of the
- homogeneous algorithm." High performance optimization. Springer US,
- 2000. 197-232.
- """
- # [4] 4.3 Equation 8.21, ignoring 8.20 requirement
- # same step is taken in primal and dual spaces
- # alpha0 is basically beta3 from [4] Table 8.1, but instead of beta3
- # the value 1 is used in Mehrota corrector and initial point correction
- i_x = d_x < 0
- i_z = d_z < 0
- alpha_x = alpha0 * np.min(x[i_x] / -d_x[i_x]) if np.any(i_x) else 1
- alpha_tau = alpha0 * tau / -d_tau if d_tau < 0 else 1
- alpha_z = alpha0 * np.min(z[i_z] / -d_z[i_z]) if np.any(i_z) else 1
- alpha_kappa = alpha0 * kappa / -d_kappa if d_kappa < 0 else 1
- alpha = np.min([1, alpha_x, alpha_tau, alpha_z, alpha_kappa])
- return alpha
- def _get_message(status):
- """
- Given problem status code, return a more detailed message.
- Parameters
- ----------
- 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
- Returns
- -------
- message : str
- A string descriptor of the exit status of the optimization.
- """
- messages = (
- ["Optimization terminated successfully.",
- "The iteration limit was reached before the algorithm converged.",
- "The algorithm terminated successfully and determined that the "
- "problem is infeasible.",
- "The algorithm terminated successfully and determined that the "
- "problem is unbounded.",
- "Numerical difficulties were encountered before the problem "
- "converged. Please check your problem formulation for errors, "
- "independence of linear equality constraints, and reasonable "
- "scaling and matrix condition numbers. If you continue to "
- "encounter this error, please submit a bug report."
- ])
- return messages[status]
- def _do_step(x, y, z, tau, kappa, d_x, d_y, d_z, d_tau, d_kappa, alpha):
- """
- An implementation of [4] Equation 8.9
- References
- ----------
- .. [4] Andersen, Erling D., and Knud D. Andersen. "The MOSEK interior point
- optimizer for linear programming: an implementation of the
- homogeneous algorithm." High performance optimization. Springer US,
- 2000. 197-232.
- """
- x = x + alpha * d_x
- tau = tau + alpha * d_tau
- z = z + alpha * d_z
- kappa = kappa + alpha * d_kappa
- y = y + alpha * d_y
- return x, y, z, tau, kappa
- def _get_blind_start(shape):
- """
- Return the starting point from [4] 4.4
- References
- ----------
- .. [4] Andersen, Erling D., and Knud D. Andersen. "The MOSEK interior point
- optimizer for linear programming: an implementation of the
- homogeneous algorithm." High performance optimization. Springer US,
- 2000. 197-232.
- """
- m, n = shape
- x0 = np.ones(n)
- y0 = np.zeros(m)
- z0 = np.ones(n)
- tau0 = 1
- kappa0 = 1
- return x0, y0, z0, tau0, kappa0
- def _indicators(A, b, c, c0, x, y, z, tau, kappa):
- """
- Implementation of several equations from [4] used as indicators of
- the status of optimization.
- References
- ----------
- .. [4] Andersen, Erling D., and Knud D. Andersen. "The MOSEK interior point
- optimizer for linear programming: an implementation of the
- homogeneous algorithm." High performance optimization. Springer US,
- 2000. 197-232.
- """
- # residuals for termination are relative to initial values
- x0, y0, z0, tau0, kappa0 = _get_blind_start(A.shape)
- # See [4], Section 4 - The Homogeneous Algorithm, Equation 8.8
- def r_p(x, tau):
- return b * tau - A.dot(x)
- def r_d(y, z, tau):
- return c * tau - A.T.dot(y) - z
- def r_g(x, y, kappa):
- return kappa + c.dot(x) - b.dot(y)
- # np.dot unpacks if they are arrays of size one
- def mu(x, tau, z, kappa):
- return (x.dot(z) + np.dot(tau, kappa)) / (len(x) + 1)
- obj = c.dot(x / tau) + c0
- def norm(a):
- return np.linalg.norm(a)
- # See [4], Section 4.5 - The Stopping Criteria
- r_p0 = r_p(x0, tau0)
- r_d0 = r_d(y0, z0, tau0)
- r_g0 = r_g(x0, y0, kappa0)
- mu_0 = mu(x0, tau0, z0, kappa0)
- rho_A = norm(c.T.dot(x) - b.T.dot(y)) / (tau + norm(b.T.dot(y)))
- rho_p = norm(r_p(x, tau)) / max(1, norm(r_p0))
- rho_d = norm(r_d(y, z, tau)) / max(1, norm(r_d0))
- rho_g = norm(r_g(x, y, kappa)) / max(1, norm(r_g0))
- rho_mu = mu(x, tau, z, kappa) / mu_0
- return rho_p, rho_d, rho_A, rho_g, rho_mu, obj
- def _display_iter(rho_p, rho_d, rho_g, alpha, rho_mu, obj, header=False):
- """
- Print indicators of optimization status to the console.
- Parameters
- ----------
- rho_p : float
- The (normalized) primal feasibility, see [4] 4.5
- rho_d : float
- The (normalized) dual feasibility, see [4] 4.5
- rho_g : float
- The (normalized) duality gap, see [4] 4.5
- alpha : float
- The step size, see [4] 4.3
- rho_mu : float
- The (normalized) path parameter, see [4] 4.5
- obj : float
- The objective function value of the current iterate
- header : bool
- True if a header is to be printed
- References
- ----------
- .. [4] Andersen, Erling D., and Knud D. Andersen. "The MOSEK interior point
- optimizer for linear programming: an implementation of the
- homogeneous algorithm." High performance optimization. Springer US,
- 2000. 197-232.
- """
- if header:
- print("Primal Feasibility ",
- "Dual Feasibility ",
- "Duality Gap ",
- "Step ",
- "Path Parameter ",
- "Objective ")
- # no clue why this works
- fmt = '{0:<20.13}{1:<20.13}{2:<20.13}{3:<17.13}{4:<20.13}{5:<20.13}'
- print(fmt.format(
- float(rho_p),
- float(rho_d),
- float(rho_g),
- alpha if isinstance(alpha, str) else float(alpha),
- float(rho_mu),
- float(obj)))
- def _ip_hsd(A, b, c, c0, alpha0, beta, maxiter, disp, tol, sparse, lstsq,
- sym_pos, cholesky, pc, ip, permc_spec, callback, postsolve_args):
- r"""
- Solve a linear programming problem in standard form:
- Minimize::
- c @ x
- Subject to::
- A @ x == b
- x >= 0
- using the interior point method of [4].
- Parameters
- ----------
- A : 2-D array
- 2-D array such that ``A @ x``, gives the values of the equality
- constraints at ``x``.
- b : 1-D array
- 1-D array of values representing the RHS of each equality constraint
- (row) in ``A`` (for standard form problem).
- c : 1-D 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. (Purely for display.)
- alpha0 : float
- The maximal step size for Mehrota's predictor-corrector search
- direction; see :math:`\beta_3`of [4] Table 8.1
- beta : float
- The desired reduction of the path parameter :math:`\mu` (see [6]_)
- maxiter : int
- The maximum number of iterations of the algorithm.
- disp : bool
- Set to ``True`` if indicators of optimization status are to be printed
- to the console each iteration.
- tol : float
- Termination tolerance; see [4]_ Section 4.5.
- sparse : bool
- Set to ``True`` if the problem is to be treated as sparse. However,
- the inputs ``A_eq`` and ``A_ub`` should nonetheless be provided as
- (dense) arrays rather than sparse matrices.
- lstsq : bool
- Set to ``True`` if the problem is expected to be very poorly
- conditioned. This should always be left as ``False`` unless severe
- numerical difficulties are frequently encountered, and a better option
- would be to improve the formulation of the problem.
- sym_pos : bool
- Leave ``True`` if the problem is expected to yield a well conditioned
- symmetric positive definite normal equation matrix (almost always).
- cholesky : bool
- Set to ``True`` if the normal equations are to be solved by explicit
- Cholesky decomposition followed by explicit forward/backward
- substitution. This is typically faster for moderate, dense problems
- that are numerically well-behaved.
- pc : bool
- Leave ``True`` if the predictor-corrector method of Mehrota is to be
- used. This is almost always (if not always) beneficial.
- ip : bool
- Set to ``True`` if the improved initial point suggestion due to [4]_
- Section 4.3 is desired. It's unclear whether this is beneficial.
- permc_spec : str (default = 'MMD_AT_PLUS_A')
- (Has effect only with ``sparse = True``, ``lstsq = False``, ``sym_pos =
- True``.) A matrix is factorized in each iteration of the algorithm.
- This option specifies how to permute the columns of the matrix for
- sparsity preservation. Acceptable values are:
- - ``NATURAL``: natural ordering.
- - ``MMD_ATA``: minimum degree ordering on the structure of A^T A.
- - ``MMD_AT_PLUS_A``: minimum degree ordering on the structure of A^T+A.
- - ``COLAMD``: approximate minimum degree column ordering.
- This option can impact the convergence of the
- interior point algorithm; test different values to determine which
- performs best for your problem. For more information, refer to
- ``scipy.sparse.linalg.splu``.
- callback : callable, optional
- If a callback function is provided, it will be called within each
- iteration of the algorithm. The callback function must accept a single
- `scipy.optimize.OptimizeResult` consisting of the following fields:
- x : 1-D array
- Current solution vector
- fun : float
- Current value of the objective function
- success : bool
- True only when an algorithm has completed successfully,
- so this is always False as the callback function is called
- only while the algorithm is still iterating.
- slack : 1-D array
- The values of the slack variables. Each slack variable
- corresponds to an inequality constraint. If the slack is zero,
- the corresponding constraint is active.
- con : 1-D array
- The (nominally zero) residuals of the equality constraints,
- that is, ``b - A_eq @ x``
- phase : int
- The phase of the algorithm being executed. This is always
- 1 for the interior-point method because it has only one phase.
- status : int
- For revised simplex, this is always 0 because if a different
- status is detected, the algorithm terminates.
- nit : int
- The number of iterations performed.
- message : str
- A string descriptor of the exit status of the optimization.
- postsolve_args : tuple
- Data needed by _postsolve to convert the solution to the standard-form
- problem into the solution to the original problem.
- Returns
- -------
- x_hat : float
- Solution vector (for standard form 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.
- iteration : int
- The number of iterations taken to solve the problem
- References
- ----------
- .. [4] Andersen, Erling D., and Knud D. Andersen. "The MOSEK interior point
- optimizer for linear programming: an implementation of the
- homogeneous algorithm." High performance optimization. Springer US,
- 2000. 197-232.
- .. [6] Freund, Robert M. "Primal-Dual Interior-Point Methods for Linear
- Programming based on Newton's Method." Unpublished Course Notes,
- March 2004. Available 2/25/2017 at:
- https://ocw.mit.edu/courses/sloan-school-of-management/15-084j-nonlinear-programming-spring-2004/lecture-notes/lec14_int_pt_mthd.pdf
- """
- iteration = 0
- # default initial point
- x, y, z, tau, kappa = _get_blind_start(A.shape)
- # first iteration is special improvement of initial point
- ip = ip if pc else False
- # [4] 4.5
- rho_p, rho_d, rho_A, rho_g, rho_mu, obj = _indicators(
- A, b, c, c0, x, y, z, tau, kappa)
- go = rho_p > tol or rho_d > tol or rho_A > tol # we might get lucky : )
- if disp:
- _display_iter(rho_p, rho_d, rho_g, "-", rho_mu, obj, header=True)
- if callback is not None:
- x_o, fun, slack, con = _postsolve(x/tau, postsolve_args)
- res = OptimizeResult({'x': x_o, 'fun': fun, 'slack': slack,
- 'con': con, 'nit': iteration, 'phase': 1,
- 'complete': False, 'status': 0,
- 'message': "", 'success': False})
- callback(res)
- status = 0
- message = "Optimization terminated successfully."
- if sparse:
- A = sps.csc_matrix(A)
- A.T = A.transpose() # A.T is defined for sparse matrices but is slow
- # Redefine it to avoid calculating again
- # This is fine as long as A doesn't change
- while go:
- iteration += 1
- if ip: # initial point
- # [4] Section 4.4
- gamma = 1
- def eta(g):
- return 1
- else:
- # gamma = 0 in predictor step according to [4] 4.1
- # if predictor/corrector is off, use mean of complementarity [6]
- # 5.1 / [4] Below Figure 10-4
- gamma = 0 if pc else beta * np.mean(z * x)
- # [4] Section 4.1
- def eta(g=gamma):
- return 1 - g
- try:
- # Solve [4] 8.6 and 8.7/8.13/8.23
- d_x, d_y, d_z, d_tau, d_kappa = _get_delta(
- A, b, c, x, y, z, tau, kappa, gamma, eta,
- sparse, lstsq, sym_pos, cholesky, pc, ip, permc_spec)
- if ip: # initial point
- # [4] 4.4
- # Formula after 8.23 takes a full step regardless if this will
- # take it negative
- alpha = 1.0
- x, y, z, tau, kappa = _do_step(
- x, y, z, tau, kappa, d_x, d_y,
- d_z, d_tau, d_kappa, alpha)
- x[x < 1] = 1
- z[z < 1] = 1
- tau = max(1, tau)
- kappa = max(1, kappa)
- ip = False # done with initial point
- else:
- # [4] Section 4.3
- alpha = _get_step(x, d_x, z, d_z, tau,
- d_tau, kappa, d_kappa, alpha0)
- # [4] Equation 8.9
- x, y, z, tau, kappa = _do_step(
- x, y, z, tau, kappa, d_x, d_y, d_z, d_tau, d_kappa, alpha)
- except (LinAlgError, FloatingPointError,
- ValueError, ZeroDivisionError):
- # this can happen when sparse solver is used and presolve
- # is turned off. Also observed ValueError in AppVeyor Python 3.6
- # Win32 build (PR #8676). I've never seen it otherwise.
- status = 4
- message = _get_message(status)
- break
- # [4] 4.5
- rho_p, rho_d, rho_A, rho_g, rho_mu, obj = _indicators(
- A, b, c, c0, x, y, z, tau, kappa)
- go = rho_p > tol or rho_d > tol or rho_A > tol
- if disp:
- _display_iter(rho_p, rho_d, rho_g, alpha, rho_mu, obj)
- if callback is not None:
- x_o, fun, slack, con = _postsolve(x/tau, postsolve_args)
- res = OptimizeResult({'x': x_o, 'fun': fun, 'slack': slack,
- 'con': con, 'nit': iteration, 'phase': 1,
- 'complete': False, 'status': 0,
- 'message': "", 'success': False})
- callback(res)
- # [4] 4.5
- inf1 = (rho_p < tol and rho_d < tol and rho_g < tol and tau < tol *
- max(1, kappa))
- inf2 = rho_mu < tol and tau < tol * min(1, kappa)
- if inf1 or inf2:
- # [4] Lemma 8.4 / Theorem 8.3
- if b.transpose().dot(y) > tol:
- status = 2
- else: # elif c.T.dot(x) < tol: ? Probably not necessary.
- status = 3
- message = _get_message(status)
- break
- elif iteration >= maxiter:
- status = 1
- message = _get_message(status)
- break
- x_hat = x / tau
- # [4] Statement after Theorem 8.2
- return x_hat, status, message, iteration
- def _linprog_ip(c, c0, A, b, callback, postsolve_args, maxiter=1000, tol=1e-8,
- disp=False, alpha0=.99995, beta=0.1, sparse=False, lstsq=False,
- sym_pos=True, cholesky=None, pc=True, ip=False,
- permc_spec='MMD_AT_PLUS_A', **unknown_options):
- r"""
- Minimize a linear objective function subject to linear
- equality and non-negativity constraints using the interior point method
- of [4]_. Linear programming is intended to solve problems
- of the following form:
- Minimize::
- c @ x
- Subject to::
- A @ x == b
- x >= 0
- User-facing documentation is in _linprog_doc.py.
- Parameters
- ----------
- c : 1-D array
- Coefficients of the linear objective function to be minimized.
- c0 : float
- Constant term in objective function due to fixed (and eliminated)
- variables. (Purely for display.)
- A : 2-D array
- 2-D array such that ``A @ x``, gives the values of the equality
- constraints at ``x``.
- b : 1-D array
- 1-D array of values representing the right hand side of each equality
- constraint (row) in ``A``.
- callback : callable, optional
- Callback function to be executed once per iteration.
- postsolve_args : tuple
- Data needed by _postsolve to convert the solution to the standard-form
- problem into the solution to the original problem.
- Options
- -------
- maxiter : int (default = 1000)
- The maximum number of iterations of the algorithm.
- tol : float (default = 1e-8)
- Termination tolerance to be used for all termination criteria;
- see [4]_ Section 4.5.
- disp : bool (default = False)
- Set to ``True`` if indicators of optimization status are to be printed
- to the console each iteration.
- alpha0 : float (default = 0.99995)
- The maximal step size for Mehrota's predictor-corrector search
- direction; see :math:`\beta_{3}` of [4]_ Table 8.1.
- beta : float (default = 0.1)
- The desired reduction of the path parameter :math:`\mu` (see [6]_)
- when Mehrota's predictor-corrector is not in use (uncommon).
- sparse : bool (default = False)
- Set to ``True`` if the problem is to be treated as sparse after
- presolve. If either ``A_eq`` or ``A_ub`` is a sparse matrix,
- this option will automatically be set ``True``, and the problem
- will be treated as sparse even during presolve. If your constraint
- matrices contain mostly zeros and the problem is not very small (less
- than about 100 constraints or variables), consider setting ``True``
- or providing ``A_eq`` and ``A_ub`` as sparse matrices.
- lstsq : bool (default = False)
- Set to ``True`` if the problem is expected to be very poorly
- conditioned. This should always be left ``False`` unless severe
- numerical difficulties are encountered. Leave this at the default
- unless you receive a warning message suggesting otherwise.
- sym_pos : bool (default = True)
- Leave ``True`` if the problem is expected to yield a well conditioned
- symmetric positive definite normal equation matrix
- (almost always). Leave this at the default unless you receive
- a warning message suggesting otherwise.
- cholesky : bool (default = True)
- Set to ``True`` if the normal equations are to be solved by explicit
- Cholesky decomposition followed by explicit forward/backward
- substitution. This is typically faster for problems
- that are numerically well-behaved.
- pc : bool (default = True)
- Leave ``True`` if the predictor-corrector method of Mehrota is to be
- used. This is almost always (if not always) beneficial.
- ip : bool (default = False)
- Set to ``True`` if the improved initial point suggestion due to [4]_
- Section 4.3 is desired. Whether this is beneficial or not
- depends on the problem.
- permc_spec : str (default = 'MMD_AT_PLUS_A')
- (Has effect only with ``sparse = True``, ``lstsq = False``, ``sym_pos =
- True``, and no SuiteSparse.)
- A matrix is factorized in each iteration of the algorithm.
- This option specifies how to permute the columns of the matrix for
- sparsity preservation. Acceptable values are:
- - ``NATURAL``: natural ordering.
- - ``MMD_ATA``: minimum degree ordering on the structure of A^T A.
- - ``MMD_AT_PLUS_A``: minimum degree ordering on the structure of A^T+A.
- - ``COLAMD``: approximate minimum degree column ordering.
- This option can impact the convergence of the
- interior point algorithm; test different values to determine which
- performs best for your problem. For more information, refer to
- ``scipy.sparse.linalg.splu``.
- unknown_options : dict
- Optional arguments not used by this particular solver. If
- `unknown_options` is non-empty a warning is issued listing all
- unused options.
- Returns
- -------
- x : 1-D array
- Solution vector.
- 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.
- iteration : int
- The number of iterations taken to solve the problem.
- Notes
- -----
- This method implements the algorithm outlined in [4]_ with ideas from [8]_
- and a structure inspired by the simpler methods of [6]_.
- The primal-dual path following method begins with initial 'guesses' of
- the primal and dual variables of the standard form problem and iteratively
- attempts to solve the (nonlinear) Karush-Kuhn-Tucker conditions for the
- problem with a gradually reduced logarithmic barrier term added to the
- objective. This particular implementation uses a homogeneous self-dual
- formulation, which provides certificates of infeasibility or unboundedness
- where applicable.
- The default initial point for the primal and dual variables is that
- defined in [4]_ Section 4.4 Equation 8.22. Optionally (by setting initial
- point option ``ip=True``), an alternate (potentially improved) starting
- point can be calculated according to the additional recommendations of
- [4]_ Section 4.4.
- A search direction is calculated using the predictor-corrector method
- (single correction) proposed by Mehrota and detailed in [4]_ Section 4.1.
- (A potential improvement would be to implement the method of multiple
- corrections described in [4]_ Section 4.2.) In practice, this is
- accomplished by solving the normal equations, [4]_ Section 5.1 Equations
- 8.31 and 8.32, derived from the Newton equations [4]_ Section 5 Equations
- 8.25 (compare to [4]_ Section 4 Equations 8.6-8.8). The advantage of
- solving the normal equations rather than 8.25 directly is that the
- matrices involved are symmetric positive definite, so Cholesky
- decomposition can be used rather than the more expensive LU factorization.
- With default options, the solver used to perform the factorization depends
- on third-party software availability and the conditioning of the problem.
- For dense problems, solvers are tried in the following order:
- 1. ``scipy.linalg.cho_factor``
- 2. ``scipy.linalg.solve`` with option ``sym_pos=True``
- 3. ``scipy.linalg.solve`` with option ``sym_pos=False``
- 4. ``scipy.linalg.lstsq``
- For sparse problems:
- 1. ``sksparse.cholmod.cholesky`` (if scikit-sparse and SuiteSparse are installed)
- 2. ``scipy.sparse.linalg.factorized`` (if scikit-umfpack and SuiteSparse are installed)
- 3. ``scipy.sparse.linalg.splu`` (which uses SuperLU distributed with SciPy)
- 4. ``scipy.sparse.linalg.lsqr``
- If the solver fails for any reason, successively more robust (but slower)
- solvers are attempted in the order indicated. Attempting, failing, and
- re-starting factorization can be time consuming, so if the problem is
- numerically challenging, options can be set to bypass solvers that are
- failing. Setting ``cholesky=False`` skips to solver 2,
- ``sym_pos=False`` skips to solver 3, and ``lstsq=True`` skips
- to solver 4 for both sparse and dense problems.
- Potential improvements for combatting issues associated with dense
- columns in otherwise sparse problems are outlined in [4]_ Section 5.3 and
- [10]_ Section 4.1-4.2; the latter also discusses the alleviation of
- accuracy issues associated with the substitution approach to free
- variables.
- After calculating the search direction, the maximum possible step size
- that does not activate the non-negativity constraints is calculated, and
- the smaller of this step size and unity is applied (as in [4]_ Section
- 4.1.) [4]_ Section 4.3 suggests improvements for choosing the step size.
- The new point is tested according to the termination conditions of [4]_
- Section 4.5. The same tolerance, which can be set using the ``tol`` option,
- is used for all checks. (A potential improvement would be to expose
- the different tolerances to be set independently.) If optimality,
- unboundedness, or infeasibility is detected, the solve procedure
- terminates; otherwise it repeats.
- The expected problem formulation differs between the top level ``linprog``
- module and the method specific solvers. The method specific solvers expect a
- problem in standard form:
- Minimize::
- c @ x
- Subject to::
- A @ x == b
- x >= 0
- Whereas the top level ``linprog`` module expects a problem of 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``.
- The original problem contains equality, upper-bound and variable constraints
- whereas the method specific solver requires equality constraints and
- variable non-negativity.
- ``linprog`` module converts the original problem to standard form by
- converting the simple bounds to upper bound constraints, introducing
- non-negative slack variables for inequality constraints, and expressing
- unbounded variables as the difference between two non-negative variables.
- References
- ----------
- .. [4] Andersen, Erling D., and Knud D. Andersen. "The MOSEK interior point
- optimizer for linear programming: an implementation of the
- homogeneous algorithm." High performance optimization. Springer US,
- 2000. 197-232.
- .. [6] Freund, Robert M. "Primal-Dual Interior-Point Methods for Linear
- Programming based on Newton's Method." Unpublished Course Notes,
- March 2004. Available 2/25/2017 at
- https://ocw.mit.edu/courses/sloan-school-of-management/15-084j-nonlinear-programming-spring-2004/lecture-notes/lec14_int_pt_mthd.pdf
- .. [8] Andersen, Erling D., and Knud D. Andersen. "Presolving in linear
- programming." Mathematical Programming 71.2 (1995): 221-245.
- .. [9] Bertsimas, Dimitris, and J. Tsitsiklis. "Introduction to linear
- programming." Athena Scientific 1 (1997): 997.
- .. [10] Andersen, Erling D., et al. Implementation of interior point methods
- for large scale linear programming. HEC/Universite de Geneve, 1996.
- """
- _check_unknown_options(unknown_options)
- # These should be warnings, not errors
- if (cholesky or cholesky is None) and sparse and not has_cholmod:
- if cholesky:
- warn("Sparse cholesky is only available with scikit-sparse. "
- "Setting `cholesky = False`",
- OptimizeWarning, stacklevel=3)
- cholesky = False
- if sparse and lstsq:
- warn("Option combination 'sparse':True and 'lstsq':True "
- "is not recommended.",
- OptimizeWarning, stacklevel=3)
- if lstsq and cholesky:
- warn("Invalid option combination 'lstsq':True "
- "and 'cholesky':True; option 'cholesky' has no effect when "
- "'lstsq' is set True.",
- OptimizeWarning, stacklevel=3)
- valid_permc_spec = ('NATURAL', 'MMD_ATA', 'MMD_AT_PLUS_A', 'COLAMD')
- if permc_spec.upper() not in valid_permc_spec:
- warn("Invalid permc_spec option: '" + str(permc_spec) + "'. "
- "Acceptable values are 'NATURAL', 'MMD_ATA', 'MMD_AT_PLUS_A', "
- "and 'COLAMD'. Reverting to default.",
- OptimizeWarning, stacklevel=3)
- permc_spec = 'MMD_AT_PLUS_A'
- # This can be an error
- if not sym_pos and cholesky:
- raise ValueError(
- "Invalid option combination 'sym_pos':False "
- "and 'cholesky':True: Cholesky decomposition is only possible "
- "for symmetric positive definite matrices.")
- cholesky = cholesky or (cholesky is None and sym_pos and not lstsq)
- x, status, message, iteration = _ip_hsd(A, b, c, c0, alpha0, beta,
- maxiter, disp, tol, sparse,
- lstsq, sym_pos, cholesky,
- pc, ip, permc_spec, callback,
- postsolve_args)
- return x, status, message, iteration
|