Exemple #1
0
def test_to_matrix():
    np.random.seed(0)
    A = np.random.randn(2, 2)
    B = np.random.randn(3, 3)
    C = np.random.randn(3, 3)

    X = np.bmat([[np.eye(2) + A, np.zeros((2, 3))], [np.zeros((3, 2)), B.dot(C.T)]])

    C = sps.csc_matrix(C)

    Aop = NumpyMatrixOperator(A)
    Bop = NumpyMatrixOperator(B)
    Cop = NumpyMatrixOperator(C)

    Xop = BlockDiagonalOperator([LincombOperator([IdentityOperator(NumpyVectorSpace(2)), Aop],
                                                 [1, 1]), Concatenation(Bop, AdjointOperator(Cop))])

    assert np.allclose(X, to_matrix(Xop))
    assert np.allclose(X, to_matrix(Xop, format='csr').toarray())

    np.random.seed(0)
    V = np.random.randn(10, 2)
    Vva = NumpyVectorSpace.make_array(V.T)
    Vop = VectorArrayOperator(Vva)
    assert np.allclose(V, to_matrix(Vop))
    Vop = VectorArrayOperator(Vva, transposed=True)
    assert np.allclose(V, to_matrix(Vop).T)
Exemple #2
0
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)
Exemple #3
0
def test_to_matrix():
    np.random.seed(0)
    A = np.random.randn(2, 2)
    B = np.random.randn(3, 3)
    C = np.random.randn(3, 3)

    X = np.bmat([[np.eye(2) + A, np.zeros((2, 3))],
                 [np.zeros((3, 2)), B.dot(C.T)]])

    C = sps.csc_matrix(C)

    Aop = NumpyMatrixOperator(A)
    Bop = NumpyMatrixOperator(B)
    Cop = NumpyMatrixOperator(C)

    Xop = BlockDiagonalOperator([
        LincombOperator([IdentityOperator(NumpyVectorSpace(2)), Aop], [1, 1]),
        Concatenation(Bop, AdjointOperator(Cop))
    ])

    assert np.allclose(X, to_matrix(Xop))
    assert np.allclose(X, to_matrix(Xop, format='csr').toarray())

    np.random.seed(0)
    V = np.random.randn(10, 2)
    Vva = NumpyVectorArray(V.T)
    Vop = VectorArrayOperator(Vva)
    assert np.allclose(V, to_matrix(Vop))
    Vop = VectorArrayOperator(Vva, transposed=True)
    assert np.allclose(V, to_matrix(Vop).T)
Exemple #4
0
def _lti_to_poles_b_c(rom):
    """Compute poles and residues.

    Parameters
    ----------
    rom
        Reduced |LTIModel| (consisting of |NumpyMatrixOperators|).

    Returns
    -------
    poles
        1D |NumPy array| of poles.
    b
        |VectorArray| from `rom.B.source`.
    c
        |VectorArray| from `rom.C.range`.
    """
    A = to_matrix(rom.A, format='dense')
    B = to_matrix(rom.B, format='dense')
    C = to_matrix(rom.C, format='dense')
    if isinstance(rom.E, IdentityOperator):
        poles, X = spla.eig(A)
        EX = X
    else:
        E = to_matrix(rom.E, format='dense')
        poles, X = spla.eig(A, E)
        EX = E @ X
    b = rom.B.source.from_numpy(spla.solve(EX, B))
    c = rom.C.range.from_numpy((C @ X).T)
    return poles, b, c
Exemple #5
0
def _lti_to_poles_b_c(rom):
    """Compute poles and residues.

    Parameters
    ----------
    rom
        Reduced |LTIModel| (consisting of |NumpyMatrixOperators|).

    Returns
    -------
    poles
        1D |NumPy array| of poles.
    b
        |NumPy array| of shape `(rom.order, rom.dim_input)`.
    c
        |NumPy array| of shape `(rom.order, rom.dim_output)`.
    """
    A = to_matrix(rom.A, format='dense')
    B = to_matrix(rom.B, format='dense')
    C = to_matrix(rom.C, format='dense')
    if isinstance(rom.E, IdentityOperator):
        poles, X = spla.eig(A)
        EX = X
    else:
        E = to_matrix(rom.E, format='dense')
        poles, X = spla.eig(A, E)
        EX = E @ X
    b = spla.solve(EX, B)
    c = (C @ X).T
    return poles, b, c
Exemple #6
0
def get_gap_rom(rom):
    """Based on a rom, create model which is used to evaluate H2-Gap norm."""
    A = to_matrix(rom.A, format='dense')
    B = to_matrix(rom.B, format='dense')
    C = to_matrix(rom.C, format='dense')

    if isinstance(rom.E, IdentityOperator):
        P = spla.solve_continuous_are(A.T,
                                      C.T,
                                      B.dot(B.T),
                                      np.eye(len(C)),
                                      balanced=False)
        F = P @ C.T
    else:
        E = to_matrix(rom.E, format='dense')
        P = spla.solve_continuous_are(A.T,
                                      C.T,
                                      B.dot(B.T),
                                      np.eye(len(C)),
                                      e=E.T,
                                      balanced=False)
        F = E @ P @ C.T

    AF = A - F @ C
    mFB = np.concatenate((-F, B), axis=1)
    return LTIModel.from_matrices(
        AF, mFB, C, E=None if isinstance(rom.E, IdentityOperator) else E)
Exemple #7
0
        def __init__(self, opt, A, E, B, C):
            super().__init__(name='RiccatiEquation', opt=opt, dim=A.source.dim)

            self.a = A
            self.e = E
            self.b = to_matrix(B, format='dense')
            self.c = to_matrix(C, format='dense')
            self.rhs = self.b if opt.type == pymess.MESS_OP_NONE else self.c.T
            self.p = []
Exemple #8
0
def _poles_and_tangential_directions(rom):
    """Compute the poles and tangential directions of a reduced order model."""
    if isinstance(rom.E, IdentityOperator):
        poles, Y, X = spla.eig(to_matrix(rom.A, format='dense'),
                               left=True, right=True)
    else:
        poles, Y, X = spla.eig(to_matrix(rom.A, format='dense'), to_matrix(rom.E, format='dense'),
                               left=True, right=True)
    Y = rom.B.range.make_array(Y.conj().T)
    X = rom.C.source.make_array(X.T)
    b = rom.B.apply_adjoint(Y)
    c = rom.C.apply(X)
    return poles, b, c
Exemple #9
0
    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)
Exemple #10
0
    def _call_pymess_dense_nm_gmpare(A, E, B, C, R, S, trans=False, options=None, plus=False):
        """Return the solution from pymess.dense_nm_gmpare solver."""
        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

        Q = B.dot(B.T) if not trans else C.T.dot(C)
        pymess_trans = pymess.MESS_OP_NONE if not trans else pymess.MESS_OP_TRANSPOSE
        if not trans:
            RinvC = spla.solve(R, C) if R is not None else C
            G = C.T.dot(RinvC)
            if S is not None:
                RinvST = spla.solve(R, S.T) if R is not None else S.T
                if not plus:
                    A -= S.dot(RinvC)
                    Q -= S.dot(RinvST)
                else:
                    A += S.dot(RinvC)
                    Q += S.dot(RinvST)
        else:
            RinvBT = spla.solve(R, B.T) if R is not None else B.T
            G = B.dot(RinvBT)
            if S is not None:
                RinvST = spla.solve(R, S.T) if R is not None else S.T
                if not plus:
                    A -= RinvBT.T.dot(S.T)
                    Q -= S.dot(RinvST)
                else:
                    A += RinvBT.T.dot(S.T)
                    Q += S.dot(RinvST)
        X, absres, relres = pymess.dense_nm_gmpare(None,
                                                   A, E, Q, G,
                                                   plus=plus, trans=pymess_trans,
                                                   linesearch=options['linesearch'],
                                                   maxit=options['maxit'],
                                                   absres_tol=options['absres_tol'],
                                                   relres_tol=options['relres_tol'],
                                                   nrm=options['nrm'])
        if absres > options['absres_tol']:
            logger = getLogger('pymess.dense_nm_gmpcare')
            logger.warning(f'Desired absolute residual tolerance was not achieved '
                           f'({absres:e} > {options["absres_tol"]:e}).')
        if relres > options['relres_tol']:
            logger = getLogger('pymess.dense_nm_gmpcare')
            logger.warning(f'Desired relative residual tolerance was not achieved '
                           f'({relres:e} > {options["relres_tol"]:e}).')

        return X
Exemple #11
0
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)
Exemple #12
0
    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)
Exemple #13
0
def _poles_and_tangential_directions(rom):
    """Compute the poles and tangential directions of a reduced order model."""
    if isinstance(rom.E, IdentityOperator):
        poles, Y, X = spla.eig(to_matrix(rom.A, format='dense'),
                               left=True,
                               right=True)
    else:
        poles, Y, X = spla.eig(to_matrix(rom.A, format='dense'),
                               to_matrix(rom.E, format='dense'),
                               left=True,
                               right=True)
    Y = rom.B.range.make_array(Y.conj().T)
    X = rom.C.source.make_array(X.T)
    b = rom.B.apply_adjoint(Y)
    c = rom.C.apply(X)
    return poles, b, c
Exemple #14
0
def assert_type_and_allclose(A, Aop, default_format):
    if default_format == 'dense':
        assert isinstance(to_matrix(Aop), np.ndarray)
        assert np.allclose(A, to_matrix(Aop))
    elif default_format == 'sparse':
        assert sps.issparse(to_matrix(Aop))
        assert np.allclose(A, to_matrix(Aop).toarray())
    else:
        assert getattr(sps, 'isspmatrix_' + default_format)(to_matrix(Aop))
        assert np.allclose(A, to_matrix(Aop).toarray())

    assert isinstance(to_matrix(Aop, format='dense'), np.ndarray)
    assert np.allclose(A, to_matrix(Aop, format='dense'))

    assert sps.isspmatrix_csr(to_matrix(Aop, format='csr'))
    assert np.allclose(A, to_matrix(Aop, format='csr').toarray())
Exemple #15
0
def test_expand():
    ops = [NumpyMatrixOperator(np.eye(1) * i) for i in range(8)]
    pfs = [ProjectionParameterFunctional('p', 9, i) for i in range(8)]
    prods = [o * p for o, p in zip(ops, pfs)]

    op = ((prods[0] + prods[1] + prods[2]) @ (prods[3] + prods[4] + prods[5])
          @ (prods[6] + prods[7]))

    eop = expand(op)

    assert isinstance(eop, LincombOperator)
    assert len(eop.operators) == 3 * 3 * 2
    assert all(
        isinstance(o, ConcatenationOperator) and len(o.operators) == 3
        for o in eop.operators)
    assert ({to_matrix(o)[0, 0]
             for o in eop.operators} == {
                 i0 * i1 * i2
                 for i0, i1, i2 in product([0, 1, 2], [3, 4, 5], [6, 7])
             })
    assert ({
        frozenset(p.index for p in pf.factors)
        for pf in eop.coefficients
    } == {
        frozenset([i0, i1, i2])
        for i0, i1, i2 in product([0, 1, 2], [3, 4, 5], [6, 7])
    })
Exemple #16
0
def assert_type_and_allclose(A, Aop, default_format):
    if default_format == 'dense':
        assert isinstance(to_matrix(Aop), np.ndarray)
        assert np.allclose(A, to_matrix(Aop))
    elif default_format == 'sparse':
        assert sps.issparse(to_matrix(Aop))
        assert np.allclose(A, to_matrix(Aop).toarray())
    else:
        assert getattr(sps, 'isspmatrix_' + default_format)(to_matrix(Aop))
        assert np.allclose(A, to_matrix(Aop).toarray())

    assert isinstance(to_matrix(Aop, format='dense'), np.ndarray)
    assert np.allclose(A, to_matrix(Aop, format='dense'))

    assert sps.isspmatrix_csr(to_matrix(Aop, format='csr'))
    assert np.allclose(A, to_matrix(Aop, format='csr').toarray())
Exemple #17
0
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)
Exemple #18
0
        def __init__(self, opt, A, E, B):
            super().__init__(name='LyapunovEquation', opt=opt, dim=A.source.dim)

            self.a = A
            self.e = E
            self.rhs = to_matrix(B, format='dense')
            if opt.type == pymess.MESS_OP_TRANSPOSE:
                self.rhs = self.rhs.T
            self.p = []
Exemple #19
0
def test_identity_numpy_lincomb():
    n = 2
    space = NumpyVectorSpace(n)
    identity = IdentityOperator(space)
    numpy_operator = NumpyMatrixOperator(np.ones((n, n)))
    for alpha in [-1, 0, 1]:
        for beta in [-1, 0, 1]:
            idop = alpha * identity + beta * numpy_operator
            mat1 = alpha * np.eye(n) + beta * np.ones((n, n))
            mat2 = to_matrix(idop.assemble(), format='dense')
            assert np.array_equal(mat1, mat2)
Exemple #20
0
def test_identity_numpy_lincomb():
    n = 2
    space = NumpyVectorSpace(n)
    identity = IdentityOperator(space)
    numpy_operator = NumpyMatrixOperator(np.ones((n, n)))
    for alpha in [-1, 0, 1]:
        for beta in [-1, 0, 1]:
            idop = alpha * identity + beta * numpy_operator
            mat1 = alpha * np.eye(n) + beta * np.ones((n, n))
            mat2 = to_matrix(idop.assemble(), format='dense')
            assert np.array_equal(mat1, mat2)
Exemple #21
0
    def solve_lyap(A, E, B, trans=False, options=None, default_solver='pymess'):
        """Find a factor of the solution of a Lyapunov equation.

        Returns factor :math:`Z` such that :math:`Z Z^T` is
        approximately the solution :math:`X` of a Lyapunov equation (if
        E is `None`).

        .. math::
            A X + X A^T + B B^T = 0

        or a generalized Lyapunov equation

        .. math::
            A X E^T + E X A^T + B B^T = 0.

        If trans is `True`, then it solves (if E is `None`)

        .. math::
            A^T X + X A + B^T B = 0

        or

        .. math::
            A^T X E + E^T X A + B^T B = 0.

        This uses the `pymess` package, in particular its `lyap` and
        `lradi` methods.
        Both methods can be used for large-scale problems.
        The restrictions are:

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

        Parameters
        ----------
        A
            The |Operator| A.
        E
            The |Operator| E or `None`.
        B
            The |Operator| B.
        trans
            If the dual equation needs to be solved.
        options
            The |solver_options| to use (see
            :func:`lyap_solver_options`).
        default_solver
            The solver to use when no `options` are specified
            (`'pymess'`, `'pymess_lyap'`, or `'pymess_lradi'`).

        Returns
        -------
        Z
            Low-rank factor of the Lyapunov equation solution,
            |VectorArray| from `A.source`.
        """
        _solve_lyap_check_args(A, E, B, trans)
        options = _parse_options(options, lyap_solver_options(), default_solver, None, False)

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

        if options['type'] == 'pymess_lyap':
            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 not trans:
                Z = pymess.lyap(A_mat, E_mat, B_mat)
            else:
                if E is None:
                    Z = pymess.lyap(A_mat.T, None, B_mat.T)
                else:
                    Z = pymess.lyap(A_mat.T, E_mat.T, B_mat.T)
        elif options['type'] == 'pymess_lradi':
            opts = options['opts']
            if trans:
                opts.type = pymess.MESS_OP_TRANSPOSE
            else:
                opts.type = pymess.MESS_OP_NONE
            eqn = LyapunovEquation(opts, A, E, B)
            Z, status = pymess.lradi(eqn, opts)

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

        return Z
Exemple #22
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
Exemple #23
0
def solve_sylv_schur(A, Ar, E=None, Er=None, B=None, Br=None, C=None, Cr=None):
    r"""Solve Sylvester equation by Schur decomposition.

    Solves Sylvester equation

    .. math::
        A V E_r^T + E V A_r^T + B B_r^T = 0

    or

    .. math::
        A^T W E_r + E^T W A_r + C^T C_r = 0

    or both using (generalized) Schur decomposition (Algorithms 3 and 4
    in [BKS11]_), if the necessary parameters are given.

    Parameters
    ----------
    A
        Real |Operator|.
    Ar
        Real |Operator|.
        It is converted into a |NumPy array| using
        :func:`~pymor.algorithms.to_matrix.to_matrix`.
    E
        Real |Operator| or `None` (then assumed to be the identity).
    Er
        Real |Operator| or `None` (then assumed to be the identity).
        It is converted into a |NumPy array| using
        :func:`~pymor.algorithms.to_matrix.to_matrix`.
    B
        Real |Operator| or `None`.
    Br
        Real |Operator| or `None`.
        It is assumed that `Br.range.from_numpy` is implemented.
    C
        Real |Operator| or `None`.
    Cr
        Real |Operator| or `None`.
        It is assumed that `Cr.source.from_numpy` is implemented.

    Returns
    -------
    V
        Returned if `B` and `Br` are given, |VectorArray| from
        `A.source`.
    W
        Returned if `C` and `Cr` are given, |VectorArray| from
        `A.source`.

    Raises
    ------
    ValueError
        If `V` and `W` cannot be returned.
    """
    # check types
    assert isinstance(A, OperatorInterface) and A.linear and A.source == A.range
    assert isinstance(Ar, OperatorInterface) and Ar.linear and Ar.source == Ar.range

    assert E is None or isinstance(E, OperatorInterface) and E.linear and E.source == E.range == A.source
    if E is None:
        E = IdentityOperator(A.source)
    assert Er is None or isinstance(Er, OperatorInterface) and Er.linear and Er.source == Er.range == Ar.source

    compute_V = B is not None and Br is not None
    compute_W = C is not None and Cr is not None

    if not compute_V and not compute_W:
        raise ValueError('Not enough parameters are given to solve a Sylvester equation.')

    if compute_V:
        assert isinstance(B, OperatorInterface) and B.linear and B.range == A.source
        assert isinstance(Br, OperatorInterface) and Br.linear and Br.range == Ar.source
        assert B.source == Br.source

    if compute_W:
        assert isinstance(C, OperatorInterface) and C.linear and C.source == A.source
        assert isinstance(Cr, OperatorInterface) and Cr.linear and Cr.source == Ar.source
        assert C.range == Cr.range

    # convert reduced operators
    Ar = to_matrix(Ar, format='dense')
    r = Ar.shape[0]
    if Er is not None:
        Er = to_matrix(Er, format='dense')

    # (Generalized) Schur decomposition
    if Er is None:
        TAr, Z = spla.schur(Ar, output='complex')
        Q = Z
    else:
        TAr, TEr, Q, Z = spla.qz(Ar, Er, output='complex')

    # solve for V, from the last column to the first
    if compute_V:
        V = A.source.empty(reserve=r)

        BrTQ = Br.apply_adjoint(Br.range.from_numpy(Q.T))
        BBrTQ = B.apply(BrTQ)
        for i in range(-1, -r - 1, -1):
            rhs = -BBrTQ[i].copy()
            if i < -1:
                if Er is not None:
                    rhs -= A.apply(V.lincomb(TEr[i, :i:-1].conjugate()))
                rhs -= E.apply(V.lincomb(TAr[i, :i:-1].conjugate()))
            TErii = 1 if Er is None else TEr[i, i]
            eAaE = TErii.conjugate() * A + TAr[i, i].conjugate() * E
            V.append(eAaE.apply_inverse(rhs))

        V = V.lincomb(Z.conjugate()[:, ::-1])
        V = V.real

    # solve for W, from the first column to the last
    if compute_W:
        W = A.source.empty(reserve=r)

        CrZ = Cr.apply(Cr.source.from_numpy(Z.T))
        CTCrZ = C.apply_adjoint(CrZ)
        for i in range(r):
            rhs = -CTCrZ[i].copy()
            if i > 0:
                if Er is not None:
                    rhs -= A.apply_adjoint(W.lincomb(TEr[:i, i]))
                rhs -= E.apply_adjoint(W.lincomb(TAr[:i, i]))
            TErii = 1 if Er is None else TEr[i, i]
            eAaE = TErii.conjugate() * A + TAr[i, i].conjugate() * E
            W.append(eAaE.apply_inverse_adjoint(rhs))

        W = W.lincomb(Q.conjugate())
        W = W.real

    if compute_V and compute_W:
        return V, W
    elif compute_V:
        return V
    else:
        return W
Exemple #24
0
    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)
Exemple #25
0
def apply_inverse(op, V, options=None, least_squares=False, check_finite=True,
                  default_solver='scipy_spsolve', default_least_squares_solver='scipy_least_squares_lsmr'):
    """Solve linear equation system.

    Applies the inverse of `op` to the vectors in `rhs` using PyAMG.

    Parameters
    ----------
    op
        The linear, non-parametric |Operator| to invert.
    rhs
        |VectorArray| of right-hand sides for the equation system.
    options
        The |solver_options| to use (see :func:`solver_options`).
    check_finite
        Test if solution only containes finite values.
    default_solver
        Default solver to use (scipy_spsolve, scipy_bicgstab, scipy_bicgstab_spilu,
        scipy_lgmres, scipy_least_squares_lsmr, scipy_least_squares_lsqr).
    default_least_squares_solver
        Default solver to use for least squares problems (scipy_least_squares_lsmr,
        scipy_least_squares_lsqr).

    Returns
    -------
    |VectorArray| of the solution vectors.
    """

    assert V in op.range

    if isinstance(op, NumpyMatrixOperator):
        matrix = op._matrix
    else:
        from pymor.algorithms.to_matrix import to_matrix
        matrix = to_matrix(op)

    options = _parse_options(options, solver_options(), default_solver, default_least_squares_solver, least_squares)

    V = V.data
    promoted_type = np.promote_types(matrix.dtype, V.dtype)
    R = np.empty((len(V), matrix.shape[1]), dtype=promoted_type)

    if options['type'] == 'scipy_bicgstab':
        for i, VV in enumerate(V):
            R[i], info = bicgstab(matrix, VV, tol=options['tol'], maxiter=options['maxiter'])
            if info != 0:
                if info > 0:
                    raise InversionError('bicgstab failed to converge after {} iterations'.format(info))
                else:
                    raise InversionError('bicgstab failed with error code {} (illegal input or breakdown)'.
                                         format(info))
    elif options['type'] == 'scipy_bicgstab_spilu':
        if Version(scipy.version.version) >= Version('0.19'):
            ilu = spilu(matrix, drop_tol=options['spilu_drop_tol'], fill_factor=options['spilu_fill_factor'],
                        drop_rule=options['spilu_drop_rule'], permc_spec=options['spilu_permc_spec'])
        else:
            if options['spilu_drop_rule']:
                logger = getLogger('pymor.operators.numpy._apply_inverse')
                logger.error("ignoring drop_rule in ilu factorization due to old SciPy")
            ilu = spilu(matrix, drop_tol=options['spilu_drop_tol'], fill_factor=options['spilu_fill_factor'],
                        permc_spec=options['spilu_permc_spec'])
        precond = LinearOperator(matrix.shape, ilu.solve)
        for i, VV in enumerate(V):
            R[i], info = bicgstab(matrix, VV, tol=options['tol'], maxiter=options['maxiter'], M=precond)
            if info != 0:
                if info > 0:
                    raise InversionError('bicgstab failed to converge after {} iterations'.format(info))
                else:
                    raise InversionError('bicgstab failed with error code {} (illegal input or breakdown)'.
                                         format(info))
    elif options['type'] == 'scipy_spsolve':
        try:
            # maybe remove unusable factorization:
            if hasattr(matrix, 'factorization'):
                fdtype = matrix.factorizationdtype
                if not np.can_cast(V.dtype, fdtype, casting='safe'):
                    del matrix.factorization

            if Version(scipy.version.version) >= Version('0.14'):
                if hasattr(matrix, 'factorization'):
                    # we may use a complex factorization of a real matrix to
                    # apply it to a real vector. In that case, we downcast
                    # the result here, removing the imaginary part,
                    # which should be zero.
                    R = matrix.factorization.solve(V.T).T.astype(promoted_type, copy=False)
                elif options['keep_factorization']:
                    # the matrix is always converted to the promoted type.
                    # if matrix.dtype == promoted_type, this is a no_op
                    matrix.factorization = splu(matrix_astype_nocopy(matrix.tocsc(), promoted_type),
                                                permc_spec=options['permc_spec'])
                    matrix.factorizationdtype = promoted_type
                    R = matrix.factorization.solve(V.T).T
                else:
                    # the matrix is always converted to the promoted type.
                    # if matrix.dtype == promoted_type, this is a no_op
                    R = spsolve(matrix_astype_nocopy(matrix, promoted_type), V.T, permc_spec=options['permc_spec']).T
            else:
                # see if-part for documentation
                if hasattr(matrix, 'factorization'):
                    for i, VV in enumerate(V):
                        R[i] = matrix.factorization.solve(VV).astype(promoted_type, copy=False)
                elif options['keep_factorization']:
                    matrix.factorization = splu(matrix_astype_nocopy(matrix.tocsc(), promoted_type),
                                                permc_spec=options['permc_spec'])
                    matrix.factorizationdtype = promoted_type
                    for i, VV in enumerate(V):
                        R[i] = matrix.factorization.solve(VV)
                elif len(V) > 1:
                    factorization = splu(matrix_astype_nocopy(matrix.tocsc(), promoted_type),
                                         permc_spec=options['permc_spec'])
                    for i, VV in enumerate(V):
                        R[i] = factorization.solve(VV)
                else:
                    R = spsolve(matrix_astype_nocopy(matrix, promoted_type), V.T, permc_spec=options['permc_spec']).reshape((1, -1))
        except RuntimeError as e:
            raise InversionError(e)
    elif options['type'] == 'scipy_lgmres':
        for i, VV in enumerate(V):
            R[i], info = lgmres(matrix, VV,
                                tol=options['tol'],
                                maxiter=options['maxiter'],
                                inner_m=options['inner_m'],
                                outer_k=options['outer_k'])
            if info > 0:
                raise InversionError('lgmres failed to converge after {} iterations'.format(info))
            assert info == 0
    elif options['type'] == 'scipy_least_squares_lsmr':
        from scipy.sparse.linalg import lsmr
        for i, VV in enumerate(V):
            R[i], info, itn, _, _, _, _, _ = lsmr(matrix, VV,
                                                  damp=options['damp'],
                                                  atol=options['atol'],
                                                  btol=options['btol'],
                                                  conlim=options['conlim'],
                                                  maxiter=options['maxiter'],
                                                  show=options['show'])
            assert 0 <= info <= 7
            if info == 7:
                raise InversionError('lsmr failed to converge after {} iterations'.format(itn))
    elif options['type'] == 'scipy_least_squares_lsqr':
        for i, VV in enumerate(V):
            R[i], info, itn, _, _, _, _, _, _, _ = lsqr(matrix, VV,
                                                        damp=options['damp'],
                                                        atol=options['atol'],
                                                        btol=options['btol'],
                                                        conlim=options['conlim'],
                                                        iter_lim=options['iter_lim'],
                                                        show=options['show'])
            assert 0 <= info <= 7
            if info == 7:
                raise InversionError('lsmr failed to converge after {} iterations'.format(itn))
    else:
        raise ValueError('Unknown solver type')

    if check_finite:
        if not np.isfinite(np.sum(R)):
            raise InversionError('Result contains non-finite values')

    return op.source.from_data(R)
Exemple #26
0
    def solve_ricc(A, E=None, B=None, Q=None, C=None, R=None, G=None, trans=False, options=None):
        """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 `slycot` package, in particular its interfaces to
        SLICOT functions `SB02MD` (for the standard Riccati equations)
        and `SG02AD` (for the generalized Riccati equations).
        These methods are only applicable to medium-sized dense
        problems and need access to the matrix data of all operators.

        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`).

        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(), 'slycot', None, False)
        assert options['type'] == 'slycot'

        import slycot
        A_mat = to_matrix(A, format='dense')
        B_mat = to_matrix(B, format='dense') if B else None
        C_mat = to_matrix(C, format='dense') if C else None
        R_mat = to_matrix(R, format='dense') if R else None
        G_mat = to_matrix(G, format='dense') if G else None
        Q_mat = to_matrix(Q, format='dense') if Q else None

        n = A_mat.shape[0]
        dico = 'C'

        if E is None:
            if not trans:
                if G is None:
                    if R is None:
                        G_mat = B_mat.dot(B_mat.T)
                    else:
                        G_mat = slycot.sb02mt(n, B_mat.shape[1], B_mat, R_mat)[-1]
                if C is not None:
                    Q_mat = C_mat.T.dot(C_mat)
                X = slycot.sb02md(n, A_mat, G_mat, Q_mat, dico)[0]
            else:
                if G is None:
                    if R is None:
                        G_mat = C_mat.T.dot(C_mat)
                    else:
                        G_mat = slycot.sb02mt(n, C_mat.shape[0], C_mat.T, R_mat)[-1]
                if B is not None:
                    Q_mat = B_mat.dot(B_mat.T)
                X = slycot.sb02md(n, A_mat.T, G_mat, Q_mat, dico)[0]
        else:
            E_mat = to_matrix(E, format='dense') if E else None
            jobb = 'B' if G is None else 'B'
            fact = 'C' if Q is None else 'N'
            uplo = 'U'
            jobl = 'Z'
            scal = 'N'
            sort = 'S'
            acc = 'R'
            if not trans:
                m = 0 if B is None else B_mat.shape[1]
                p = 0 if C is None else C_mat.shape[0]
                if G is not None:
                    B_mat = G_mat
                    R_mat = np.empty((1, 1))
                elif R is None:
                    R_mat = np.eye(m)
                if Q is None:
                    Q_mat = C_mat
                L_mat = np.empty((n, m))
                ret = slycot.sg02ad(dico, jobb, fact, uplo, jobl, scal, sort, acc, n, m, p,
                                    A_mat, E_mat, B_mat, Q_mat, R_mat, L_mat)
            else:
                m = 0 if C is None else C_mat.shape[0]
                p = 0 if B is None else B_mat.shape[1]
                if G is not None:
                    C_mat = G_mat
                    R_mat = np.empty((1, 1))
                elif R is None:
                    C_mat = C_mat.T
                    R_mat = np.eye(m)
                else:
                    C_mat = C_mat.T
                if Q is None:
                    Q_mat = B_mat.T
                L_mat = np.empty((n, m))
                ret = slycot.sg02ad(dico, jobb, fact, uplo, jobl, scal, sort, acc, n, m, p,
                                    A_mat.T, E_mat.T, C_mat, Q_mat, R_mat, L_mat)
            X = ret[1]
            iwarn = ret[-1]
            if iwarn == 1:
                print('slycot.sg02ad warning: solution may be inaccurate.')

        from pymor.bindings.scipy import chol
        Z = chol(X, copy=False)
        Z = A.source.from_numpy(np.array(Z).T)

        return Z
Exemple #27
0
def apply_inverse(op,
                  V,
                  initial_guess=None,
                  options=None,
                  least_squares=False,
                  check_finite=True,
                  default_solver='scipy_spsolve',
                  default_least_squares_solver='scipy_least_squares_lsmr'):
    """Solve linear equation system.

    Applies the inverse of `op` to the vectors in `V` using SciPy.

    Parameters
    ----------
    op
        The linear, non-parametric |Operator| to invert.
    V
        |VectorArray| of right-hand sides for the equation system.
    initial_guess
        |VectorArray| with the same length as `V` containing initial guesses
        for the solution.  Some implementations of `apply_inverse` may
        ignore this parameter.  If `None` a solver-dependent default is used.
    options
        The |solver_options| to use (see :func:`solver_options`).
    least_squares
        If `True`, return least squares solution.
    check_finite
        Test if solution only contains finite values.
    default_solver
        Default solver to use (scipy_spsolve, scipy_bicgstab, scipy_bicgstab_spilu,
        scipy_lgmres, scipy_least_squares_lsmr, scipy_least_squares_lsqr).
    default_least_squares_solver
        Default solver to use for least squares problems (scipy_least_squares_lsmr,
        scipy_least_squares_lsqr).

    Returns
    -------
    |VectorArray| of the solution vectors.
    """

    assert V in op.range
    assert initial_guess is None or initial_guess in op.source and len(
        initial_guess) == len(V)

    if isinstance(op, NumpyMatrixOperator):
        matrix = op.matrix
    else:
        from pymor.algorithms.to_matrix import to_matrix
        matrix = to_matrix(op)

    options = _parse_options(options, solver_options(), default_solver,
                             default_least_squares_solver, least_squares)

    V = V.to_numpy()
    initial_guess = initial_guess.to_numpy(
    ) if initial_guess is not None else None
    promoted_type = np.promote_types(matrix.dtype, V.dtype)
    R = np.empty((len(V), matrix.shape[1]), dtype=promoted_type)

    if options['type'] == 'scipy_bicgstab':
        for i, VV in enumerate(V):
            R[i], info = bicgstab(
                matrix,
                VV,
                initial_guess[i] if initial_guess is not None else None,
                tol=options['tol'],
                maxiter=options['maxiter'])
            if info != 0:
                if info > 0:
                    raise InversionError(
                        f'bicgstab failed to converge after {info} iterations')
                else:
                    raise InversionError(
                        'bicgstab failed with error code {} (illegal input or breakdown)'
                        .format(info))
    elif options['type'] == 'scipy_bicgstab_spilu':
        ilu = spilu(matrix,
                    drop_tol=options['spilu_drop_tol'],
                    fill_factor=options['spilu_fill_factor'],
                    drop_rule=options['spilu_drop_rule'],
                    permc_spec=options['spilu_permc_spec'])
        precond = LinearOperator(matrix.shape, ilu.solve)
        for i, VV in enumerate(V):
            R[i], info = bicgstab(
                matrix,
                VV,
                initial_guess[i] if initial_guess is not None else None,
                tol=options['tol'],
                maxiter=options['maxiter'],
                M=precond)
            if info != 0:
                if info > 0:
                    raise InversionError(
                        f'bicgstab failed to converge after {info} iterations')
                else:
                    raise InversionError(
                        'bicgstab failed with error code {} (illegal input or breakdown)'
                        .format(info))
    elif options['type'] == 'scipy_spsolve':
        try:
            # maybe remove unusable factorization:
            if hasattr(matrix, 'factorization'):
                fdtype = matrix.factorizationdtype
                if not np.can_cast(V.dtype, fdtype, casting='safe'):
                    del matrix.factorization

            if hasattr(matrix, 'factorization'):
                # we may use a complex factorization of a real matrix to
                # apply it to a real vector. In that case, we downcast
                # the result here, removing the imaginary part,
                # which should be zero.
                R = matrix.factorization.solve(V.T).T.astype(promoted_type,
                                                             copy=False)
            elif options['keep_factorization']:
                # the matrix is always converted to the promoted type.
                # if matrix.dtype == promoted_type, this is a no_op
                matrix.factorization = splu(matrix_astype_nocopy(
                    matrix.tocsc(), promoted_type),
                                            permc_spec=options['permc_spec'])
                matrix.factorizationdtype = promoted_type
                R = matrix.factorization.solve(V.T).T
            else:
                # the matrix is always converted to the promoted type.
                # if matrix.dtype == promoted_type, this is a no_op
                R = spsolve(matrix_astype_nocopy(matrix, promoted_type),
                            V.T,
                            permc_spec=options['permc_spec']).T
        except RuntimeError as e:
            raise InversionError(e)
    elif options['type'] == 'scipy_lgmres':
        for i, VV in enumerate(V):
            R[i], info = lgmres(
                matrix,
                VV,
                initial_guess[i] if initial_guess is not None else None,
                tol=options['tol'],
                atol=options['tol'],
                maxiter=options['maxiter'],
                inner_m=options['inner_m'],
                outer_k=options['outer_k'])
            if info > 0:
                raise InversionError(
                    f'lgmres failed to converge after {info} iterations')
            assert info == 0
    elif options['type'] == 'scipy_least_squares_lsmr':
        from scipy.sparse.linalg import lsmr
        for i, VV in enumerate(V):
            R[i], info, itn, _, _, _, _, _ = lsmr(
                matrix,
                VV,
                damp=options['damp'],
                atol=options['atol'],
                btol=options['btol'],
                conlim=options['conlim'],
                maxiter=options['maxiter'],
                show=options['show'],
                x0=initial_guess[i] if initial_guess is not None else None)
            assert 0 <= info <= 7
            if info == 7:
                raise InversionError(
                    f'lsmr failed to converge after {itn} iterations')
    elif options['type'] == 'scipy_least_squares_lsqr':
        for i, VV in enumerate(V):
            R[i], info, itn, _, _, _, _, _, _, _ = lsqr(
                matrix,
                VV,
                damp=options['damp'],
                atol=options['atol'],
                btol=options['btol'],
                conlim=options['conlim'],
                iter_lim=options['iter_lim'],
                show=options['show'],
                x0=initial_guess[i] if initial_guess is not None else None)
            assert 0 <= info <= 7
            if info == 7:
                raise InversionError(
                    f'lsmr failed to converge after {itn} iterations')
    else:
        raise ValueError('Unknown solver type')

    if check_finite:
        if not np.isfinite(np.sum(R)):
            raise InversionError('Result contains non-finite values')

    return op.source.from_numpy(R)
Exemple #28
0
def solve_lyap(A, E, B, trans=False, options=None):
    """Find a factor of the solution of a Lyapunov equation.

    Returns factor :math:`Z` such that :math:`Z Z^T` is approximately
    the solution :math:`X` of a Lyapunov equation (if E is `None`).

    .. math::
        A X + X A^T + B B^T = 0

    or generalized Lyapunov equation

    .. math::
        A X E^T + E X A^T + B B^T = 0.

    If trans is `True`, then it solves (if E is `None`)

    .. math::
        A^T X + X A + B^T B = 0

    or

    .. math::
        A^T X E + E^T X A + B^T B = 0.

    This uses the `scipy.linalg.spla.solve_continuous_lyapunov` method.
    It is only applicable to the standard Lyapunov equation (E = I).
    Furthermore, it can only solve medium-sized dense problems and
    assumes access to the matrix data of all operators.

    Parameters
    ----------
    A
        The |Operator| A.
    E
        The |Operator| E or `None`.
    B
        The |Operator| B.
    trans
        If the dual equation needs to be solved.
    options
        The |solver_options| to use (see :func:`lyap_solver_options`).

    Returns
    -------
    Z
        Low-rank factor of the Lyapunov equation solution, |VectorArray|
        from `A.source`.
    """
    _solve_lyap_check_args(A, E, B, trans)
    options = _parse_options(options, lyap_solver_options(), 'scipy', None, False)
    assert options['type'] == 'scipy'

    if E is not None:
        raise NotImplementedError
    import scipy.linalg as spla
    A_mat = to_matrix(A, format='dense')
    B_mat = to_matrix(B, format='dense')
    if not trans:
        X = spla.solve_continuous_lyapunov(A_mat, -B_mat.dot(B_mat.T))
    else:
        X = spla.solve_continuous_lyapunov(A_mat.T, -B_mat.T.dot(B_mat))

    Z = chol(X, copy=False)
    Z = A.source.from_numpy(np.array(Z).T)

    return Z
Exemple #29
0
def solve_ricc(A, E=None, B=None, Q=None, C=None, R=None, G=None,
               trans=False, options=None):
    """Find a factor of the solution of a Riccati equation using solve_continuous_are.

    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 `scipy.linalg.spla.solve_continuous_are` method.
    Generalized Riccati equation is not supported.
    It can only solve medium-sized dense problems and assumes access to
    the matrix data of all operators.

    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`).

    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, lyap_solver_options(), 'scipy', None, False)
    assert options['type'] == 'scipy'

    if E is not None or G is not None:
        raise NotImplementedError

    import scipy.linalg as spla
    A_mat = to_matrix(A, format='dense')
    B_mat = to_matrix(B, format='dense') if B else None
    C_mat = to_matrix(C, format='dense') if C else None
    Q_mat = to_matrix(Q, format='dense') if Q else None
    R_mat = to_matrix(R, format='dense') if R else None

    if R is None:
        if not trans:
            R_mat = np.eye(B.source.dim)
        else:
            R_mat = np.eye(C.range.dim)
    if not trans:
        if Q is None:
            Q_mat = C_mat.T.dot(C_mat)
        X = spla.solve_continuous_are(A_mat, B_mat, Q_mat, R_mat)
    else:
        if Q is None:
            Q_mat = B_mat.dot(B_mat.T)
        X = spla.solve_continuous_are(A_mat.T, C_mat.T, Q_mat, R_mat)

    Z = chol(X, copy=False)
    Z = A.source.from_numpy(np.array(Z).T)

    return Z
Exemple #30
0
    def apply_inverse(op, V, options=None, least_squares=False, check_finite=True, default_solver='pyamg_solve'):
        """Solve linear equation system.

        Applies the inverse of `op` to the vectors in `rhs` using PyAMG.

        Parameters
        ----------
        op
            The linear, non-parametric |Operator| to invert.
        rhs
            |VectorArray| of right-hand sides for the equation system.
        options
            The |solver_options| to use (see :func:`solver_options`).
        check_finite
            Test if solution only containes finite values.
        default_solver
            Default solver to use (pyamg_solve, pyamg_rs, pyamg_sa).

        Returns
        -------
        |VectorArray| of the solution vectors.
        """

        assert V in op.range

        if isinstance(op, NumpyMatrixOperator):
            matrix = op._matrix
        else:
            from pymor.algorithms.to_matrix import to_matrix
            matrix = to_matrix(op)

        options = _parse_options(options, solver_options(), default_solver, None, least_squares)

        V = V.data
        promoted_type = np.promote_types(matrix.dtype, V.dtype)
        R = np.empty((len(V), matrix.shape[1]), dtype=promoted_type)

        if options['type'] == 'pyamg_solve':
            if len(V) > 0:
                V_iter = iter(enumerate(V))
                R[0], ml = pyamg.solve(matrix, next(V_iter)[1],
                                       tol=options['tol'],
                                       maxiter=options['maxiter'],
                                       return_solver=True)
                for i, VV in V_iter:
                    R[i] = pyamg.solve(matrix, VV,
                                       tol=options['tol'],
                                       maxiter=options['maxiter'],
                                       existing_solver=ml)
        elif options['type'] == 'pyamg_rs':
            ml = pyamg.ruge_stuben_solver(matrix,
                                          strength=options['strength'],
                                          CF=options['CF'],
                                          presmoother=options['presmoother'],
                                          postsmoother=options['postsmoother'],
                                          max_levels=options['max_levels'],
                                          max_coarse=options['max_coarse'],
                                          coarse_solver=options['coarse_solver'])
            for i, VV in enumerate(V):
                R[i] = ml.solve(VV,
                                tol=options['tol'],
                                maxiter=options['maxiter'],
                                cycle=options['cycle'],
                                accel=options['accel'])
        elif options['type'] == 'pyamg_sa':
            ml = pyamg.smoothed_aggregation_solver(matrix,
                                                   symmetry=options['symmetry'],
                                                   strength=options['strength'],
                                                   aggregate=options['aggregate'],
                                                   smooth=options['smooth'],
                                                   presmoother=options['presmoother'],
                                                   postsmoother=options['postsmoother'],
                                                   improve_candidates=options['improve_candidates'],
                                                   max_levels=options['max_levels'],
                                                   max_coarse=options['max_coarse'],
                                                   diagonal_dominance=options['diagonal_dominance'])
            for i, VV in enumerate(V):
                R[i] = ml.solve(VV,
                                tol=options['tol'],
                                maxiter=options['maxiter'],
                                cycle=options['cycle'],
                                accel=options['accel'])
        else:
            raise ValueError('Unknown solver type')

        if check_finite:
            if not np.isfinite(np.sum(R)):
                raise InversionError('Result contains non-finite values')

        return op.source.from_data(R)
Exemple #31
0
    def apply_inverse(op,
                      V,
                      options=None,
                      least_squares=False,
                      check_finite=True,
                      default_solver='pyamg_solve'):
        """Solve linear equation system.

        Applies the inverse of `op` to the vectors in `rhs` using PyAMG.

        Parameters
        ----------
        op
            The linear, non-parametric |Operator| to invert.
        rhs
            |VectorArray| of right-hand sides for the equation system.
        options
            The |solver_options| to use (see :func:`solver_options`).
        least_squares
            Must be `False`.
        check_finite
            Test if solution only contains finite values.
        default_solver
            Default solver to use (pyamg_solve, pyamg_rs, pyamg_sa).

        Returns
        -------
        |VectorArray| of the solution vectors.
        """

        assert V in op.range

        if least_squares:
            raise NotImplementedError

        if isinstance(op, NumpyMatrixOperator):
            matrix = op.matrix
        else:
            from pymor.algorithms.to_matrix import to_matrix
            matrix = to_matrix(op)

        options = _parse_options(options, solver_options(), default_solver,
                                 None, least_squares)

        V = V.to_numpy()
        promoted_type = np.promote_types(matrix.dtype, V.dtype)
        R = np.empty((len(V), matrix.shape[1]), dtype=promoted_type)

        if options['type'] == 'pyamg_solve':
            if len(V) > 0:
                V_iter = iter(enumerate(V))
                R[0], ml = pyamg.solve(matrix,
                                       next(V_iter)[1],
                                       tol=options['tol'],
                                       maxiter=options['maxiter'],
                                       return_solver=True)
                for i, VV in V_iter:
                    R[i] = pyamg.solve(matrix,
                                       VV,
                                       tol=options['tol'],
                                       maxiter=options['maxiter'],
                                       existing_solver=ml)
        elif options['type'] == 'pyamg_rs':
            ml = pyamg.ruge_stuben_solver(
                matrix,
                strength=options['strength'],
                CF=options['CF'],
                presmoother=options['presmoother'],
                postsmoother=options['postsmoother'],
                max_levels=options['max_levels'],
                max_coarse=options['max_coarse'],
                coarse_solver=options['coarse_solver'])
            for i, VV in enumerate(V):
                R[i] = ml.solve(VV,
                                tol=options['tol'],
                                maxiter=options['maxiter'],
                                cycle=options['cycle'],
                                accel=options['accel'])
        elif options['type'] == 'pyamg_sa':
            ml = pyamg.smoothed_aggregation_solver(
                matrix,
                symmetry=options['symmetry'],
                strength=options['strength'],
                aggregate=options['aggregate'],
                smooth=options['smooth'],
                presmoother=options['presmoother'],
                postsmoother=options['postsmoother'],
                improve_candidates=options['improve_candidates'],
                max_levels=options['max_levels'],
                max_coarse=options['max_coarse'],
                diagonal_dominance=options['diagonal_dominance'])
            for i, VV in enumerate(V):
                R[i] = ml.solve(VV,
                                tol=options['tol'],
                                maxiter=options['maxiter'],
                                cycle=options['cycle'],
                                accel=options['accel'])
        else:
            raise ValueError('Unknown solver type')

        if check_finite:
            if not np.isfinite(np.sum(R)):
                raise InversionError('Result contains non-finite values')

        return op.source.from_numpy(R)
Exemple #32
0
    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)
Exemple #33
0
    def solve_lyap(A, E, B, trans=False, options=None):
        """Find a factor of the solution of a Lyapunov equation.

        Returns factor :math:`Z` such that :math:`Z Z^T` is
        approximately the solution :math:`X` of a Lyapunov equation (if
        E is `None`).

        .. math::
            A X + X A^T + B B^T = 0

        or generalized Lyapunov equation

        .. math::
            A X E^T + E X A^T + B B^T = 0.

        If trans is `True`, then it solves (if E is `None`)

        .. math::
            A^T X + X A + B^T B = 0

        or

        .. math::
            A^T X E + E^T X A + B^T B = 0.

        This uses the `slycot` package, in particular its interfaces to
        SLICOT functions `SB03MD` (for the standard Lyapunov equations)
        and `SG03AD` (for the generalized Lyapunov equations).
        These methods are only applicable to medium-sized dense
        problems and need access to the matrix data of all operators.

        Parameters
        ----------
        A
            The |Operator| A.
        E
            The |Operator| E or `None`.
        B
            The |Operator| B.
        trans
            If the dual equation needs to be solved.
        options
            The |solver_options| to use (see
            :func:`lyap_solver_options`).

        Returns
        -------
        Z
            Low-rank factor of the Lyapunov equation solution, |VectorArray| from `A.source`.
        """
        _solve_lyap_check_args(A, E, B, trans)
        options = _parse_options(options, lyap_solver_options(), 'slycot', None, False)
        assert options['type'] == 'slycot'

        import slycot
        A_mat = to_matrix(A, format='dense')
        if E is not None:
            E_mat = to_matrix(E, format='dense')
        B_mat = to_matrix(B, format='dense')

        n = A_mat.shape[0]
        if not trans:
            C = -B_mat.dot(B_mat.T)
            trana = 'T'
        else:
            C = -B_mat.T.dot(B_mat)
            trana = 'N'
        dico = 'C'

        if E is None:
            U = np.zeros((n, n))
            X, scale, _, _, _ = slycot.sb03md(n, C, A_mat, U, dico, trana=trana)
        else:
            job = 'B'
            fact = 'N'
            Q = np.zeros((n, n))
            Z = np.zeros((n, n))
            uplo = 'L'
            X = C
            _, _, _, _, X, scale, _, _, _, _, _ = slycot.sg03ad(dico, job, fact, trana, uplo, n, A_mat, E_mat,
                                                                Q, Z, X)

        from pymor.bindings.scipy import chol
        Z = chol(X, copy=False)

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

        return Z
Exemple #34
0
    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)
Exemple #35
0
def run_cl_simulation(rom, name, Re=110, level=2, palpha=1e-3, control='bc'):
    """Run the closed-loop simulation with reduced LQG controller."""

    # define strings, directories and paths for data storage
    setup_str = 'lvl_' + str(level) + ('_' + control if control is not None else '') \
                + '_re_' + str(Re) + ('_palpha_' + str(palpha) if control == 'bc' else '')
    data_path = '../data/' + 'lvl_' + str(level) + ('_' + control if control
                                                    is not None else '')
    setup_path = data_path + '/re_' + str(Re) + ('_palpha_' + str(palpha)
                                                 if control == 'bc' else '')
    simulation_path = setup_str + '/' + name + '_simulation'

    if not os.path.exists(simulation_path):
        os.makedirs(simulation_path)

    with open(simulation_path + '/rom_' + str(rom.order) + '.csv',
              'w') as file:
        file.write('t, yerr \n')

    # define first order model and matrices for simulation
    fom = load_fom(Re=110, level=2, palpha=1e-3, control='bc')

    mats = spio.loadmat(data_path + '/mats')

    M = mats['M']
    J = mats['J']
    hmat = mats['H']
    fv = mats['fv'] + 1. / Re * mats['fv_diff'] + mats['fv_conv']
    fp = mats['fp'] + mats['fp_div']
    vcmat = mats['Cv']
    NV, NP = fv.shape[0], fp.shape[0]

    if control == 'bc':
        A = 1. / Re * mats['A'] + mats['L1'] + mats[
            'L2'] + 1. / palpha * mats['Arob']
        B = 1. / palpha * mats['Brob']
    else:
        A = 1. / Re * mats['A'] + mats['L1'] + mats['L2']
        B = mats['B']
        # restrict to less dofs in the input
        NU = B.shape[1]
        B = B[:, [0, NU // 2]]

    # compute steady-state solution and linearized convection
    if not os.path.isfile(setup_path + '/ss_nse_sol'):
        ss_nse_v, _ = solve_steadystate_nse(mats, Re, control, palpha=palpha)
        conv_mat = linearized_convection(mats['H'], ss_nse_v)
        spio.savemat(setup_path + '/ss_nse_sol', {
            'ss_nse_v': ss_nse_v,
            'conv_mat': conv_mat
        })
    else:
        ss_nse_sol = spio.loadmat(setup_path + '/ss_nse_sol')
        ss_nse_v, conv_mat = ss_nse_sol['ss_nse_v'], ss_nse_sol['conv_mat']

    # Define parameters for time stepping
    t0 = 0.
    tE = 8.
    Nts = 2**12
    DT = (tE - t0) / Nts
    trange = np.linspace(t0, tE, Nts + 1)

    # Define functions that represent the system inputs
    if control is None:

        def bbcu(ko_state):
            return np.zeros((NV, 1))

        def update_ko_state(ko_state, Cv, DT):
            return ko_state

    else:
        Arom = to_matrix(rom.A, format='dense')  # .real?
        Brom = to_matrix(rom.B, format='dense')
        Crom = to_matrix(rom.C, format='dense')

        XCARE = spla.solve_continuous_are(Arom,
                                          Brom,
                                          Crom.T @ Crom,
                                          np.eye(Brom.shape[1]),
                                          balanced=False)
        XFARE = spla.solve_continuous_are(Arom.T,
                                          Crom.T,
                                          Brom @ Brom.T,
                                          np.eye(Crom.shape[0]),
                                          balanced=False)

        # define control based on Kalman observer state
        def bbcu(ko_state):
            uvec = -Brom.T @ XCARE @ ko_state
            return B @ uvec

        ko1_mat = Arom - XFARE @ Crom.T @ Crom - Brom @ Brom.T @ XCARE
        ko2_mat = XFARE @ Crom.T
        lu_piv = spla.lu_factor(np.eye(rom.order) - DT * ko1_mat)

        Css = vcmat @ ss_nse_v

        # function that determines the next state of the Kalman observer via implicit euler step
        def update_ko_state(ko_state, Cv, DT):
            return spla.lu_solve(lu_piv, ko_state + DT * ko2_mat @ (Cv - Css))

    # introduce small perturbation to steady-state solution as initial value
    pert = fom.A.source.project_onto_subspace(fom.A.operator.source.ones(),
                                              trans=True).to_numpy().T
    old_v = ss_nse_v + 1e-3 * pert

    # initialize state for observer
    ko_state = np.zeros((rom.order, 1))

    sysmat = sps.vstack([
        sps.hstack([M + DT * A, -J.T]),
        sps.hstack([J, sps.csc_matrix((NP, NP))])
    ]).tocsc()
    sysmati = spsla.factorized(sysmat)

    try:
        for k, t in enumerate(trange):
            crhsv = M * old_v + DT * (fv - eva_quadterm(hmat, old_v) +
                                      bbcu(ko_state))
            crhs = np.vstack([crhsv, fp])
            vp_new = np.atleast_2d(sysmati(crhs.flatten())).T
            old_v = vp_new[:NV]
            Cv = vcmat @ old_v
            ko_state = update_ko_state(ko_state, Cv, DT)

            print(k, '/', Nts)
            print(spla.norm(Cv - Css, 2))

            with open(simulation_path + '/rom_' + str(rom.order) + '.csv',
                      'a') as file:
                file.write(str(t) + ',' + str(spla.norm(Cv - Css, 2)) + '\n')
    except:
        with open('simulation_error_log.txt', 'a') as file:
            file.write(name + '_' + str(rom.order) + '\n')
Exemple #36
0
    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)
Exemple #37
0
def solve_sylv_schur(A, Ar, E=None, Er=None, B=None, Br=None, C=None, Cr=None):
    r"""Solve Sylvester equation by Schur decomposition.

    Solves Sylvester equation

    .. math::
        A V E_r^T + E V A_r^T + B B_r^T = 0

    or

    .. math::
        A^T W E_r + E^T W A_r + C^T C_r = 0

    or both using (generalized) Schur decomposition (Algorithms 3 and 4
    in [BKS11]_), if the necessary parameters are given.

    Parameters
    ----------
    A
        Real |Operator|.
    Ar
        Real |Operator|.
        It is converted into a |NumPy array| using
        :func:`~pymor.algorithms.to_matrix.to_matrix`.
    E
        Real |Operator| or `None` (then assumed to be the identity).
    Er
        Real |Operator| or `None` (then assumed to be the identity).
        It is converted into a |NumPy array| using
        :func:`~pymor.algorithms.to_matrix.to_matrix`.
    B
        Real |Operator| or `None`.
    Br
        Real |Operator| or `None`.
        It is assumed that `Br.range.from_numpy` is implemented.
    C
        Real |Operator| or `None`.
    Cr
        Real |Operator| or `None`.
        It is assumed that `Cr.source.from_numpy` is implemented.

    Returns
    -------
    V
        Returned if `B` and `Br` are given, |VectorArray| from
        `A.source`.
    W
        Returned if `C` and `Cr` are given, |VectorArray| from
        `A.source`.

    Raises
    ------
    ValueError
        If `V` and `W` cannot be returned.
    """
    # check types
    assert isinstance(A,
                      OperatorInterface) and A.linear and A.source == A.range
    assert isinstance(
        Ar, OperatorInterface) and Ar.linear and Ar.source == Ar.range

    assert E is None or isinstance(
        E, OperatorInterface) and E.linear and E.source == E.range == A.source
    if E is None:
        E = IdentityOperator(A.source)
    assert Er is None or isinstance(
        Er,
        OperatorInterface) and Er.linear and Er.source == Er.range == Ar.source

    compute_V = B is not None and Br is not None
    compute_W = C is not None and Cr is not None

    if not compute_V and not compute_W:
        raise ValueError(
            'Not enough parameters are given to solve a Sylvester equation.')

    if compute_V:
        assert isinstance(
            B, OperatorInterface) and B.linear and B.range == A.source
        assert isinstance(
            Br, OperatorInterface) and Br.linear and Br.range == Ar.source
        assert B.source == Br.source

    if compute_W:
        assert isinstance(
            C, OperatorInterface) and C.linear and C.source == A.source
        assert isinstance(
            Cr, OperatorInterface) and Cr.linear and Cr.source == Ar.source
        assert C.range == Cr.range

    # convert reduced operators
    Ar = to_matrix(Ar, format='dense')
    r = Ar.shape[0]
    if Er is not None:
        Er = to_matrix(Er, format='dense')

    # (Generalized) Schur decomposition
    if Er is None:
        TAr, Z = spla.schur(Ar, output='complex')
        Q = Z
    else:
        TAr, TEr, Q, Z = spla.qz(Ar, Er, output='complex')

    # solve for V, from the last column to the first
    if compute_V:
        V = A.source.empty(reserve=r)

        BrTQ = Br.apply_adjoint(Br.range.from_numpy(Q.T))
        BBrTQ = B.apply(BrTQ)
        for i in range(-1, -r - 1, -1):
            rhs = -BBrTQ[i].copy()
            if i < -1:
                if Er is not None:
                    rhs -= A.apply(V.lincomb(TEr[i, :i:-1].conjugate()))
                rhs -= E.apply(V.lincomb(TAr[i, :i:-1].conjugate()))
            TErii = 1 if Er is None else TEr[i, i]
            eAaE = TErii.conjugate() * A + TAr[i, i].conjugate() * E
            V.append(eAaE.apply_inverse(rhs))

        V = V.lincomb(Z.conjugate()[:, ::-1])
        V = V.real

    # solve for W, from the first column to the last
    if compute_W:
        W = A.source.empty(reserve=r)

        CrZ = Cr.apply(Cr.source.from_numpy(Z.T))
        CTCrZ = C.apply_adjoint(CrZ)
        for i in range(r):
            rhs = -CTCrZ[i].copy()
            if i > 0:
                if Er is not None:
                    rhs -= A.apply_adjoint(W.lincomb(TEr[:i, i]))
                rhs -= E.apply_adjoint(W.lincomb(TAr[:i, i]))
            TErii = 1 if Er is None else TEr[i, i]
            eAaE = TErii.conjugate() * A + TAr[i, i].conjugate() * E
            W.append(eAaE.apply_inverse_adjoint(rhs))

        W = W.lincomb(Q.conjugate())
        W = W.real

    if compute_V and compute_W:
        return V, W
    elif compute_V:
        return V
    else:
        return W
Exemple #38
0
def apply_inverse(op,
                  V,
                  options=None,
                  least_squares=False,
                  check_finite=True,
                  default_solver='scipy_spsolve',
                  default_least_squares_solver='scipy_least_squares_lsmr'):
    """Solve linear equation system.

    Applies the inverse of `op` to the vectors in `rhs` using PyAMG.

    Parameters
    ----------
    op
        The linear, non-parametric |Operator| to invert.
    rhs
        |VectorArray| of right-hand sides for the equation system.
    options
        The |solver_options| to use (see :func:`solver_options`).
    check_finite
        Test if solution only containes finite values.
    default_solver
        Default solver to use (scipy_spsolve, scipy_bicgstab, scipy_bicgstab_spilu,
        scipy_lgmres, scipy_least_squares_lsmr, scipy_least_squares_lsqr).
    default_least_squares_solver
        Default solver to use for least squares problems (scipy_least_squares_lsmr,
        scipy_least_squares_lsqr).

    Returns
    -------
    |VectorArray| of the solution vectors.
    """

    assert V in op.range

    if isinstance(op, NumpyMatrixOperator):
        matrix = op._matrix
    else:
        from pymor.algorithms.to_matrix import to_matrix
        matrix = to_matrix(op)

    options = _parse_options(options, solver_options(), default_solver,
                             default_least_squares_solver, least_squares)

    V = V.data
    promoted_type = np.promote_types(matrix.dtype, V.dtype)
    R = np.empty((len(V), matrix.shape[1]), dtype=promoted_type)

    if options['type'] == 'scipy_bicgstab':
        for i, VV in enumerate(V):
            R[i], info = bicgstab(matrix,
                                  VV,
                                  tol=options['tol'],
                                  maxiter=options['maxiter'])
            if info != 0:
                if info > 0:
                    raise InversionError(
                        'bicgstab failed to converge after {} iterations'.
                        format(info))
                else:
                    raise InversionError(
                        'bicgstab failed with error code {} (illegal input or breakdown)'
                        .format(info))
    elif options['type'] == 'scipy_bicgstab_spilu':
        if Version(scipy.version.version) >= Version('0.19'):
            ilu = spilu(matrix,
                        drop_tol=options['spilu_drop_tol'],
                        fill_factor=options['spilu_fill_factor'],
                        drop_rule=options['spilu_drop_rule'],
                        permc_spec=options['spilu_permc_spec'])
        else:
            if options['spilu_drop_rule']:
                logger = getLogger('pymor.operators.numpy._apply_inverse')
                logger.error(
                    "ignoring drop_rule in ilu factorization due to old SciPy")
            ilu = spilu(matrix,
                        drop_tol=options['spilu_drop_tol'],
                        fill_factor=options['spilu_fill_factor'],
                        permc_spec=options['spilu_permc_spec'])
        precond = LinearOperator(matrix.shape, ilu.solve)
        for i, VV in enumerate(V):
            R[i], info = bicgstab(matrix,
                                  VV,
                                  tol=options['tol'],
                                  maxiter=options['maxiter'],
                                  M=precond)
            if info != 0:
                if info > 0:
                    raise InversionError(
                        'bicgstab failed to converge after {} iterations'.
                        format(info))
                else:
                    raise InversionError(
                        'bicgstab failed with error code {} (illegal input or breakdown)'
                        .format(info))
    elif options['type'] == 'scipy_spsolve':
        try:
            # maybe remove unusable factorization:
            if hasattr(matrix, 'factorization'):
                fdtype = matrix.factorizationdtype
                if not np.can_cast(V.dtype, fdtype, casting='safe'):
                    del matrix.factorization

            if Version(scipy.version.version) >= Version('0.14'):
                if hasattr(matrix, 'factorization'):
                    # we may use a complex factorization of a real matrix to
                    # apply it to a real vector. In that case, we downcast
                    # the result here, removing the imaginary part,
                    # which should be zero.
                    R = matrix.factorization.solve(V.T).T.astype(promoted_type,
                                                                 copy=False)
                elif options['keep_factorization']:
                    # the matrix is always converted to the promoted type.
                    # if matrix.dtype == promoted_type, this is a no_op
                    matrix.factorization = splu(
                        matrix_astype_nocopy(matrix.tocsc(), promoted_type),
                        permc_spec=options['permc_spec'])
                    matrix.factorizationdtype = promoted_type
                    R = matrix.factorization.solve(V.T).T
                else:
                    # the matrix is always converted to the promoted type.
                    # if matrix.dtype == promoted_type, this is a no_op
                    R = spsolve(matrix_astype_nocopy(matrix, promoted_type),
                                V.T,
                                permc_spec=options['permc_spec']).T
            else:
                # see if-part for documentation
                if hasattr(matrix, 'factorization'):
                    for i, VV in enumerate(V):
                        R[i] = matrix.factorization.solve(VV).astype(
                            promoted_type, copy=False)
                elif options['keep_factorization']:
                    matrix.factorization = splu(
                        matrix_astype_nocopy(matrix.tocsc(), promoted_type),
                        permc_spec=options['permc_spec'])
                    matrix.factorizationdtype = promoted_type
                    for i, VV in enumerate(V):
                        R[i] = matrix.factorization.solve(VV)
                elif len(V) > 1:
                    factorization = splu(matrix_astype_nocopy(
                        matrix.tocsc(), promoted_type),
                                         permc_spec=options['permc_spec'])
                    for i, VV in enumerate(V):
                        R[i] = factorization.solve(VV)
                else:
                    R = spsolve(matrix_astype_nocopy(matrix, promoted_type),
                                V.T,
                                permc_spec=options['permc_spec']).reshape(
                                    (1, -1))
        except RuntimeError as e:
            raise InversionError(e)
    elif options['type'] == 'scipy_lgmres':
        for i, VV in enumerate(V):
            R[i], info = lgmres(matrix,
                                VV,
                                tol=options['tol'],
                                maxiter=options['maxiter'],
                                inner_m=options['inner_m'],
                                outer_k=options['outer_k'])
            if info > 0:
                raise InversionError(
                    'lgmres failed to converge after {} iterations'.format(
                        info))
            assert info == 0
    elif options['type'] == 'scipy_least_squares_lsmr':
        from scipy.sparse.linalg import lsmr
        for i, VV in enumerate(V):
            R[i], info, itn, _, _, _, _, _ = lsmr(matrix,
                                                  VV,
                                                  damp=options['damp'],
                                                  atol=options['atol'],
                                                  btol=options['btol'],
                                                  conlim=options['conlim'],
                                                  maxiter=options['maxiter'],
                                                  show=options['show'])
            assert 0 <= info <= 7
            if info == 7:
                raise InversionError(
                    'lsmr failed to converge after {} iterations'.format(itn))
    elif options['type'] == 'scipy_least_squares_lsqr':
        for i, VV in enumerate(V):
            R[i], info, itn, _, _, _, _, _, _, _ = lsqr(
                matrix,
                VV,
                damp=options['damp'],
                atol=options['atol'],
                btol=options['btol'],
                conlim=options['conlim'],
                iter_lim=options['iter_lim'],
                show=options['show'])
            assert 0 <= info <= 7
            if info == 7:
                raise InversionError(
                    'lsmr failed to converge after {} iterations'.format(itn))
    else:
        raise ValueError('Unknown solver type')

    if check_finite:
        if not np.isfinite(np.sum(R)):
            raise InversionError('Result contains non-finite values')

    return op.source.from_data(R)
Exemple #39
0
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)