| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051 |
- import warnings
- import numpy as np
- from scipy.sparse.linalg._interface import LinearOperator
- from .utils import make_system
- from scipy.linalg import get_lapack_funcs
- __all__ = ['bicg', 'bicgstab', 'cg', 'cgs', 'gmres', 'qmr']
- def _get_atol_rtol(name, b_norm, atol=0., rtol=1e-5):
- """
- A helper function to handle tolerance normalization
- """
- if atol == 'legacy' or atol is None or atol < 0:
- msg = (f"'scipy.sparse.linalg.{name}' called with invalid `atol`={atol}; "
- "if set, `atol` must be a real, non-negative number.")
- raise ValueError(msg)
- atol = max(float(atol), float(rtol) * float(b_norm))
- return atol, rtol
- def bicg(A, b, x0=None, *, rtol=1e-5, atol=0., maxiter=None, M=None, callback=None):
- """
- Solve ``Ax = b`` with the BIConjugate Gradient method.
- Parameters
- ----------
- A : {sparse array, ndarray, LinearOperator}
- The real or complex N-by-N matrix of the linear system.
- Alternatively, `A` can be a linear operator which can
- produce ``Ax`` and ``A^T x`` using, e.g.,
- ``scipy.sparse.linalg.LinearOperator``.
- b : ndarray
- Right hand side of the linear system. Has shape (N,) or (N,1).
- x0 : ndarray
- Starting guess for the solution.
- rtol, atol : float, optional
- Parameters for the convergence test. For convergence,
- ``norm(b - A @ x) <= max(rtol*norm(b), atol)`` should be satisfied.
- The default is ``atol=0.`` and ``rtol=1e-5``.
- maxiter : integer
- Maximum number of iterations. Iteration will stop after maxiter
- steps even if the specified tolerance has not been achieved.
- M : {sparse array, ndarray, LinearOperator}
- Preconditioner for `A`. It should approximate the
- inverse of `A` (see Notes). Effective preconditioning dramatically improves the
- rate of convergence, which implies that fewer iterations are needed
- to reach a given error tolerance.
- callback : function
- User-supplied function to call after each iteration. It is called
- as ``callback(xk)``, where ``xk`` is the current solution vector.
- Returns
- -------
- x : ndarray
- The converged solution.
- info : integer
- Provides convergence information:
- 0 : successful exit
- >0 : convergence to tolerance not achieved, number of iterations
- <0 : parameter breakdown
- Notes
- -----
- The preconditioner `M` should be a matrix such that ``M @ A`` has a smaller
- condition number than `A`, see [1]_ .
- References
- ----------
- .. [1] "Preconditioner", Wikipedia,
- https://en.wikipedia.org/wiki/Preconditioner
- .. [2] "Biconjugate gradient method", Wikipedia,
- https://en.wikipedia.org/wiki/Biconjugate_gradient_method
- Examples
- --------
- >>> import numpy as np
- >>> from scipy.sparse import csc_array
- >>> from scipy.sparse.linalg import bicg
- >>> A = csc_array([[3, 2, 0], [1, -1, 0], [0, 5, 1.]])
- >>> b = np.array([2., 4., -1.])
- >>> x, exitCode = bicg(A, b, atol=1e-5)
- >>> print(exitCode) # 0 indicates successful convergence
- 0
- >>> np.allclose(A.dot(x), b)
- True
- """
- A, M, x, b = make_system(A, M, x0, b)
- bnrm2 = np.linalg.norm(b)
- atol, _ = _get_atol_rtol('bicg', bnrm2, atol, rtol)
- if bnrm2 == 0:
- return b, 0
- n = len(b)
- dotprod = np.vdot if np.iscomplexobj(x) else np.dot
- if maxiter is None:
- maxiter = n*10
- matvec, rmatvec = A.matvec, A.rmatvec
- psolve, rpsolve = M.matvec, M.rmatvec
- rhotol = np.finfo(x.dtype.char).eps**2
- # Dummy values to initialize vars, silence linter warnings
- rho_prev, p, ptilde = None, None, None
- r = b - matvec(x) if x.any() else b.copy()
- rtilde = r.copy()
- for iteration in range(maxiter):
- if np.linalg.norm(r) < atol: # Are we done?
- return x, 0
- z = psolve(r)
- ztilde = rpsolve(rtilde)
- # order matters in this dot product
- rho_cur = dotprod(rtilde, z)
- if np.abs(rho_cur) < rhotol: # Breakdown case
- return x, -10
- if iteration > 0:
- beta = rho_cur / rho_prev
- p *= beta
- p += z
- ptilde *= beta.conj()
- ptilde += ztilde
- else: # First spin
- p = z.copy()
- ptilde = ztilde.copy()
- q = matvec(p)
- qtilde = rmatvec(ptilde)
- rv = dotprod(ptilde, q)
- if rv == 0:
- return x, -11
- alpha = rho_cur / rv
- x += alpha*p
- r -= alpha*q
- rtilde -= alpha.conj()*qtilde
- rho_prev = rho_cur
- if callback:
- callback(x)
- else: # for loop exhausted
- # Return incomplete progress
- return x, maxiter
- def bicgstab(A, b, x0=None, *, rtol=1e-5, atol=0., maxiter=None, M=None,
- callback=None):
- """
- Solve ``Ax = b`` with the BIConjugate Gradient STABilized method.
- Parameters
- ----------
- A : {sparse array, ndarray, LinearOperator}
- The real or complex N-by-N matrix of the linear system.
- Alternatively, `A` can be a linear operator which can
- produce ``Ax`` and ``A^T x`` using, e.g.,
- ``scipy.sparse.linalg.LinearOperator``.
- b : ndarray
- Right hand side of the linear system. Has shape (N,) or (N,1).
- x0 : ndarray
- Starting guess for the solution.
- rtol, atol : float, optional
- Parameters for the convergence test. For convergence,
- ``norm(b - A @ x) <= max(rtol*norm(b), atol)`` should be satisfied.
- The default is ``atol=0.`` and ``rtol=1e-5``.
- maxiter : integer
- Maximum number of iterations. Iteration will stop after maxiter
- steps even if the specified tolerance has not been achieved.
- M : {sparse array, ndarray, LinearOperator}
- Preconditioner for `A`. It should approximate the
- inverse of `A` (see Notes). Effective preconditioning dramatically improves the
- rate of convergence, which implies that fewer iterations are needed
- to reach a given error tolerance.
- callback : function
- User-supplied function to call after each iteration. It is called
- as ``callback(xk)``, where ``xk`` is the current solution vector.
- Returns
- -------
- x : ndarray
- The converged solution.
- info : integer
- Provides convergence information:
- 0 : successful exit
- >0 : convergence to tolerance not achieved, number of iterations
- <0 : parameter breakdown
- Notes
- -----
- The preconditioner `M` should be a matrix such that ``M @ A`` has a smaller
- condition number than `A`, see [1]_ .
- References
- ----------
- .. [1] "Preconditioner", Wikipedia,
- https://en.wikipedia.org/wiki/Preconditioner
- .. [2] "Biconjugate gradient stabilized method",
- Wikipedia, https://en.wikipedia.org/wiki/Biconjugate_gradient_stabilized_method
- Examples
- --------
- >>> import numpy as np
- >>> from scipy.sparse import csc_array
- >>> from scipy.sparse.linalg import bicgstab
- >>> R = np.array([[4, 2, 0, 1],
- ... [3, 0, 0, 2],
- ... [0, 1, 1, 1],
- ... [0, 2, 1, 0]])
- >>> A = csc_array(R)
- >>> b = np.array([-1, -0.5, -1, 2])
- >>> x, exit_code = bicgstab(A, b, atol=1e-5)
- >>> print(exit_code) # 0 indicates successful convergence
- 0
- >>> np.allclose(A.dot(x), b)
- True
- """
- A, M, x, b = make_system(A, M, x0, b)
- bnrm2 = np.linalg.norm(b)
- atol, _ = _get_atol_rtol('bicgstab', bnrm2, atol, rtol)
- if bnrm2 == 0:
- return b, 0
- n = len(b)
- dotprod = np.vdot if np.iscomplexobj(x) else np.dot
- if maxiter is None:
- maxiter = n*10
- matvec = A.matvec
- psolve = M.matvec
- # These values make no sense but coming from original Fortran code
- # sqrt might have been meant instead.
- rhotol = np.finfo(x.dtype.char).eps**2
- omegatol = rhotol
- # Dummy values to initialize vars, silence linter warnings
- rho_prev, omega, alpha, p, v = None, None, None, None, None
- r = b - matvec(x) if x.any() else b.copy()
- rtilde = r.copy()
- for iteration in range(maxiter):
- if np.linalg.norm(r) < atol: # Are we done?
- return x, 0
- rho = dotprod(rtilde, r)
- if np.abs(rho) < rhotol: # rho breakdown
- return x, -10
- if iteration > 0:
- if np.abs(omega) < omegatol: # omega breakdown
- return x, -11
- beta = (rho / rho_prev) * (alpha / omega)
- p -= omega*v
- p *= beta
- p += r
- else: # First spin
- s = np.empty_like(r)
- p = r.copy()
- phat = psolve(p)
- v = matvec(phat)
- rv = dotprod(rtilde, v)
- if rv == 0:
- return x, -11
- alpha = rho / rv
- r -= alpha*v
- s[:] = r[:]
- if np.linalg.norm(s) < atol:
- x += alpha*phat
- return x, 0
- shat = psolve(s)
- t = matvec(shat)
- omega = dotprod(t, s) / dotprod(t, t)
- x += alpha*phat
- x += omega*shat
- r -= omega*t
- rho_prev = rho
- if callback:
- callback(x)
- else: # for loop exhausted
- # Return incomplete progress
- return x, maxiter
- def cg(A, b, x0=None, *, rtol=1e-5, atol=0., maxiter=None, M=None, callback=None):
- """
- Solve ``Ax = b`` with the Conjugate Gradient method, for a symmetric,
- positive-definite `A`.
- Parameters
- ----------
- A : {sparse array, ndarray, LinearOperator}
- The real or complex N-by-N matrix of the linear system.
- `A` must represent a hermitian, positive definite matrix.
- Alternatively, `A` can be a linear operator which can
- produce ``Ax`` using, e.g.,
- ``scipy.sparse.linalg.LinearOperator``.
- b : ndarray
- Right hand side of the linear system. Has shape (N,) or (N,1).
- x0 : ndarray
- Starting guess for the solution.
- rtol, atol : float, optional
- Parameters for the convergence test. For convergence,
- ``norm(b - A @ x) <= max(rtol*norm(b), atol)`` should be satisfied.
- The default is ``atol=0.`` and ``rtol=1e-5``.
- maxiter : integer
- Maximum number of iterations. Iteration will stop after maxiter
- steps even if the specified tolerance has not been achieved.
- M : {sparse array, ndarray, LinearOperator}
- Preconditioner for `A`. `M` must represent a hermitian, positive definite
- matrix. It should approximate the inverse of `A` (see Notes).
- Effective preconditioning dramatically improves the
- rate of convergence, which implies that fewer iterations are needed
- to reach a given error tolerance.
- callback : function
- User-supplied function to call after each iteration. It is called
- as ``callback(xk)``, where ``xk`` is the current solution vector.
- Returns
- -------
- x : ndarray
- The converged solution.
- info : integer
- Provides convergence information:
- 0 : successful exit
- >0 : convergence to tolerance not achieved, number of iterations
- Notes
- -----
- The preconditioner `M` should be a matrix such that ``M @ A`` has a smaller
- condition number than `A`, see [2]_.
- References
- ----------
- .. [1] "Conjugate Gradient Method, Wikipedia,
- https://en.wikipedia.org/wiki/Conjugate_gradient_method
- .. [2] "Preconditioner",
- Wikipedia, https://en.wikipedia.org/wiki/Preconditioner
- Examples
- --------
- >>> import numpy as np
- >>> from scipy.sparse import csc_array
- >>> from scipy.sparse.linalg import cg
- >>> P = np.array([[4, 0, 1, 0],
- ... [0, 5, 0, 0],
- ... [1, 0, 3, 2],
- ... [0, 0, 2, 4]])
- >>> A = csc_array(P)
- >>> b = np.array([-1, -0.5, -1, 2])
- >>> x, exit_code = cg(A, b, atol=1e-5)
- >>> print(exit_code) # 0 indicates successful convergence
- 0
- >>> np.allclose(A.dot(x), b)
- True
- """
- A, M, x, b = make_system(A, M, x0, b)
- bnrm2 = np.linalg.norm(b)
- atol, _ = _get_atol_rtol('cg', bnrm2, atol, rtol)
- if bnrm2 == 0:
- return b, 0
- n = len(b)
- if maxiter is None:
- maxiter = n*10
- dotprod = np.vdot if np.iscomplexobj(x) else np.dot
- matvec = A.matvec
- psolve = M.matvec
- r = b - matvec(x) if x.any() else b.copy()
- # Dummy value to initialize var, silences warnings
- rho_prev, p = None, None
- for iteration in range(maxiter):
- if np.linalg.norm(r) < atol: # Are we done?
- return x, 0
- z = psolve(r)
- rho_cur = dotprod(r, z)
- if iteration > 0:
- beta = rho_cur / rho_prev
- p *= beta
- p += z
- else: # First spin
- p = np.empty_like(r)
- p[:] = z[:]
- q = matvec(p)
- alpha = rho_cur / dotprod(p, q)
- x += alpha*p
- r -= alpha*q
- rho_prev = rho_cur
- if callback:
- callback(x)
- else: # for loop exhausted
- # Return incomplete progress
- return x, maxiter
- def cgs(A, b, x0=None, *, rtol=1e-5, atol=0., maxiter=None, M=None, callback=None):
- """
- Solve ``Ax = b`` with the Conjugate Gradient Squared method.
- Parameters
- ----------
- A : {sparse array, ndarray, LinearOperator}
- The real-valued N-by-N matrix of the linear system.
- Alternatively, `A` can be a linear operator which can
- produce ``Ax`` using, e.g.,
- ``scipy.sparse.linalg.LinearOperator``.
- b : ndarray
- Right hand side of the linear system. Has shape (N,) or (N,1).
- x0 : ndarray
- Starting guess for the solution.
- rtol, atol : float, optional
- Parameters for the convergence test. For convergence,
- ``norm(b - A @ x) <= max(rtol*norm(b), atol)`` should be satisfied.
- The default is ``atol=0.`` and ``rtol=1e-5``.
- maxiter : integer
- Maximum number of iterations. Iteration will stop after maxiter
- steps even if the specified tolerance has not been achieved.
- M : {sparse array, ndarray, LinearOperator}
- Preconditioner for ``A``. It should approximate the
- inverse of `A` (see Notes). Effective preconditioning dramatically improves the
- rate of convergence, which implies that fewer iterations are needed
- to reach a given error tolerance.
- callback : function
- User-supplied function to call after each iteration. It is called
- as ``callback(xk)``, where ``xk`` is the current solution vector.
- Returns
- -------
- x : ndarray
- The converged solution.
- info : integer
- Provides convergence information:
- 0 : successful exit
- >0 : convergence to tolerance not achieved, number of iterations
- <0 : parameter breakdown
- Notes
- -----
- The preconditioner `M` should be a matrix such that ``M @ A`` has a smaller
- condition number than `A`, see [1]_.
- References
- ----------
- .. [1] "Preconditioner", Wikipedia,
- https://en.wikipedia.org/wiki/Preconditioner
- .. [2] "Conjugate gradient squared", Wikipedia,
- https://en.wikipedia.org/wiki/Conjugate_gradient_squared_method
- Examples
- --------
- >>> import numpy as np
- >>> from scipy.sparse import csc_array
- >>> from scipy.sparse.linalg import cgs
- >>> R = np.array([[4, 2, 0, 1],
- ... [3, 0, 0, 2],
- ... [0, 1, 1, 1],
- ... [0, 2, 1, 0]])
- >>> A = csc_array(R)
- >>> b = np.array([-1, -0.5, -1, 2])
- >>> x, exit_code = cgs(A, b)
- >>> print(exit_code) # 0 indicates successful convergence
- 0
- >>> np.allclose(A.dot(x), b)
- True
- """
- A, M, x, b = make_system(A, M, x0, b)
- bnrm2 = np.linalg.norm(b)
- atol, _ = _get_atol_rtol('cgs', bnrm2, atol, rtol)
- if bnrm2 == 0:
- return b, 0
- n = len(b)
- dotprod = np.vdot if np.iscomplexobj(x) else np.dot
- if maxiter is None:
- maxiter = n*10
- matvec = A.matvec
- psolve = M.matvec
- rhotol = np.finfo(x.dtype.char).eps**2
- r = b - matvec(x) if x.any() else b.copy()
- rtilde = r.copy()
- bnorm = np.linalg.norm(b)
- if bnorm == 0:
- bnorm = 1
- # Dummy values to initialize vars, silence linter warnings
- rho_prev, p, u, q = None, None, None, None
- for iteration in range(maxiter):
- rnorm = np.linalg.norm(r)
- if rnorm < atol: # Are we done?
- return x, 0
- rho_cur = dotprod(rtilde, r)
- if np.abs(rho_cur) < rhotol: # Breakdown case
- return x, -10
- if iteration > 0:
- beta = rho_cur / rho_prev
- # u = r + beta * q
- # p = u + beta * (q + beta * p);
- u[:] = r[:]
- u += beta*q
- p *= beta
- p += q
- p *= beta
- p += u
- else: # First spin
- p = r.copy()
- u = r.copy()
- q = np.empty_like(r)
- phat = psolve(p)
- vhat = matvec(phat)
- rv = dotprod(rtilde, vhat)
- if rv == 0: # Dot product breakdown
- return x, -11
- alpha = rho_cur / rv
- q[:] = u[:]
- q -= alpha*vhat
- uhat = psolve(u + q)
- x += alpha*uhat
- # Due to numerical error build-up the actual residual is computed
- # instead of the following two lines that were in the original
- # FORTRAN templates, still using a single matvec.
- # qhat = matvec(uhat)
- # r -= alpha*qhat
- r = b - matvec(x)
- rho_prev = rho_cur
- if callback:
- callback(x)
- else: # for loop exhausted
- # Return incomplete progress
- return x, maxiter
- def gmres(A, b, x0=None, *, rtol=1e-5, atol=0., restart=None, maxiter=None, M=None,
- callback=None, callback_type=None):
- """
- Solve ``Ax = b`` with the Generalized Minimal RESidual method.
- Parameters
- ----------
- A : {sparse array, ndarray, LinearOperator}
- The real or complex N-by-N matrix of the linear system.
- Alternatively, `A` can be a linear operator which can
- produce ``Ax`` using, e.g.,
- ``scipy.sparse.linalg.LinearOperator``.
- b : ndarray
- Right hand side of the linear system. Has shape (N,) or (N,1).
- x0 : ndarray
- Starting guess for the solution (a vector of zeros by default).
- atol, rtol : float
- Parameters for the convergence test. For convergence,
- ``norm(b - A @ x) <= max(rtol*norm(b), atol)`` should be satisfied.
- The default is ``atol=0.`` and ``rtol=1e-5``.
- restart : int, optional
- Number of iterations between restarts. Larger values increase
- iteration cost, but may be necessary for convergence.
- If omitted, ``min(20, n)`` is used.
- maxiter : int, optional
- Maximum number of iterations (restart cycles). Iteration will stop
- after maxiter steps even if the specified tolerance has not been
- achieved. See `callback_type`.
- M : {sparse array, ndarray, LinearOperator}
- Inverse of the preconditioner of `A`. `M` should approximate the
- inverse of `A` and be easy to solve for (see Notes). Effective
- preconditioning dramatically improves the rate of convergence,
- which implies that fewer iterations are needed to reach a given
- error tolerance. By default, no preconditioner is used.
- In this implementation, left preconditioning is used,
- and the preconditioned residual is minimized. However, the final
- convergence is tested with respect to the ``b - A @ x`` residual.
- callback : function
- User-supplied function to call after each iteration. It is called
- as ``callback(args)``, where ``args`` are selected by `callback_type`.
- callback_type : {'x', 'pr_norm', 'legacy'}, optional
- Callback function argument requested:
- - ``x``: current iterate (ndarray), called on every restart
- - ``pr_norm``: relative (preconditioned) residual norm (float),
- called on every inner iteration
- - ``legacy`` (default): same as ``pr_norm``, but also changes the
- meaning of `maxiter` to count inner iterations instead of restart
- cycles.
- This keyword has no effect if `callback` is not set.
- Returns
- -------
- x : ndarray
- The converged solution.
- info : int
- Provides convergence information:
- 0 : successful exit
- >0 : convergence to tolerance not achieved, number of iterations
- See Also
- --------
- LinearOperator
- Notes
- -----
- A preconditioner, P, is chosen such that P is close to A but easy to solve
- for. The preconditioner parameter required by this routine is
- ``M = P^-1``. The inverse should preferably not be calculated
- explicitly. Rather, use the following template to produce M::
- # Construct a linear operator that computes P^-1 @ x.
- import scipy.sparse.linalg as spla
- M_x = lambda x: spla.spsolve(P, x)
- M = spla.LinearOperator((n, n), M_x)
- Examples
- --------
- >>> import numpy as np
- >>> from scipy.sparse import csc_array
- >>> from scipy.sparse.linalg import gmres
- >>> A = csc_array([[3, 2, 0], [1, -1, 0], [0, 5, 1]], dtype=float)
- >>> b = np.array([2, 4, -1], dtype=float)
- >>> x, exitCode = gmres(A, b, atol=1e-5)
- >>> print(exitCode) # 0 indicates successful convergence
- 0
- >>> np.allclose(A.dot(x), b)
- True
- """
- if callback is not None and callback_type is None:
- # Warn about 'callback_type' semantic changes.
- # Probably should be removed only in far future, Scipy 2.0 or so.
- msg = ("scipy.sparse.linalg.gmres called without specifying "
- "`callback_type`. The default value will be changed in"
- " a future release. For compatibility, specify a value "
- "for `callback_type` explicitly, e.g., "
- "``gmres(..., callback_type='pr_norm')``, or to retain the "
- "old behavior ``gmres(..., callback_type='legacy')``"
- )
- warnings.warn(msg, category=DeprecationWarning, stacklevel=3)
- if callback_type is None:
- callback_type = 'legacy'
- if callback_type not in ('x', 'pr_norm', 'legacy'):
- raise ValueError(f"Unknown callback_type: {callback_type!r}")
- if callback is None:
- callback_type = None
- A, M, x, b = make_system(A, M, x0, b)
- matvec = A.matvec
- psolve = M.matvec
- n = len(b)
- bnrm2 = np.linalg.norm(b)
- atol, _ = _get_atol_rtol('gmres', bnrm2, atol, rtol)
- if bnrm2 == 0:
- return b, 0
- eps = np.finfo(x.dtype.char).eps
- dotprod = np.vdot if np.iscomplexobj(x) else np.dot
- if maxiter is None:
- maxiter = n*10
- if restart is None:
- restart = 20
- restart = min(restart, n)
- Mb_nrm2 = np.linalg.norm(psolve(b))
- # ====================================================
- # =========== Tolerance control from gh-8400 =========
- # ====================================================
- # Tolerance passed to GMRESREVCOM applies to the inner
- # iteration and deals with the left-preconditioned
- # residual.
- ptol_max_factor = 1.
- ptol = Mb_nrm2 * min(ptol_max_factor, atol / bnrm2)
- presid = 0.
- # ====================================================
- lartg = get_lapack_funcs('lartg', dtype=x.dtype)
- # allocate internal variables
- v = np.empty([restart+1, n], dtype=x.dtype)
- h = np.zeros([restart, restart+1], dtype=x.dtype)
- givens = np.zeros([restart, 2], dtype=x.dtype)
- # legacy iteration count
- inner_iter = 0
- for iteration in range(maxiter):
- if iteration == 0:
- r = b - matvec(x) if x.any() else b.copy()
- if np.linalg.norm(r) < atol: # Are we done?
- return x, 0
- v[0, :] = psolve(r)
- tmp = np.linalg.norm(v[0, :])
- v[0, :] *= (1 / tmp)
- # RHS of the Hessenberg problem
- S = np.zeros(restart+1, dtype=x.dtype)
- S[0] = tmp
- breakdown = False
- for col in range(restart):
- av = matvec(v[col, :])
- w = psolve(av)
- # Modified Gram-Schmidt
- h0 = np.linalg.norm(w)
- for k in range(col+1):
- tmp = dotprod(v[k, :], w)
- h[col, k] = tmp
- w -= tmp*v[k, :]
- h1 = np.linalg.norm(w)
- h[col, col + 1] = h1
- v[col + 1, :] = w[:]
- # Exact solution indicator
- if h1 <= eps*h0:
- h[col, col + 1] = 0
- breakdown = True
- else:
- v[col + 1, :] *= (1 / h1)
- # apply past Givens rotations to current h column
- for k in range(col):
- c, s = givens[k, 0], givens[k, 1]
- n0, n1 = h[col, [k, k+1]]
- h[col, [k, k + 1]] = [c*n0 + s*n1, -s.conj()*n0 + c*n1]
- # get and apply current rotation to h and S
- c, s, mag = lartg(h[col, col], h[col, col+1])
- givens[col, :] = [c, s]
- h[col, [col, col+1]] = mag, 0
- # S[col+1] component is always 0
- tmp = -np.conjugate(s)*S[col]
- S[[col, col + 1]] = [c*S[col], tmp]
- presid = np.abs(tmp)
- inner_iter += 1
- if callback_type in ('legacy', 'pr_norm'):
- callback(presid / bnrm2)
- # Legacy behavior
- if callback_type == 'legacy' and inner_iter == maxiter:
- break
- if presid <= ptol or breakdown:
- break
- # Solve h(col, col) upper triangular system and allow pseudo-solve
- # singular cases as in (but without the f2py copies):
- # y = trsv(h[:col+1, :col+1].T, S[:col+1])
- if h[col, col] == 0:
- S[col] = 0
- y = np.zeros([col+1], dtype=x.dtype)
- y[:] = S[:col+1]
- for k in range(col, 0, -1):
- if y[k] != 0:
- y[k] /= h[k, k]
- tmp = y[k]
- y[:k] -= tmp*h[k, :k]
- if y[0] != 0:
- y[0] /= h[0, 0]
- x += y @ v[:col+1, :]
- r = b - matvec(x)
- rnorm = np.linalg.norm(r)
- # Legacy exit
- if callback_type == 'legacy' and inner_iter == maxiter:
- return x, 0 if rnorm <= atol else maxiter
- if callback_type == 'x':
- callback(x)
- if rnorm <= atol:
- break
- elif breakdown:
- # Reached breakdown (= exact solution), but the external
- # tolerance check failed. Bail out with failure.
- break
- elif presid <= ptol:
- # Inner loop passed but outer didn't
- ptol_max_factor = max(eps, 0.25 * ptol_max_factor)
- else:
- ptol_max_factor = min(1.0, 1.5 * ptol_max_factor)
- ptol = presid * min(ptol_max_factor, atol / rnorm)
- info = 0 if (rnorm <= atol) else maxiter
- return x, info
- def qmr(A, b, x0=None, *, rtol=1e-5, atol=0., maxiter=None, M1=None, M2=None,
- callback=None):
- """
- Solve ``Ax = b`` with the Quasi-Minimal Residual method.
- Parameters
- ----------
- A : {sparse array, ndarray, LinearOperator}
- The real-valued N-by-N matrix of the linear system.
- Alternatively, ``A`` can be a linear operator which can
- produce ``Ax`` and ``A^T x`` using, e.g.,
- ``scipy.sparse.linalg.LinearOperator``.
- b : ndarray
- Right hand side of the linear system. Has shape (N,) or (N,1).
- x0 : ndarray
- Starting guess for the solution.
- atol, rtol : float, optional
- Parameters for the convergence test. For convergence,
- ``norm(b - A @ x) <= max(rtol*norm(b), atol)`` should be satisfied.
- The default is ``atol=0.`` and ``rtol=1e-5``.
- maxiter : integer
- Maximum number of iterations. Iteration will stop after maxiter
- steps even if the specified tolerance has not been achieved.
- M1 : {sparse array, ndarray, LinearOperator}
- Left preconditioner for A.
- M2 : {sparse array, ndarray, LinearOperator}
- Right preconditioner for A. Used together with the left
- preconditioner M1. The matrix M1@A@M2 should have better
- conditioned than A alone.
- callback : function
- User-supplied function to call after each iteration. It is called
- as callback(xk), where xk is the current solution vector.
- Returns
- -------
- x : ndarray
- The converged solution.
- info : integer
- Provides convergence information:
- 0 : successful exit
- >0 : convergence to tolerance not achieved, number of iterations
- <0 : parameter breakdown
- See Also
- --------
- LinearOperator
- Examples
- --------
- >>> import numpy as np
- >>> from scipy.sparse import csc_array
- >>> from scipy.sparse.linalg import qmr
- >>> A = csc_array([[3., 2., 0.], [1., -1., 0.], [0., 5., 1.]])
- >>> b = np.array([2., 4., -1.])
- >>> x, exitCode = qmr(A, b, atol=1e-5)
- >>> print(exitCode) # 0 indicates successful convergence
- 0
- >>> np.allclose(A.dot(x), b)
- True
- """
- A_ = A
- A, M, x, b = make_system(A, None, x0, b)
- bnrm2 = np.linalg.norm(b)
- atol, _ = _get_atol_rtol('qmr', bnrm2, atol, rtol)
- if bnrm2 == 0:
- return b, 0
- if M1 is None and M2 is None:
- if hasattr(A_, 'psolve'):
- def left_psolve(b):
- return A_.psolve(b, 'left')
- def right_psolve(b):
- return A_.psolve(b, 'right')
- def left_rpsolve(b):
- return A_.rpsolve(b, 'left')
- def right_rpsolve(b):
- return A_.rpsolve(b, 'right')
- M1 = LinearOperator(A.shape,
- matvec=left_psolve,
- rmatvec=left_rpsolve)
- M2 = LinearOperator(A.shape,
- matvec=right_psolve,
- rmatvec=right_rpsolve)
- else:
- def id(b):
- return b
- M1 = LinearOperator(A.shape, matvec=id, rmatvec=id)
- M2 = LinearOperator(A.shape, matvec=id, rmatvec=id)
- n = len(b)
- if maxiter is None:
- maxiter = n*10
- dotprod = np.vdot if np.iscomplexobj(x) else np.dot
- rhotol = np.finfo(x.dtype.char).eps
- betatol = rhotol
- gammatol = rhotol
- deltatol = rhotol
- epsilontol = rhotol
- xitol = rhotol
- r = b - A.matvec(x) if x.any() else b.copy()
- vtilde = r.copy()
- y = M1.matvec(vtilde)
- rho = np.linalg.norm(y)
- wtilde = r.copy()
- z = M2.rmatvec(wtilde)
- xi = np.linalg.norm(z)
- gamma, eta, theta = 1, -1, 0
- v = np.empty_like(vtilde)
- w = np.empty_like(wtilde)
- # Dummy values to initialize vars, silence linter warnings
- epsilon, q, d, p, s = None, None, None, None, None
- for iteration in range(maxiter):
- if np.linalg.norm(r) < atol: # Are we done?
- return x, 0
- if np.abs(rho) < rhotol: # rho breakdown
- return x, -10
- if np.abs(xi) < xitol: # xi breakdown
- return x, -15
- v[:] = vtilde[:]
- v *= (1 / rho)
- y *= (1 / rho)
- w[:] = wtilde[:]
- w *= (1 / xi)
- z *= (1 / xi)
- delta = dotprod(z, y)
- if np.abs(delta) < deltatol: # delta breakdown
- return x, -13
- ytilde = M2.matvec(y)
- ztilde = M1.rmatvec(z)
- if iteration > 0:
- ytilde -= (xi * delta / epsilon) * p
- p[:] = ytilde[:]
- ztilde -= (rho * (delta / epsilon).conj()) * q
- q[:] = ztilde[:]
- else: # First spin
- p = ytilde.copy()
- q = ztilde.copy()
- ptilde = A.matvec(p)
- epsilon = dotprod(q, ptilde)
- if np.abs(epsilon) < epsilontol: # epsilon breakdown
- return x, -14
- beta = epsilon / delta
- if np.abs(beta) < betatol: # beta breakdown
- return x, -11
- vtilde[:] = ptilde[:]
- vtilde -= beta*v
- y = M1.matvec(vtilde)
- rho_prev = rho
- rho = np.linalg.norm(y)
- wtilde[:] = w[:]
- wtilde *= - beta.conj()
- wtilde += A.rmatvec(q)
- z = M2.rmatvec(wtilde)
- xi = np.linalg.norm(z)
- gamma_prev = gamma
- theta_prev = theta
- theta = rho / (gamma_prev * np.abs(beta))
- gamma = 1 / np.sqrt(1 + theta**2)
- if np.abs(gamma) < gammatol: # gamma breakdown
- return x, -12
- eta *= -(rho_prev / beta) * (gamma / gamma_prev)**2
- if iteration > 0:
- d *= (theta_prev * gamma) ** 2
- d += eta*p
- s *= (theta_prev * gamma) ** 2
- s += eta*ptilde
- else:
- d = p.copy()
- d *= eta
- s = ptilde.copy()
- s *= eta
- x += d
- r -= s
- if callback:
- callback(x)
- else: # for loop exhausted
- # Return incomplete progress
- return x, maxiter
|