| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351 | 
							- """Linear least squares with bound constraints on independent variables."""
 
- import numpy as np
 
- from numpy.linalg import norm
 
- from scipy.sparse import issparse, csr_matrix
 
- from scipy.sparse.linalg import LinearOperator, lsmr
 
- from scipy.optimize import OptimizeResult
 
- from .common import in_bounds, compute_grad
 
- from .trf_linear import trf_linear
 
- from .bvls import bvls
 
- def prepare_bounds(bounds, n):
 
-     lb, ub = [np.asarray(b, dtype=float) for b in bounds]
 
-     if lb.ndim == 0:
 
-         lb = np.resize(lb, n)
 
-     if ub.ndim == 0:
 
-         ub = np.resize(ub, n)
 
-     return lb, ub
 
- TERMINATION_MESSAGES = {
 
-     -1: "The algorithm was not able to make progress on the last iteration.",
 
-     0: "The maximum number of iterations is exceeded.",
 
-     1: "The first-order optimality measure is less than `tol`.",
 
-     2: "The relative change of the cost function is less than `tol`.",
 
-     3: "The unconstrained solution is optimal."
 
- }
 
- def lsq_linear(A, b, bounds=(-np.inf, np.inf), method='trf', tol=1e-10,
 
-                lsq_solver=None, lsmr_tol=None, max_iter=None,
 
-                verbose=0, *, lsmr_maxiter=None,):
 
-     r"""Solve a linear least-squares problem with bounds on the variables.
 
-     Given a m-by-n design matrix A and a target vector b with m elements,
 
-     `lsq_linear` solves the following optimization problem::
 
-         minimize 0.5 * ||A x - b||**2
 
-         subject to lb <= x <= ub
 
-     This optimization problem is convex, hence a found minimum (if iterations
 
-     have converged) is guaranteed to be global.
 
-     Parameters
 
-     ----------
 
-     A : array_like, sparse matrix of LinearOperator, shape (m, n)
 
-         Design matrix. Can be `scipy.sparse.linalg.LinearOperator`.
 
-     b : array_like, shape (m,)
 
-         Target vector.
 
-     bounds : 2-tuple of array_like, optional
 
-         Lower and upper bounds on independent variables. Defaults to no bounds.
 
-         Each array must have shape (n,) or be a scalar, in the latter
 
-         case a bound will be the same for all variables. Use ``np.inf`` with
 
-         an appropriate sign to disable bounds on all or some variables.
 
-     method : 'trf' or 'bvls', optional
 
-         Method to perform minimization.
 
-             * 'trf' : Trust Region Reflective algorithm adapted for a linear
 
-               least-squares problem. This is an interior-point-like method
 
-               and the required number of iterations is weakly correlated with
 
-               the number of variables.
 
-             * 'bvls' : Bounded-variable least-squares algorithm. This is
 
-               an active set method, which requires the number of iterations
 
-               comparable to the number of variables. Can't be used when `A` is
 
-               sparse or LinearOperator.
 
-         Default is 'trf'.
 
-     tol : float, optional
 
-         Tolerance parameter. The algorithm terminates if a relative change
 
-         of the cost function is less than `tol` on the last iteration.
 
-         Additionally, the first-order optimality measure is considered:
 
-             * ``method='trf'`` terminates if the uniform norm of the gradient,
 
-               scaled to account for the presence of the bounds, is less than
 
-               `tol`.
 
-             * ``method='bvls'`` terminates if Karush-Kuhn-Tucker conditions
 
-               are satisfied within `tol` tolerance.
 
-     lsq_solver : {None, 'exact', 'lsmr'}, optional
 
-         Method of solving unbounded least-squares problems throughout
 
-         iterations:
 
-             * 'exact' : Use dense QR or SVD decomposition approach. Can't be
 
-               used when `A` is sparse or LinearOperator.
 
-             * 'lsmr' : Use `scipy.sparse.linalg.lsmr` iterative procedure
 
-               which requires only matrix-vector product evaluations. Can't
 
-               be used with ``method='bvls'``.
 
-         If None (default), the solver is chosen based on type of `A`.
 
-     lsmr_tol : None, float or 'auto', optional
 
-         Tolerance parameters 'atol' and 'btol' for `scipy.sparse.linalg.lsmr`
 
-         If None (default), it is set to ``1e-2 * tol``. If 'auto', the
 
-         tolerance will be adjusted based on the optimality of the current
 
-         iterate, which can speed up the optimization process, but is not always
 
-         reliable.
 
-     max_iter : None or int, optional
 
-         Maximum number of iterations before termination. If None (default), it
 
-         is set to 100 for ``method='trf'`` or to the number of variables for
 
-         ``method='bvls'`` (not counting iterations for 'bvls' initialization).
 
-     verbose : {0, 1, 2}, optional
 
-         Level of algorithm's verbosity:
 
-             * 0 : work silently (default).
 
-             * 1 : display a termination report.
 
-             * 2 : display progress during iterations.
 
-     lsmr_maxiter : None or int, optional
 
-         Maximum number of iterations for the lsmr least squares solver,
 
-         if it is used (by setting ``lsq_solver='lsmr'``). If None (default), it
 
-         uses lsmr's default of ``min(m, n)`` where ``m`` and ``n`` are the
 
-         number of rows and columns of `A`, respectively. Has no effect if
 
-         ``lsq_solver='exact'``.
 
-     Returns
 
-     -------
 
-     OptimizeResult with the following fields defined:
 
-     x : ndarray, shape (n,)
 
-         Solution found.
 
-     cost : float
 
-         Value of the cost function at the solution.
 
-     fun : ndarray, shape (m,)
 
-         Vector of residuals at the solution.
 
-     optimality : float
 
-         First-order optimality measure. The exact meaning depends on `method`,
 
-         refer to the description of `tol` parameter.
 
-     active_mask : ndarray of int, shape (n,)
 
-         Each component shows whether a corresponding constraint is active
 
-         (that is, whether a variable is at the bound):
 
-             *  0 : a constraint is not active.
 
-             * -1 : a lower bound is active.
 
-             *  1 : an upper bound is active.
 
-         Might be somewhat arbitrary for the `trf` method as it generates a
 
-         sequence of strictly feasible iterates and active_mask is determined
 
-         within a tolerance threshold.
 
-     unbounded_sol : tuple
 
-         Unbounded least squares solution tuple returned by the least squares
 
-         solver (set with `lsq_solver` option). If `lsq_solver` is not set or is
 
-         set to ``'exact'``, the tuple contains an ndarray of shape (n,) with
 
-         the unbounded solution, an ndarray with the sum of squared residuals,
 
-         an int with the rank of `A`, and an ndarray with the singular values
 
-         of `A` (see NumPy's ``linalg.lstsq`` for more information). If
 
-         `lsq_solver` is set to ``'lsmr'``, the tuple contains an ndarray of
 
-         shape (n,) with the unbounded solution, an int with the exit code,
 
-         an int with the number of iterations, and five floats with
 
-         various norms and the condition number of `A` (see SciPy's
 
-         ``sparse.linalg.lsmr`` for more information). This output can be
 
-         useful for determining the convergence of the least squares solver,
 
-         particularly the iterative ``'lsmr'`` solver. The unbounded least
 
-         squares problem is to minimize ``0.5 * ||A x - b||**2``.
 
-     nit : int
 
-         Number of iterations. Zero if the unconstrained solution is optimal.
 
-     status : int
 
-         Reason for algorithm termination:
 
-             * -1 : the algorithm was not able to make progress on the last
 
-               iteration.
 
-             *  0 : the maximum number of iterations is exceeded.
 
-             *  1 : the first-order optimality measure is less than `tol`.
 
-             *  2 : the relative change of the cost function is less than `tol`.
 
-             *  3 : the unconstrained solution is optimal.
 
-     message : str
 
-         Verbal description of the termination reason.
 
-     success : bool
 
-         True if one of the convergence criteria is satisfied (`status` > 0).
 
-     See Also
 
-     --------
 
-     nnls : Linear least squares with non-negativity constraint.
 
-     least_squares : Nonlinear least squares with bounds on the variables.
 
-     Notes
 
-     -----
 
-     The algorithm first computes the unconstrained least-squares solution by
 
-     `numpy.linalg.lstsq` or `scipy.sparse.linalg.lsmr` depending on
 
-     `lsq_solver`. This solution is returned as optimal if it lies within the
 
-     bounds.
 
-     Method 'trf' runs the adaptation of the algorithm described in [STIR]_ for
 
-     a linear least-squares problem. The iterations are essentially the same as
 
-     in the nonlinear least-squares algorithm, but as the quadratic function
 
-     model is always accurate, we don't need to track or modify the radius of
 
-     a trust region. The line search (backtracking) is used as a safety net
 
-     when a selected step does not decrease the cost function. Read more
 
-     detailed description of the algorithm in `scipy.optimize.least_squares`.
 
-     Method 'bvls' runs a Python implementation of the algorithm described in
 
-     [BVLS]_. The algorithm maintains active and free sets of variables, on
 
-     each iteration chooses a new variable to move from the active set to the
 
-     free set and then solves the unconstrained least-squares problem on free
 
-     variables. This algorithm is guaranteed to give an accurate solution
 
-     eventually, but may require up to n iterations for a problem with n
 
-     variables. Additionally, an ad-hoc initialization procedure is
 
-     implemented, that determines which variables to set free or active
 
-     initially. It takes some number of iterations before actual BVLS starts,
 
-     but can significantly reduce the number of further iterations.
 
-     References
 
-     ----------
 
-     .. [STIR] M. A. Branch, T. F. Coleman, and Y. Li, "A Subspace, Interior,
 
-               and Conjugate Gradient Method for Large-Scale Bound-Constrained
 
-               Minimization Problems," SIAM Journal on Scientific Computing,
 
-               Vol. 21, Number 1, pp 1-23, 1999.
 
-     .. [BVLS] P. B. Start and R. L. Parker, "Bounded-Variable Least-Squares:
 
-               an Algorithm and Applications", Computational Statistics, 10,
 
-               129-141, 1995.
 
-     Examples
 
-     --------
 
-     In this example, a problem with a large sparse matrix and bounds on the
 
-     variables is solved.
 
-     >>> import numpy as np
 
-     >>> from scipy.sparse import rand
 
-     >>> from scipy.optimize import lsq_linear
 
-     >>> rng = np.random.default_rng()
 
-     ...
 
-     >>> m = 20000
 
-     >>> n = 10000
 
-     ...
 
-     >>> A = rand(m, n, density=1e-4, random_state=rng)
 
-     >>> b = rng.standard_normal(m)
 
-     ...
 
-     >>> lb = rng.standard_normal(n)
 
-     >>> ub = lb + 1
 
-     ...
 
-     >>> res = lsq_linear(A, b, bounds=(lb, ub), lsmr_tol='auto', verbose=1)
 
-     # may vary
 
-     The relative change of the cost function is less than `tol`.
 
-     Number of iterations 16, initial cost 1.5039e+04, final cost 1.1112e+04,
 
-     first-order optimality 4.66e-08.
 
-     """
 
-     if method not in ['trf', 'bvls']:
 
-         raise ValueError("`method` must be 'trf' or 'bvls'")
 
-     if lsq_solver not in [None, 'exact', 'lsmr']:
 
-         raise ValueError("`solver` must be None, 'exact' or 'lsmr'.")
 
-     if verbose not in [0, 1, 2]:
 
-         raise ValueError("`verbose` must be in [0, 1, 2].")
 
-     if issparse(A):
 
-         A = csr_matrix(A)
 
-     elif not isinstance(A, LinearOperator):
 
-         A = np.atleast_2d(np.asarray(A))
 
-     if method == 'bvls':
 
-         if lsq_solver == 'lsmr':
 
-             raise ValueError("method='bvls' can't be used with "
 
-                              "lsq_solver='lsmr'")
 
-         if not isinstance(A, np.ndarray):
 
-             raise ValueError("method='bvls' can't be used with `A` being "
 
-                              "sparse or LinearOperator.")
 
-     if lsq_solver is None:
 
-         if isinstance(A, np.ndarray):
 
-             lsq_solver = 'exact'
 
-         else:
 
-             lsq_solver = 'lsmr'
 
-     elif lsq_solver == 'exact' and not isinstance(A, np.ndarray):
 
-         raise ValueError("`exact` solver can't be used when `A` is "
 
-                          "sparse or LinearOperator.")
 
-     if len(A.shape) != 2:  # No ndim for LinearOperator.
 
-         raise ValueError("`A` must have at most 2 dimensions.")
 
-     if len(bounds) != 2:
 
-         raise ValueError("`bounds` must contain 2 elements.")
 
-     if max_iter is not None and max_iter <= 0:
 
-         raise ValueError("`max_iter` must be None or positive integer.")
 
-     m, n = A.shape
 
-     b = np.atleast_1d(b)
 
-     if b.ndim != 1:
 
-         raise ValueError("`b` must have at most 1 dimension.")
 
-     if b.size != m:
 
-         raise ValueError("Inconsistent shapes between `A` and `b`.")
 
-     lb, ub = prepare_bounds(bounds, n)
 
-     if lb.shape != (n,) and ub.shape != (n,):
 
-         raise ValueError("Bounds have wrong shape.")
 
-     if np.any(lb >= ub):
 
-         raise ValueError("Each lower bound must be strictly less than each "
 
-                          "upper bound.")
 
-     if lsmr_maxiter is not None and lsmr_maxiter < 1:
 
-         raise ValueError("`lsmr_maxiter` must be None or positive integer.")
 
-     if not ((isinstance(lsmr_tol, float) and lsmr_tol > 0) or
 
-             lsmr_tol in ('auto', None)):
 
-         raise ValueError("`lsmr_tol` must be None, 'auto', or positive float.")
 
-     if lsq_solver == 'exact':
 
-         unbd_lsq = np.linalg.lstsq(A, b, rcond=-1)
 
-     elif lsq_solver == 'lsmr':
 
-         first_lsmr_tol = lsmr_tol  # tol of first call to lsmr
 
-         if lsmr_tol is None or lsmr_tol == 'auto':
 
-             first_lsmr_tol = 1e-2 * tol  # default if lsmr_tol not defined
 
-         unbd_lsq = lsmr(A, b, maxiter=lsmr_maxiter,
 
-                         atol=first_lsmr_tol, btol=first_lsmr_tol)
 
-     x_lsq = unbd_lsq[0]  # extract the solution from the least squares solver
 
-     if in_bounds(x_lsq, lb, ub):
 
-         r = A @ x_lsq - b
 
-         cost = 0.5 * np.dot(r, r)
 
-         termination_status = 3
 
-         termination_message = TERMINATION_MESSAGES[termination_status]
 
-         g = compute_grad(A, r)
 
-         g_norm = norm(g, ord=np.inf)
 
-         if verbose > 0:
 
-             print(termination_message)
 
-             print("Final cost {0:.4e}, first-order optimality {1:.2e}"
 
-                   .format(cost, g_norm))
 
-         return OptimizeResult(
 
-             x=x_lsq, fun=r, cost=cost, optimality=g_norm,
 
-             active_mask=np.zeros(n), unbounded_sol=unbd_lsq,
 
-             nit=0, status=termination_status,
 
-             message=termination_message, success=True)
 
-     if method == 'trf':
 
-         res = trf_linear(A, b, x_lsq, lb, ub, tol, lsq_solver, lsmr_tol,
 
-                          max_iter, verbose, lsmr_maxiter=lsmr_maxiter)
 
-     elif method == 'bvls':
 
-         res = bvls(A, b, x_lsq, lb, ub, tol, max_iter, verbose)
 
-     res.unbounded_sol = unbd_lsq
 
-     res.message = TERMINATION_MESSAGES[res.status]
 
-     res.success = res.status > 0
 
-     if verbose > 0:
 
-         print(res.message)
 
-         print("Number of iterations {0}, initial cost {1:.4e}, "
 
-               "final cost {2:.4e}, first-order optimality {3:.2e}."
 
-               .format(res.nit, res.initial_cost, res.cost, res.optimality))
 
-     del res.initial_cost
 
-     return res
 
 
  |