def solve_ricc_lrcf(A, E, B, C, R=None, trans=False, options=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 `scipy.linalg.solve_continuous_are`, which is a dense solver. Therefore, we assume all |Operators| and |VectorArrays| can be converted to |NumPy arrays| using :func:`~pymor.algorithms.to_matrix.to_matrix` and :func:`~pymor.vectorarrays.interface.VectorArray.to_numpy`. 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`. trans Whether the first |Operator| in the Riccati equation is transposed. options The solver options to use (see :func:`ricc_lrcf_solver_options`). Returns ------- Z Low-rank Cholesky factor of the Riccati equation solution, |VectorArray| from `A.source`. """ _solve_ricc_check_args(A, E, B, C, R, trans) options = _parse_options(options, ricc_lrcf_solver_options(), 'scipy', None, False) if options['type'] != 'scipy': raise ValueError( f"Unexpected Riccati equation solver ({options['type']}).") A_source = A.source A = to_matrix(A, format='dense') E = to_matrix(E, format='dense') if E else None B = B.to_numpy().T C = C.to_numpy() if R is None: R = np.eye(C.shape[0] if not trans else B.shape[1]) if not trans: if E is not None: E = E.T X = solve_continuous_are(A.T, C.T, B.dot(B.T), R, E) else: X = solve_continuous_are(A, B, C.T.dot(C), R, E) return A_source.from_numpy(_chol(X).T)
def solve_pos_ricc_lrcf(A, E, B, C, R=None, trans=False, options=None): """Compute an approximate low-rank solution of a positive Riccati equation. See :func:`pymor.algorithms.riccati.solve_pos_ricc_lrcf` for a general description. This function uses `pymess.dense_nm_gmpcare`. 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`. trans Whether the first |Operator| in the Riccati equation is transposed. options The solver options to use (see :func:`pos_ricc_lrcf_solver_options`). Returns ------- Z Low-rank Cholesky factor of the Riccati equation solution, |VectorArray| from `A.source`. """ _solve_ricc_check_args(A, E, B, C, R, trans) options = _parse_options(options, pos_ricc_lrcf_solver_options(), 'pymess_dense_nm_gmpcare', None, False) if options['type'] == 'pymess_dense_nm_gmpcare': X = _call_pymess_dense_nm_gmpare(A, E, B, C, R, trans=trans, options=options['opts'], plus=True, method_name='solve_pos_ricc_lrcf') Z = _chol(X) else: raise ValueError( f'Unexpected positive Riccati equation solver ({options["type"]}).' ) return A.source.from_numpy(Z.T)
def solve_lyap_lrcf(A, E, B, trans=False, options=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 `slycot.sb03md` (if `E is None`) and `slycot.sg03ad` (if `E is not None`), which are dense solvers based on the Bartels-Stewart algorithm. Therefore, we assume A and E can be converted to |NumPy arrays| using :func:`~pymor.algorithms.to_matrix.to_matrix` and that `B.to_numpy` is implemented. 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`). Returns ------- Z Low-rank Cholesky factor of the Lyapunov equation solution, |VectorArray| from `A.source`. """ _solve_lyap_lrcf_check_args(A, E, B, trans) options = _parse_options(options, lyap_lrcf_solver_options(), 'slycot_bartels-stewart', None, False) if options['type'] == 'slycot_bartels-stewart': 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) 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): """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 `scipy.linalg.solve_continuous_lyapunov`, which is a dense solver for Lyapunov equations with E=I. Therefore, we assume A and E can be converted to |NumPy arrays| using :func:`~pymor.algorithms.to_matrix.to_matrix` and that `B.to_numpy` is implemented. .. note:: If E is not `None`, the problem will be reduced to a standard continuous-time algebraic Lyapunov equation by inverting E. 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`). Returns ------- Z Low-rank Cholesky factor of the Lyapunov equation solution, |VectorArray| from `A.source`. """ _solve_lyap_lrcf_check_args(A, E, B, trans) options = _parse_options(options, lyap_lrcf_solver_options(), 'scipy', None, False) 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) return A.source.from_numpy(_chol(X).T)
def solve_lyap_lrcf(A, E, B, trans=False, options=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 `slycot.sb03md` (if `E is None`) and `slycot.sg03ad` (if `E is not None`), which are dense solvers based on the Bartels-Stewart algorithm. Therefore, we assume A and E can be converted to |NumPy arrays| using :func:`~pymor.algorithms.to_matrix.to_matrix` and that `B.to_numpy` is implemented. 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`). Returns ------- Z Low-rank Cholesky factor of the Lyapunov equation solution, |VectorArray| from `A.source`. """ _solve_lyap_lrcf_check_args(A, E, B, trans) options = _parse_options(options, lyap_lrcf_solver_options(), 'slycot_bartels-stewart', None, False) if options['type'] == 'slycot_bartels-stewart': 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) else: raise ValueError(f"Unexpected Lyapunov equation solver ({options['type']}).") return A.source.from_numpy(Z.T)
def solve_pos_ricc_lrcf(A, E, B, C, R=None, S=None, trans=False, options=None): """Compute an approximate low-rank solution of a positive Riccati equation. See :func:`pymor.algorithms.riccati.solve_pos_ricc_lrcf` for a general description. This function uses `pymess.dense_nm_gmpcare`. 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:`pos_ricc_lrcf_solver_options`). 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) options = _parse_options(options, pos_ricc_lrcf_solver_options(), 'pymess_dense_nm_gmpcare', 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=True) Z = _chol(X) else: raise ValueError(f'Unexpected positive Riccati equation solver ({options["type"]}).') return A.source.from_numpy(Z.T)
def solve_lyap_lrcf(A, E, B, trans=False, options=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 `scipy.linalg.solve_continuous_lyapunov`, which is a dense solver for Lyapunov equations with E=I. Therefore, we assume A and E can be converted to |NumPy arrays| using :func:`~pymor.algorithms.to_matrix.to_matrix` and that `B.to_numpy` is implemented. .. note:: If E is not `None`, the problem will be reduced to a standard continuous-time algebraic Lyapunov equation by inverting E. 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`). Returns ------- Z Low-rank Cholesky factor of the Lyapunov equation solution, |VectorArray| from `A.source`. """ _solve_lyap_lrcf_check_args(A, E, B, trans) options = _parse_options(options, lyap_lrcf_solver_options(), 'scipy', None, False) 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) return A.source.from_numpy(_chol(X).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.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_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_ricc_lrcf(A, E, B, C, R=None, S=None, trans=False, options=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 `slycot.sb02md` (if E and S are `None`), `slycot.sb02od` (if E is `None` and S is not `None`) and `slycot.sg03ad` (if E is not `None`), which are dense solvers. Therefore, we assume all |Operators| and |VectorArrays| can be converted to |NumPy arrays| using :func:`~pymor.algorithms.to_matrix.to_matrix` and :func:`~pymor.vectorarrays.interfaces.VectorArrayInterface.to_numpy`. 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`). 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) options = _parse_options(options, ricc_lrcf_solver_options(), 'slycot', None, False) if options['type'] != 'slycot': raise ValueError( f"Unexpected Riccati equation solver ({options['type']}).") A_source = A.source A = to_matrix(A, format='dense') E = to_matrix(E, format='dense') if E else None B = B.to_numpy().T C = C.to_numpy() S = S.to_numpy().T if S else None n = A.shape[0] dico = 'C' if E is None: if S is None: if not trans: A = A.T G = C.T.dot(C) if R is None else slycot.sb02mt( n, C.shape[0], C.T, R)[-1] else: G = B.dot(B.T) if R is None else slycot.sb02mt( n, B.shape[1], B, R)[-1] Q = B.dot(B.T) if not trans else C.T.dot(C) X, rcond = slycot.sb02md(n, A, G, Q, dico)[:2] _ricc_rcond_check('slycot.sb02md', rcond) else: m = C.shape[0] if not trans else B.shape[1] p = B.shape[1] if not trans else C.shape[0] if R is None: R = np.eye(m) if not trans: A = A.T B, C = C.T, B.T X, rcond = slycot.sb02od(n, m, A, B, C, R, dico, p=p, L=S, fact='C')[:2] _ricc_rcond_check('slycot.sb02od', rcond) else: jobb = 'B' fact = 'C' uplo = 'U' jobl = 'Z' if S is None else 'N' scal = 'N' sort = 'S' acc = 'R' m = C.shape[0] if not trans else B.shape[1] p = B.shape[1] if not trans else C.shape[0] if R is None: R = np.eye(m) if S is None: S = np.empty((n, m)) if not trans: A = A.T E = E.T B, C = C.T, B.T out = slycot.sg02ad(dico, jobb, fact, uplo, jobl, scal, sort, acc, n, m, p, A, E, B, C, R, S) X = out[1] rcond = out[0] _ricc_rcond_check('slycot.sg02ad', rcond) return A_source.from_numpy(_chol(X).T)
def solve_ricc_lrcf(A, E, B, C, R=None, S=None, trans=False, options=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 `slycot.sb02md` (if E and S are `None`), `slycot.sb02od` (if E is `None` and S is not `None`) and `slycot.sg03ad` (if E is not `None`), which are dense solvers. Therefore, we assume all |Operators| and |VectorArrays| can be converted to |NumPy arrays| using :func:`~pymor.algorithms.to_matrix.to_matrix` and :func:`~pymor.vectorarrays.interfaces.VectorArrayInterface.to_numpy`. 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`). 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) options = _parse_options(options, ricc_lrcf_solver_options(), 'slycot', None, False) if options['type'] != 'slycot': raise ValueError(f"Unexpected Riccati equation solver ({options['type']}).") A_source = A.source A = to_matrix(A, format='dense') E = to_matrix(E, format='dense') if E else None B = B.to_numpy().T C = C.to_numpy() S = S.to_numpy().T if S else None n = A.shape[0] dico = 'C' if E is None: if S is None: if not trans: A = A.T G = C.T.dot(C) if R is None else slycot.sb02mt(n, C.shape[0], C.T, R)[-1] else: G = B.dot(B.T) if R is None else slycot.sb02mt(n, B.shape[1], B, R)[-1] Q = B.dot(B.T) if not trans else C.T.dot(C) X, rcond = slycot.sb02md(n, A, G, Q, dico)[:2] _ricc_rcond_check('slycot.sb02md', rcond) else: m = C.shape[0] if not trans else B.shape[1] p = B.shape[1] if not trans else C.shape[0] if R is None: R = np.eye(m) if not trans: A = A.T B, C = C.T, B.T X, rcond = slycot.sb02od(n, m, A, B, C, R, dico, p=p, L=S, fact='C')[:2] _ricc_rcond_check('slycot.sb02od', rcond) else: jobb = 'B' fact = 'C' uplo = 'U' jobl = 'Z' if S is None else 'N' scal = 'N' sort = 'S' acc = 'R' m = C.shape[0] if not trans else B.shape[1] p = B.shape[1] if not trans else C.shape[0] if R is None: R = np.eye(m) if S is None: S = np.empty((n, m)) if not trans: A = A.T E = E.T B, C = C.T, B.T out = slycot.sg02ad(dico, jobb, fact, uplo, jobl, scal, sort, acc, n, m, p, A, E, B, C, R, S) X = out[1] rcond = out[0] _ricc_rcond_check('slycot.sg02ad', rcond) return A_source.from_numpy(_chol(X).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.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)
def solve_ricc_lrcf(A, E, B, C, R=None, S=None, trans=False, options=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 `scipy.linalg.solve_continuous_are`, which is a dense solver. Therefore, we assume all |Operators| and |VectorArrays| can be converted to |NumPy arrays| using :func:`~pymor.algorithms.to_matrix.to_matrix` and :func:`~pymor.vectorarrays.interfaces.VectorArrayInterface.to_numpy`. 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`). 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) options = _parse_options(options, ricc_lrcf_solver_options(), 'scipy', None, False) if options['type'] != 'scipy': raise ValueError(f"Unexpected Riccati equation solver ({options['type']}).") A_source = A.source A = to_matrix(A, format='dense') E = to_matrix(E, format='dense') if E else None B = B.to_numpy().T C = C.to_numpy() S = S.to_numpy().T if S else None if R is None: R = np.eye(C.shape[0] if not trans else B.shape[1]) if not trans: if E is not None: E = E.T X = solve_continuous_are(A.T, C.T, B.dot(B.T), R, E, S) else: X = solve_continuous_are(A, B, C.T.dot(C), R, E, S) return A_source.from_numpy(_chol(X).T)