Exemplo n.º 1
0
    def template_riccati(self, rhs, para):
        """template to reduce code in the test cases"""
        #set some options
        self.opt.adi.output = 0
        self.opt.adi.res2_tol = 1e-10
        self.opt.nm.output = 0
        self.opt.nm.res2_tol = 1e-3

        # setup equation
        if para < 0:
            eqn = MyEquation2(self.opt, self.a, self.e, rhs, self.b, self.c)
            z, status = lrnm(eqn, self.opt)
        else:
            eqn2 = MyEquation(self.opt, self.a, self.e, rhs, self.b, self.c)
            self.opt.adi.shifts.paratype = para
            z, status = lrnm(eqn2, self.opt)

        #get res2 from lrnm
        res2 = status.res2_norm
        res2_0 = status.res2_0
        it = status.it
        print("it=%d\t rel_res2=%e\t res2=%e\t tol=%e\n" %
              (it, res2 / res2_0, res2, self.opt.nm.res2_tol))
        self.assertLessEqual(res2 / res2_0, self.opt.nm.res2_tol)

        #check norm
        nrmr, nrmrhs, relnrm = res2_ric(self.a, self.e, self.b, self.c, z,
                                        self.opt.type)
        print("check:\t rel_res2=%e\t res2=%e\t nrmrhs=%e\n" %
              (relnrm, nrmr, nrmrhs))
        # compare residual, should be roughly the same magnitude
        self.assertLessEqual(abs(log10(relnrm) - log10(res2 / res2_0)), 1)
Exemplo n.º 2
0
def main():
    """Solve standard/generalized Riccati equation."""

    # read data
    e = mmread("@CMAKE_SOURCE_DIR@/tests/data/filter2D/filter2D.E").tocsr()
    a = mmread("@CMAKE_SOURCE_DIR@/tests/data/filter2D/filter2D.A").tocsr()
    b = mmread("@CMAKE_SOURCE_DIR@/tests/data/filter2D/filter2D.B")
    c = mmread("@CMAKE_SOURCE_DIR@/tests/data/filter2D/filter2D.C")

    #create opt instance
    opt1 = Options()
    opt1.nm.output = 1
    opt1.adi.output = 0
    opt1.nm.res2_tol = 1e-4
    opt2 = Options()
    opt2.nm.output = 1
    opt2.adi.output = 0
    opt2.nm.res2_tol = 1e-4

    # create standard and generalized equation
    eqn1 = EquationGRiccati(opt1, a, None, b, c)
    eqn2 = EquationGRiccati(opt2, a, e, b, c)

    # solve both equations
    z1, stat1 = lrnm(eqn1, opt1)
    z2, stat2 = lrnm(eqn2, opt2)

    print("Standard Riccati Equation \n")
    print("Size of Low Rank Solution Factor Z1: %d x %d \n"%(z1.shape))
    print(stat1.lrnm_stat())
    print(stat1)
    print("Generalized Riccati Equation \n")
    print("Size of Low Rank Solution Factor Z2: %d x %d \n"%(z2.shape))
    print(stat2.lrnm_stat())
    print(stat2)
Exemplo n.º 3
0
    def template_so1(self, mytype):
        """template to reduce code in the test cases"""
        #set type
        self.opt.type = mytype

        #get matrices
        n = self.m.shape[0]
        if self.opt.type == MESS_OP_NONE:
            b = np.random.rand(2 * n, 1)
            c = np.random.rand(1, n)
        else:
            b = np.random.rand(n, 1)
            c = np.random.rand(1, 2 * n)

        #build equation
        eqn = EquationGRiccatiSO1(self.opt, self.m, self.d, self.k, b, c,
                                  self.lowerbound, self.upperbound)

        #get res2 from lrnm
        _, status = lrnm(eqn, self.opt)
        res2 = status.res2_norm
        res2_0 = status.res2_0
        it = status.it
        print("it=%d\t rel_res2=%e\t res2=%e\t tol=%e\n" %
              (it, res2 / res2_0, res2, self.opt.nm.res2_tol))
        self.assertLessEqual(res2 / res2_0, self.opt.nm.res2_tol)
Exemplo n.º 4
0
    def template_dae2(self, mytype, re, mem_usage):
        """template to reduce code in the test cases"""
        #set type
        self.opt.type = mytype
        self.opt.adi.memory_usage = mem_usage

        #get matrices
        m = mmread(self.matmtx.format(re, 'M'))
        a = mmread(self.matmtx.format(re, 'A'))
        g = mmread(self.matmtx.format(re, 'G'))
        b = mmread(self.matmtx.format(re, 'B'))
        c = mmread(self.matmtx.format(re, 'C'))

        if self.opt.type == MESS_OP_NONE:
            if re >= 300:
                self.opt.nm.k0 = mmread(self.matmtx.format(re, 'Feed1'))
        else:
            if re >= 300:
                self.opt.nm.k0 = mmread(self.matmtx.format(re, 'Feed0'))

        #build equation
        eqn = EquationGRiccatiDAE2(self.opt, m, a, g, b, c, self.delta)

        #get res2 from lrnm
        _, status = lrnm(eqn, self.opt)
        res2 = status.res2_norm
        res2_0 = status.res2_0
        it = status.it
        print("it=%d\t rel_res2=%e\t res2=%e\t tol=%e\n" %
              (it, res2 / res2_0, res2, self.opt.nm.res2_tol))
        self.assertLessEqual(res2 / res2_0, self.opt.nm.res2_tol)
Exemplo n.º 5
0
def main():
    """Solve Riccati Equation DAE2 System."""

    # read data
    m = mmread("@CMAKE_SOURCE_DIR@/tests/data/NSE/NSE_RE_100_lvl1_M.mtx").tocsr()
    a = mmread("@CMAKE_SOURCE_DIR@/tests/data/NSE/NSE_RE_100_lvl1_A.mtx").tocsr()
    g = mmread("@CMAKE_SOURCE_DIR@/tests/data/NSE/NSE_RE_100_lvl1_G.mtx").tocsr()
    b = mmread("@CMAKE_SOURCE_DIR@/tests/data/NSE/NSE_RE_100_lvl1_B.mtx")
    c = mmread("@CMAKE_SOURCE_DIR@/tests/data/NSE/NSE_RE_100_lvl1_C.mtx")
    delta = -0.02

    #create opt instance
    opt = Options()
    opt.adi.output = 0
    opt.nm.output = 0
    opt.nm.res2_tol = 1e-2
    opt.adi.paratype = MESS_LRCFADI_PARA_ADAPTIVE_V

    # create equation
    eqn = EquationGRiccatiDAE2(opt, m, a, g, b, c, delta)

    # solve equation
    z, status = lrnm(eqn, opt)

    # get residual
    res2 = status.res2_norm
    res2_0 = status.res2_0
    it = status.it
    print("Size of Low Rank Solution Factor Z: %d x %d \n"%(z.shape))
    print("it = %d \t rel_res2 = %e\t res2 = %e \n" % (it, res2 / res2_0, res2))
    print(status.lrnm_stat())
Exemplo n.º 6
0
    def template_dae1(self, mytype):
        """template to reduce code in the test cases"""
        #set type
        self.opt.type = mytype

        #get matrices
        e11 = mmread(self.matmtx.format('E11'))
        a11 = mmread(self.matmtx.format('A11'))
        a12 = mmread(self.matmtx.format('A12'))
        a21 = mmread(self.matmtx.format('A21'))
        a22 = mmread(self.matmtx.format('A22'))

        np.random.seed(1111)
        if mytype == MESS_OP_TRANSPOSE:
            b = np.random.rand(e11.shape[0], 2)
            c = np.random.rand(2, e11.shape[1] + a22.shape[1])
        else:
            b = np.random.rand(e11.shape[0] + a22.shape[0], 2)
            c = np.random.rand(2, e11.shape[1])

        b = b / np.linalg.norm(b, 'fro')
        c = c / np.linalg.norm(c, 'fro')

        #build equation
        eqn = EquationGRiccatiDAE1(self.opt, e11, a11, a12, a21, a22, b, c)

        #get res2 from lrnm
        _, status = lrnm(eqn, self.opt)
        res2 = status.res2_norm
        res2_0 = status.res2_0
        it = status.it
        print("it=%d\t rel_res2=%e\t res2=%e\t tol=%e\n" %
              (it, res2 / res2_0, res2, self.opt.nm.res2_tol))
        self.assertLessEqual(res2 / res2_0, self.opt.nm.res2_tol)
Exemplo n.º 7
0
def main():
    """Demonstrate Callback functionality"""

    # read data
    a = mmread('@CMAKE_SOURCE_DIR@/tests/data/Rail/A.mtx')
    b = mmread('@CMAKE_SOURCE_DIR@/tests/data/Rail/B.mtx')
    c = mmread('@CMAKE_SOURCE_DIR@/tests/data/Rail/C.mtx')
    e = mmread('@CMAKE_SOURCE_DIR@/tests/data/Rail/E.mtx')

    # create options
    opt = Options()
    opt.nm.output = 0
    opt.adi.output = 0

    # create instance of MyEquation and solve
    opt.type = MESS_OP_NONE
    eqn1 = MyEquation(opt, a, e, b, c)
    z1, stat1 = lrnm(eqn1, opt)

    # create instance of MyEquation and solve
    opt.type = MESS_OP_TRANSPOSE
    eqn2 = MyEquation(opt, a, e, b, c)
    z2, stat2 = lrnm(eqn2, opt)

    # print information and compute residual again to make sure that we have everything correct
    print("\n")
    print("MyEquation: eqn1")
    print("Size of Low Rank Solution Factor Z1: %d x %d \n" % (z1.shape))
    print(stat1.lrnm_stat())
    nrmr1, nrmrhs1, relnrm1 = res2_ric(a, e, b, c, z1, MESS_OP_NONE)
    print("check for eqn1:\t rel_res2=%e\t res2=%e\t nrmrhs=%e\n" %
          (relnrm1, nrmr1, nrmrhs1))

    print("\n")
    print(
        "--------------------------------------------------------------------------------------"
    )
    print("\n")

    # print information and compute residual again to make sure that we have everything correct
    print("MyEquation: eqn2")
    print("Size of Low Rank Solution Factor Z2: %d x %d \n" % (z2.shape))
    print(stat2.lrnm_stat())
    nrmr2, nrmrhs2, relnrm2 = res2_ric(a, e, b, c, z2, MESS_OP_TRANSPOSE)
    print("check for eqn1:\t rel_res2=%e\t res2=%e\t nrmrhs=%e\n" %
          (relnrm2, nrmr2, nrmrhs2))
def pymess_dae2_cnt_riccati(mmat=None, amat=None, jmat=None,
                            bmat=None, wmat=None, z0=None, mtxoldb=None,
                            transposed=False, aditol=5e-10, nwtn_res2_tol=5e-8,
                            maxit=20, verbose=False, linesearch=False, **kw):
    """ solve the projected algebraic ricc via newton adi

    `M.T*X*A + A.T*X*M - M.T*X*B*B.T*X*M + J(Y) = -WW.T`

    `JXM = 0 and M.TXJ.T = 0`

    If `mtxb` is given,
    (e.g. as the feedback computed in a previous step of a Newton iteration),
    the coefficient matrix with feedback

    `A.T <- A.T - mtxb*b`

    is considered

    """

    import pymess

    optns = pymess.Options()
    optns.adi.res2_tol = aditol
    optns.adi.output = 0
    if verbose:
        optns.nm.output = 1
    if linesearch:
        optns.nm.linesearch = 1
    optns.nm.maxit = maxit
    optns.nm.res2_tol = nwtn_res2_tol
    optns.adi.shifts.paratype = pymess.MESS_LRCFADI_PARA_ADAPTIVE_V
    optns.type = pymess.MESS_OP_TRANSPOSE  # solve the cont Riccati!!!
    delta = -0.02

    if z0 is not None:
        mtxoldb = mmat.T*lau.comp_uvz_spdns(z0, z0.T, bmat)
    if mtxoldb is not None:
        optns.nm.K0 = mtxoldb.T  # initial stabilizing feedback

    ricceq = pymess.EquationGRiccatiDAE2(optns, mmat, amat, jmat.T,
                                         bmat, wmat.T, delta)

    Z, status = pymess.lrnm(ricceq, optns)

    nwtn_upd_fnorms = []

    return dict(zfac=Z, nwtn_upd_fnorms=nwtn_upd_fnorms)
Exemplo n.º 9
0
    def template_filter(self, mytype, cnum, e):
        """template to reduce code in the test cases"""
        self.opt.type = mytype
        c = mmread(self.cmtx.format(cnum)).todense()
        e = mmread(self.emtx).tocsr() if e else None

        # build equation
        eqn = EquationGRiccati(self.opt, self.a, e, self.b, c)

        #get res2 from lrnm
        _, status = lrnm(eqn, self.opt)
        res2 = status.res2_norm
        res2_0 = status.res2_0
        it = status.it
        print("it=%d\t rel_res2=%e\t res2=%e\t tol=%e\n" %
              (it, res2 / res2_0, res2, self.opt.nm.res2_tol))
        self.assertLessEqual(res2 / res2_0, self.opt.nm.res2_tol)
Exemplo n.º 10
0
def main():
    """Solve Riccati Equation DAE1 System."""

    # read data
    e11 = mmread("@CMAKE_SOURCE_DIR@/tests/data/bips98_606/E11.mtx").tocsr()
    a11 = mmread("@CMAKE_SOURCE_DIR@/tests/data/bips98_606/A11.mtx").tocsr()
    a12 = mmread("@CMAKE_SOURCE_DIR@/tests/data/bips98_606/A12.mtx").tocsr()
    a21 = mmread("@CMAKE_SOURCE_DIR@/tests/data/bips98_606/A21.mtx").tocsr()
    a22 = mmread("@CMAKE_SOURCE_DIR@/tests/data/bips98_606/A22.mtx").tocsr()

    #create opt instance
    opt = Options()
    opt.adi.output = 0
    opt.nm.output = 0
    opt.nm.res2_tol = 1e-10

    # generate some matrices b and c
    seed(1111)
    if opt.type == MESS_OP_TRANSPOSE:
        b = rand(e11.shape[0], 2)
        c = rand(2, e11.shape[1] + a22.shape[1])
    else:
        b = rand(e11.shape[0] + a22.shape[0], 2)
        c = rand(2, e11.shape[1])

    b = b / norm(b, 'fro')
    c = c / norm(c, 'fro')

    # create equation
    eqn = EquationGRiccatiDAE1(opt, e11, a11, a12, a21, a22, b, c)

    # solve equation
    z, status = lrnm(eqn, opt)

    # get residual
    res2 = status.res2_norm
    res2_0 = status.res2_0
    it = status.it
    print("Size of Low Rank Solution Factor Z: %d x %d \n" % (z.shape))
    print("it = %d \t rel_res2 = %e\t res2 = %e \n" %
          (it, res2 / res2_0, res2))
    print(status.lrnm_stat())
Exemplo n.º 11
0
def main():
    """Solve Riccati Equation SO1 System equation."""

    # read data
    m = mmread("@CMAKE_SOURCE_DIR@/tests/data/TripleChain/M_301.mtx").tocsr()
    d = mmread("@CMAKE_SOURCE_DIR@/tests/data/TripleChain/D_301.mtx").tocsr()
    k = mmread("@CMAKE_SOURCE_DIR@/tests/data/TripleChain/K_301.mtx").tocsr()

    # generate rhs
    b = np.ones((2 * m.shape[0], 1), dtype=np.double)
    c = np.ones((1, m.shape[0]), dtype=np.double)

    # bounds for shift parameters
    lowerbound = 1e-8
    upperbound = 1e+8

    # create options instance
    opt = Options()
    opt.adi.output = 0
    opt.nm.output = 0
    opt.nm.res2_tol = 1e-6
    opt.adi.res2_tol = 1e-10
    opt.adi.maxit = 1000
    opt.type = MESS_OP_NONE
    opt.adi.shifts.paratype = MESS_LRCFADI_PARA_MINMAX

    # create equation
    eqn = EquationGRiccatiSO1(opt, m, d, k, b, c, lowerbound, upperbound)

    # solve equation
    z, status = lrnm(eqn, opt)

    # get residual
    res2 = status.res2_norm
    res2_0 = status.res2_0
    it = status.it
    print("it = %d \t rel_res2 = %e\t res2 = %e \n" %
          (it, res2 / res2_0, res2))
    print("Size of Low Rank Solution Factor Z: %d x %d \n" % (z.shape))
    print(status.lrnm_stat())
Exemplo n.º 12
0
    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)
Exemplo n.º 13
0
    def solve_ricc(A, E=None, B=None, Q=None, C=None, R=None, G=None,
                   trans=False, options=None, default_solver='pymess'):
        """Find a factor of the solution of a Riccati equation

        Returns factor :math:`Z` such that :math:`Z Z^T` is
        approximately the solution :math:`X` of a Riccati equation

        .. math::
            A^T X E + E^T X A - E^T X B R^{-1} B^T X E + Q = 0.

        If E in `None`, it is taken to be the identity matrix.
        Q can instead be given as C^T * C. In this case, Q needs to be
        `None`, and C not `None`.
        B * R^{-1} B^T can instead be given by G. In this case, B and R
        need to be `None`, and G not `None`.
        If R and G are `None`, then R is taken to be the identity
        matrix.
        If trans is `True`, then the dual Riccati equation is solved

        .. math::
            A X E^T + E X A^T - E X C^T R^{-1} C X E^T + Q = 0,

        where Q can be replaced by B * B^T and C^T * R^{-1} * C by G.

        This uses the `pymess` package, in particular its `care` and
        `lrnm` methods.
        Operators Q, R, and G are not supported,
        Both methods can be used for large-scale problems.
        The restrictions are:

            - `care` needs access to all matrix data, i.e., it expects
              :func:`~pymor.algorithms.to_matrix.to_matrix` to work for
              A, E, B, and C,
            - `lrnm` needs access to the data of the operators B and C,
              i.e., it expects
              :func:`~pymor.algorithms.to_matrix.to_matrix` to work for
              B and C.

        Parameters
        ----------
        A
            The |Operator| A.
        B
            The |Operator| B or `None`.
        E
            The |Operator| E or `None`.
        Q
            The |Operator| Q or `None`.
        C
            The |Operator| C or `None`.
        R
            The |Operator| R or `None`.
        G
            The |Operator| G or `None`.
        trans
            If the dual equation needs to be solved.
        options
            The |solver_options| to use (see
            :func:`ricc_solver_options`).
        default_solver
            The solver to use when no `options` are specified (pymess,
            pymess_care, pymess_lrnm).

        Returns
        -------
        Z
            Low-rank factor of the Riccati equation solution,
            |VectorArray| from `A.source`.
        """
        _solve_ricc_check_args(A, E, B, Q, C, R, G, trans)
        options = _parse_options(options, ricc_solver_options(), default_solver, None, False)

        if options['type'] == 'pymess':
            if A.source.dim >= PYMESS_MIN_SPARSE_SIZE:
                options = dict(options, type='pymess_lrnm')  # do not modify original dict!
            else:
                options = dict(options, type='pymess_care')  # do not modify original dict!

        if options['type'] == 'pymess_care':
            if Q is not None or R is not None or G is not None:
                raise NotImplementedError
            A_mat = to_matrix(A, format='dense') if A.source.dim < PYMESS_MIN_SPARSE_SIZE else to_matrix(A)
            if E is not None:
                E_mat = to_matrix(E, format='dense') if A.source.dim < PYMESS_MIN_SPARSE_SIZE else to_matrix(E)
            else:
                E_mat = None
            B_mat = to_matrix(B, format='dense') if B else None
            C_mat = to_matrix(C, format='dense') if C else None
            if not trans:
                Z = pymess.care(A_mat, E_mat, B_mat, C_mat)
            else:
                if E is None:
                    Z = pymess.care(A_mat.T, None, C_mat.T, B_mat.T)
                else:
                    Z = pymess.care(A_mat.T, E_mat.T, C_mat.T, B_mat.T)
        elif options['type'] == 'pymess_lrnm':
            if Q is not None or R is not None or G is not None:
                raise NotImplementedError
            opts = options['opts']
            if not trans:
                opts.type = pymess.MESS_OP_TRANSPOSE
            else:
                opts.type = pymess.MESS_OP_NONE
            eqn = RiccatiEquation(opts, A, E, B, C)
            Z, status = pymess.lrnm(eqn, opts)

        Z = A.source.from_numpy(np.array(Z).T)

        return Z
Exemplo n.º 14
0
    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)