def solve_ricc_lrcf( A, E, B, C, R=None, S=None, trans=False, options=None, default_sparse_solver_backend=_DEFAULT_RICC_LRCF_SPARSE_SOLVER_BACKEND, default_dense_solver_backend=_DEFAULT_RICC_LRCF_DENSE_SOLVER_BACKEND): """Compute an approximate low-rank solution of a Riccati equation. Returns a low-rank Cholesky factor :math:`Z` such that :math:`Z Z^T` approximates the solution :math:`X` of a (generalized) continuous-time algebraic Riccati equation: - if trans is `False` .. math:: A X E^T + E X A^T - (E X C^T + S) R^{-1} (E X C^T + S)^T + B B^T = 0. - if trans is `True` .. math:: A^T X E + E^T X A - (E^T X B + S) R^{-1} (E^T X B + S)^T + C^T C = 0. If E is None, it is taken to be identity, and similarly for R. If S is None, it is taken to be zero. We assume: - A and E are real |Operators|, - B, C and S are real |VectorArrays| from `A.source`, - R is a real |NumPy array|, - (E, A, B, C) is stabilizable and detectable, - R is symmetric positive definite, and - :math:`B B^T - S R^{-1} S^T` (:math:`C^T C - S R^{-1} S^T`) is positive semi-definite trans is `False` (`True`). For large-scale problems, we additionally assume that `len(B)` and `len(C)` are small. If the solver is not specified using the options argument, a solver backend is chosen based on availability in the following order: - for sparse problems (minimum size specified by :func:`~pymor.algorithms.lyapunov.mat_eqn_sparse_min_size`) 1. `pymess` (see :func:`pymor.bindings.pymess.solve_ricc_lrcf`), - for dense problems (smaller than :func:`~pymor.algorithms.lyapunov.mat_eqn_sparse_min_size`) 1. `pymess` (see :func:`pymor.bindings.pymess.solve_ricc_lrcf`), 2. `slycot` (see :func:`pymor.bindings.slycot.solve_ricc_lrcf`), 3. `scipy` (see :func:`pymor.bindings.scipy.solve_ricc_lrcf`). Parameters ---------- A The |Operator| A. E The |Operator| E or `None`. B The operator B as a |VectorArray| from `A.source`. C The operator C as a |VectorArray| from `A.source`. R The operator R as a 2D |NumPy array| or `None`. S The operator S as a |VectorArray| from `A.source` or `None`. trans Whether the first |Operator| in the Riccati equation is transposed. options The solver options to use. See: - :func:`pymor.bindings.scipy.ricc_lrcf_solver_options`, - :func:`pymor.bindings.slycot.ricc_lrcf_solver_options`, - :func:`pymor.bindings.pymess.ricc_lrcf_solver_options`. default_sparse_solver_backend Default sparse solver backend to use (pymess). default_dense_solver_backend Default dense solver backend to use (pymess, slycot, scipy). Returns ------- Z Low-rank Cholesky factor of the Riccati equation solution, |VectorArray| from `A.source`. """ _solve_ricc_check_args(A, E, B, C, R, S, trans) if options: solver = options if isinstance(options, str) else options['type'] backend = solver.split('_')[0] else: if A.source.dim >= mat_eqn_sparse_min_size(): backend = default_sparse_solver_backend else: backend = default_dense_solver_backend if backend == 'scipy': from pymor.bindings.scipy import solve_ricc_lrcf as solve_ricc_impl elif backend == 'slycot': from pymor.bindings.slycot import solve_ricc_lrcf as solve_ricc_impl elif backend == 'pymess': from pymor.bindings.pymess import solve_ricc_lrcf as solve_ricc_impl else: raise ValueError(f'Unknown solver backend ({backend}).') return solve_ricc_impl(A, E, B, C, R, S, trans=trans, options=options)
def solve_ricc_lrcf(A, E, B, C, R=None, S=None, trans=False, options=None, default_solver=None): """Compute an approximate low-rank solution of a Riccati equation. See :func:`pymor.algorithms.riccati.solve_ricc_lrcf` for a general description. This function uses `pymess.dense_nm_gmpcare` and `pymess.lrnm`. For both methods, :meth:`~pymor.vectorarrays.interface.VectorArray.to_numpy` and :meth:`~pymor.vectorarrays.interface.VectorSpace.from_numpy` need to be implemented for `A.source`. Additionally, since `dense_nm_gmpcare` is a dense solver, it expects :func:`~pymor.algorithms.to_matrix.to_matrix` to work for A and E. If the solver is not specified using the options or default_solver arguments, `dense_nm_gmpcare` is used for small problems (smaller than defined with :func:`~pymor.algorithms.lyapunov.mat_eqn_sparse_min_size`) and `lrnm` for large problems. Parameters ---------- A The non-parametric |Operator| A. E The non-parametric |Operator| E or `None`. B The operator B as a |VectorArray| from `A.source`. C The operator C as a |VectorArray| from `A.source`. R The operator R as a 2D |NumPy array| or `None`. S The operator S as a |VectorArray| from `A.source` or `None`. trans Whether the first |Operator| in the Riccati equation is transposed. options The solver options to use (see :func:`ricc_lrcf_solver_options`). default_solver Default solver to use (pymess_lrnm, pymess_dense_nm_gmpcare). If `None`, chose solver depending on dimension `A`. Returns ------- Z Low-rank Cholesky factor of the Riccati equation solution, |VectorArray| from `A.source`. """ _solve_ricc_check_args(A, E, B, C, R, S, trans) if default_solver is None: default_solver = 'pymess_lrnm' if A.source.dim >= mat_eqn_sparse_min_size( ) else 'pymess_dense_nm_gmpcare' options = _parse_options(options, ricc_lrcf_solver_options(), default_solver, None, False) if options['type'] == 'pymess_dense_nm_gmpcare': X = _call_pymess_dense_nm_gmpare(A, E, B, C, R, S, trans=trans, options=options['opts'], plus=False, method_name='solve_ricc_lrcf') Z = _chol(X) elif options['type'] == 'pymess_lrnm': if S is not None: raise NotImplementedError if R is not None: import scipy.linalg as spla Rc = spla.cholesky(R) # R = Rc^T * Rc Rci = spla.solve_triangular(Rc, np.eye( Rc.shape[0])) # R^{-1} = Rci * Rci^T if not trans: C = C.lincomb(Rci.T) # C <- Rci^T * C = (C^T * Rci)^T else: B = B.lincomb(Rci.T) # B <- B * Rci opts = options['opts'] opts.type = pymess.MESS_OP_NONE if not trans else pymess.MESS_OP_TRANSPOSE eqn = RiccatiEquation(opts, A, E, B, C) Z, status = pymess.lrnm(eqn, opts) relres = status.res2_norm / status.res2_0 if relres > opts.adi.res2_tol: logger = getLogger('pymor.bindings.pymess.solve_ricc_lrcf') logger.warning( f'Desired relative residual tolerance was not achieved ' f'({relres:e} > {opts.adi.res2_tol:e}).') else: raise ValueError( f'Unexpected Riccati equation solver ({options["type"]}).') return A.source.from_numpy(Z.T)
def solve_ricc_lrcf(A, E, B, C, R=None, S=None, trans=False, options=None, default_solver=None): """Compute an approximate low-rank solution of a Riccati equation. See :func:`pymor.algorithms.riccati.solve_ricc_lrcf` for a general description. This function uses `pymess.dense_nm_gmpcare` and `pymess.lrnm`. For both methods, :meth:`~pymor.vectorarrays.interfaces.VectorArrayInterface.to_numpy` and :meth:`~pymor.vectorarrays.interfaces.VectorSpaceInterface.from_numpy` need to be implemented for `A.source`. Additionally, since `dense_nm_gmpcare` is a dense solver, it expects :func:`~pymor.algorithms.to_matrix.to_matrix` to work for A and E. If the solver is not specified using the options or default_solver arguments, `dense_nm_gmpcare` is used for small problems (smaller than defined with :func:`~pymor.algorithms.lyapunov.mat_eqn_sparse_min_size`) and `lrnm` for large problems. Parameters ---------- A The |Operator| A. E The |Operator| E or `None`. B The operator B as a |VectorArray| from `A.source`. C The operator C as a |VectorArray| from `A.source`. R The operator R as a 2D |NumPy array| or `None`. S The operator S as a |VectorArray| from `A.source` or `None`. trans Whether the first |Operator| in the Riccati equation is transposed. options The solver options to use (see :func:`ricc_lrcf_solver_options`). default_solver Default solver to use (pymess_lrnm, pymess_dense_nm_gmpcare). If `None`, chose solver depending on dimension `A`. Returns ------- Z Low-rank Cholesky factor of the Riccati equation solution, |VectorArray| from `A.source`. """ _solve_ricc_check_args(A, E, B, C, R, S, trans) if default_solver is None: default_solver = 'pymess_lrnm' if A.source.dim >= mat_eqn_sparse_min_size() else 'pymess_dense_nm_gmpcare' options = _parse_options(options, ricc_lrcf_solver_options(), default_solver, None, False) if options['type'] == 'pymess_dense_nm_gmpcare': X = _call_pymess_dense_nm_gmpare(A, E, B, C, R, S, trans=trans, options=options['opts'], plus=False) Z = _chol(X) elif options['type'] == 'pymess_lrnm': if S is not None: raise NotImplementedError if R is not None: import scipy.linalg as spla Rc = spla.cholesky(R) # R = Rc^T * Rc Rci = spla.solve_triangular(Rc, np.eye(Rc.shape[0])) # R^{-1} = Rci * Rci^T if not trans: C = C.lincomb(Rci.T) # C <- Rci^T * C = (C^T * Rci)^T else: B = B.lincomb(Rci.T) # B <- B * Rci opts = options['opts'] opts.type = pymess.MESS_OP_NONE if not trans else pymess.MESS_OP_TRANSPOSE eqn = RiccatiEquation(opts, A, E, B, C) Z, status = pymess.lrnm(eqn, opts) else: raise ValueError(f'Unexpected Riccati equation solver ({options["type"]}).') return A.source.from_numpy(Z.T)
def solve_lyap_lrcf(A, E, B, trans=False, options=None, default_solver=None): """Compute an approximate low-rank solution of a Lyapunov equation. See :func:`pymor.algorithms.lyapunov.solve_lyap_lrcf` for a general description. This function uses `pymess.glyap` and `pymess.lradi`. For both methods, :meth:`~pymor.vectorarrays.interface.VectorArray.to_numpy` and :meth:`~pymor.vectorarrays.interface.VectorSpace.from_numpy` need to be implemented for `A.source`. Additionally, since `glyap` is a dense solver, it expects :func:`~pymor.algorithms.to_matrix.to_matrix` to work for A and E. If the solver is not specified using the options or default_solver arguments, `glyap` is used for small problems (smaller than defined with :func:`~pymor.algorithms.lyapunov.mat_eqn_sparse_min_size`) and `lradi` for large problems. Parameters ---------- A The non-parametric |Operator| A. E The non-parametric |Operator| E or `None`. B The operator B as a |VectorArray| from `A.source`. trans Whether the first |Operator| in the Lyapunov equation is transposed. options The solver options to use (see :func:`lyap_lrcf_solver_options`). default_solver Default solver to use (pymess_lradi, pymess_glyap). If `None`, choose solver depending on the dimension of A. Returns ------- Z Low-rank Cholesky factor of the Lyapunov equation solution, |VectorArray| from `A.source`. """ _solve_lyap_lrcf_check_args(A, E, B, trans) if default_solver is None: default_solver = 'pymess_lradi' if A.source.dim >= mat_eqn_sparse_min_size( ) else 'pymess_glyap' options = _parse_options(options, lyap_lrcf_solver_options(), default_solver, None, False) if options['type'] == 'pymess_glyap': X = solve_lyap_dense(to_matrix(A, format='dense'), to_matrix(E, format='dense') if E else None, B.to_numpy().T if not trans else B.to_numpy(), trans=trans, options=options) Z = _chol(X) elif options['type'] == 'pymess_lradi': opts = options['opts'] opts.type = pymess.MESS_OP_NONE if not trans else pymess.MESS_OP_TRANSPOSE eqn = LyapunovEquation(opts, A, E, B) Z, status = pymess.lradi(eqn, opts) relres = status.res2_norm / status.res2_0 if relres > opts.adi.res2_tol: logger = getLogger('pymor.bindings.pymess.solve_lyap_lrcf') logger.warning( f'Desired relative residual tolerance was not achieved ' f'({relres:e} > {opts.adi.res2_tol:e}).') else: raise ValueError( f'Unexpected Lyapunov equation solver ({options["type"]}).') return A.source.from_numpy(Z.T)
def solve_lyap_lrcf(A, E, B, trans=False, options=None, default_solver=None): """Compute an approximate low-rank solution of a Lyapunov equation. See :func:`pymor.algorithms.lyapunov.solve_lyap_lrcf` for a general description. This function uses `pymess.glyap` and `pymess.lradi`. For both methods, :meth:`~pymor.vectorarrays.interfaces.VectorArrayInterface.to_numpy` and :meth:`~pymor.vectorarrays.interfaces.VectorSpaceInterface.from_numpy` need to be implemented for `A.source`. Additionally, since `glyap` is a dense solver, it expects :func:`~pymor.algorithms.to_matrix.to_matrix` to work for A and E. If the solver is not specified using the options or default_solver arguments, `glyap` is used for small problems (smaller than defined with :func:`~pymor.algorithms.lyapunov.mat_eqn_sparse_min_size`) and `lradi` for large problems. Parameters ---------- A The |Operator| A. E The |Operator| E or `None`. B The operator B as a |VectorArray| from `A.source`. trans Whether the first |Operator| in the Lyapunov equation is transposed. options The solver options to use (see :func:`lyap_lrcf_solver_options`). default_solver Default solver to use (pymess_lradi, pymess_glyap). If `None`, choose solver depending on the dimension of A. Returns ------- Z Low-rank Cholesky factor of the Lyapunov equation solution, |VectorArray| from `A.source`. """ _solve_lyap_lrcf_check_args(A, E, B, trans) if default_solver is None: default_solver = 'pymess_lradi' if A.source.dim >= mat_eqn_sparse_min_size() else 'pymess_glyap' options = _parse_options(options, lyap_lrcf_solver_options(), default_solver, None, False) if options['type'] == 'pymess_glyap': X = solve_lyap_dense(to_matrix(A, format='dense'), to_matrix(E, format='dense') if E else None, B.to_numpy().T if not trans else B.to_numpy(), trans=trans, options=options) Z = _chol(X) elif options['type'] == 'pymess_lradi': opts = options['opts'] opts.type = pymess.MESS_OP_NONE if not trans else pymess.MESS_OP_TRANSPOSE eqn = LyapunovEquation(opts, A, E, B) Z, status = pymess.lradi(eqn, opts) else: raise ValueError(f'Unexpected Lyapunov equation solver ({options["type"]}).') return A.source.from_numpy(Z.T)