예제 #1
0
def implicit_euler(A, F, M, U0, t0, t1, nt, mu=None, num_values=None, solver_options='operator'):
    assert isinstance(A, OperatorInterface)
    assert isinstance(F, (type(None), OperatorInterface, VectorArrayInterface))
    assert isinstance(M, (type(None), OperatorInterface))
    assert A.source == A.range
    num_values = num_values or nt + 1
    dt = (t1 - t0) / nt
    DT = (t1 - t0) / (num_values - 1)

    if F is None:
        F_time_dep = False
    elif isinstance(F, OperatorInterface):
        assert F.range.dim == 1
        assert F.source == A.range
        F_time_dep = F.parametric and '_t' in F.parameter_type
        if not F_time_dep:
            dt_F = F.as_vector(mu, space=A.source) * dt
    else:
        assert len(F) == 1
        assert F in A.range
        F_time_dep = False
        dt_F = F * dt

    if M is None:
        from pymor.operators.constructions import IdentityOperator
        M = IdentityOperator(A.source)

    assert A.source == M.source == M.range
    assert not M.parametric
    assert U0 in A.source
    assert len(U0) == 1

    A_time_dep = A.parametric and '_t' in A.parameter_type

    R = A.source.empty(reserve=nt+1)
    R.append(U0)

    options = A.solver_options if solver_options == 'operator' else \
              M.solver_options if solver_options == 'mass' else \
              solver_options
    M_dt_A = (M + A * dt).with_(solver_options=options)
    if not A_time_dep:
        M_dt_A = M_dt_A.assemble(mu)

    t = t0
    U = U0.copy()

    for n in range(nt):
        t += dt
        mu['_t'] = t
        rhs = M.apply(U)
        if F_time_dep:
            dt_F = F.as_vector(mu, space=A.source) * dt
        if F:
            rhs += dt_F
        U = M_dt_A.apply_inverse(rhs, mu=mu)
        while t - t0 + (min(dt, DT) * 0.5) >= len(R) * DT:
            R.append(U)

    return R
예제 #2
0
def lradi(A, E, B, trans=False, options=None):
    """Find a factor of the solution of a Lyapunov equation using the
    low-rank ADI iteration as described in Algorithm 4.3 in [PK16]_.

    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`.
    """
    logger = getLogger('pymor.algorithms.lyapunov.lradi')

    shift_options = options['shift_options'][options['shifts']]
    if shift_options['type'] == 'projection_shifts':
        init_shifts = projection_shifts_init
        iteration_shifts = projection_shifts
    else:
        raise ValueError('Unknown lradi shift strategy')

    if E is None:
        E = IdentityOperator(A.source)

    if not trans:
        Z = A.source.empty(reserve=B.source.dim * options['maxiter'])
        W = B.as_range_array()
    else:
        Z = A.range.empty(reserve=B.range.dim * options['maxiter'])
        W = B.as_source_array()

    j = 0
    shifts = init_shifts(A, E, W, shift_options)
    size_shift = shifts.size
    res = np.linalg.norm(W.gramian(), ord=2)
    init_res = res
    Btol = res * options['tol']

    while res > Btol and j < options['maxiter']:
        if shifts[j].imag == 0:
            AaE = A + shifts[j].real * E
            if not trans:
                V = AaE.apply_inverse(W)
                W -= E.apply(V) * (2 * shifts[j].real)
            else:
                V = AaE.apply_inverse_adjoint(W)
                W -= E.apply_adjoint(V) * (2 * shifts[j].real)
            Z.append(V * np.sqrt(-2 * shifts[j].real))
            j += 1
        else:
            AaE = A + shifts[j] * E
            g = 2 * np.sqrt(-shifts[j].real)
            d = shifts[j].real / shifts[j].imag
            if not trans:
                V = AaE.apply_inverse(W)
                W += E.apply(V.real + V.imag * d) * g**2
            else:
                V = AaE.apply_inverse_adjoint(W).conj()
                W += E.apply_adjoint(V.real + V.imag * d) * g**2
            Z.append((V.real + V.imag * d) * g)
            Z.append(V.imag * (g * np.sqrt(d**2 + 1)))
            j += 2
        if j >= size_shift:
            shifts = iteration_shifts(A, E, Z, W, shifts, shift_options)
            size_shift = shifts.size
        res = np.linalg.norm(W.gramian(), ord=2)
        logger.info("Relative residual at step {}: {:.5e}".format(j, res / init_res))

    if res > Btol:
        logger.warning('Prescribed relative residual tolerance was not achieved ({:e} > {:e}) after '
                       '{} ADI steps.'.format(res / init_res, options['tol'], options['maxiter']))

    return Z
예제 #3
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 the low-rank ADI iteration as described in
    Algorithm 4.3 in [PK16]_.

    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(), 'lradi',
                             None, False)
    logger = getLogger('pymor.algorithms.lradi.solve_lyap_lrcf')

    shift_options = options['shift_options'][options['shifts']]
    if shift_options['type'] == 'projection_shifts':
        init_shifts = projection_shifts_init
        iteration_shifts = projection_shifts
    else:
        raise ValueError('Unknown lradi shift strategy.')

    if E is None:
        E = IdentityOperator(A.source)

    Z = A.source.empty(reserve=len(B) * options['maxiter'])
    W = B.copy()

    j = 0
    j_shift = 0
    shifts = init_shifts(A, E, W, shift_options)
    res = np.linalg.norm(W.gramian(), ord=2)
    init_res = res
    Btol = res * options['tol']

    while res > Btol and j < options['maxiter']:
        if shifts[j_shift].imag == 0:
            AaE = A + shifts[j_shift].real * E
            if not trans:
                V = AaE.apply_inverse(W)
                W -= E.apply(V) * (2 * shifts[j_shift].real)
            else:
                V = AaE.apply_inverse_adjoint(W)
                W -= E.apply_adjoint(V) * (2 * shifts[j_shift].real)
            Z.append(V * np.sqrt(-2 * shifts[j_shift].real))
            j += 1
        else:
            AaE = A + shifts[j_shift] * E
            gs = -4 * shifts[j_shift].real
            d = shifts[j_shift].real / shifts[j_shift].imag
            if not trans:
                V = AaE.apply_inverse(W)
                W += E.apply(V.real + V.imag * d) * gs
            else:
                V = AaE.apply_inverse_adjoint(W).conj()
                W += E.apply_adjoint(V.real + V.imag * d) * gs
            g = np.sqrt(gs)
            Z.append((V.real + V.imag * d) * g)
            Z.append(V.imag * (g * np.sqrt(d**2 + 1)))
            j += 2
        j_shift += 1
        res = np.linalg.norm(W.gramian(), ord=2)
        logger.info(f'Relative residual at step {j}: {res/init_res:.5e}')
        if j_shift >= shifts.size:
            shifts = iteration_shifts(A, E, V, shifts)
            j_shift = 0

    if res > Btol:
        logger.warning(
            f'Prescribed relative residual tolerance was not achieved '
            f'({res/init_res:e} > {options["tol"]:e}) after '
            f'{options["maxiter"]} ADI steps.')

    return Z
예제 #4
0
파일: lrradi.py 프로젝트: weslowrie/pymor
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 is an implementation of Algorithm 2 in [BBKS18]_.

    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, None, None, trans)
    options = _parse_options(options, ricc_lrcf_solver_options(), 'lrradi',
                             None, False)
    logger = getLogger('pymor.algorithms.lrradi.solve_ricc_lrcf')

    shift_options = options['shift_options'][options['shifts']]
    if shift_options['type'] == 'hamiltonian_shifts':
        init_shifts = hamiltonian_shifts_init
        iteration_shifts = hamiltonian_shifts
    else:
        raise ValueError('Unknown lrradi shift strategy.')

    if E is None:
        E = IdentityOperator(A.source)

    if S is not None:
        raise NotImplementedError

    if R is not None:
        Rc = spla.cholesky(R)  # R = Rc^T * Rc
        Rci = spla.solve_triangular(Rc, np.eye(
            Rc.shape[0]))  # R^{-1} = Rci * Rci^T
        if not trans:
            C = C.lincomb(Rci.T)  # C <- Rci^T * C = (C^T * Rci)^T
        else:
            B = B.lincomb(Rci.T)  # B <- B * Rci

    if not trans:
        B, C = C, B

    Z = A.source.empty(reserve=len(C) * options['maxiter'])
    Y = np.empty((0, 0))

    K = A.source.zeros(len(B))
    RF = C.copy()

    j = 0
    j_shift = 0
    shifts = init_shifts(A, E, B, C, shift_options)

    res = np.linalg.norm(RF.gramian(), ord=2)
    init_res = res
    Ctol = res * options['tol']

    while res > Ctol and j < options['maxiter']:
        if not trans:
            AsE = A + shifts[j_shift] * E
        else:
            AsE = A + np.conj(shifts[j_shift]) * E
        if j == 0:
            if not trans:
                V = AsE.apply_inverse(RF) * np.sqrt(-2 * shifts[j_shift].real)
            else:
                V = AsE.apply_inverse_adjoint(RF) * np.sqrt(
                    -2 * shifts[j_shift].real)
        else:
            if not trans:
                LN = AsE.apply_inverse(cat_arrays([RF, K]))
            else:
                LN = AsE.apply_inverse_adjoint(cat_arrays([RF, K]))
            L = LN[:len(RF)]
            N = LN[-len(K):]
            ImBN = np.eye(len(K)) - B.dot(N)
            ImBNKL = spla.solve(ImBN, B.dot(L))
            V = (L + N.lincomb(ImBNKL.T)) * np.sqrt(-2 * shifts[j_shift].real)

        if np.imag(shifts[j_shift]) == 0:
            Z.append(V)
            VB = V.dot(B)
            Yt = np.eye(len(C)) - (VB @ VB.T) / (2 * shifts[j_shift].real)
            Y = spla.block_diag(Y, Yt)
            if not trans:
                EVYt = E.apply(V).lincomb(np.linalg.inv(Yt))
            else:
                EVYt = E.apply_adjoint(V).lincomb(np.linalg.inv(Yt))
            RF.axpy(np.sqrt(-2 * shifts[j_shift].real), EVYt)
            K += EVYt.lincomb(VB.T)
            j += 1
        else:
            Z.append(V.real)
            Z.append(V.imag)
            Vr = V.real.dot(B)
            Vi = V.imag.dot(B)
            sa = np.abs(shifts[j_shift])
            F1 = np.vstack((-shifts[j_shift].real / sa * Vr -
                            shifts[j_shift].imag / sa * Vi,
                            shifts[j_shift].imag / sa * Vr -
                            shifts[j_shift].real / sa * Vi))
            F2 = np.vstack((Vr, Vi))
            F3 = np.vstack((shifts[j_shift].imag / sa * np.eye(len(C)),
                            shifts[j_shift].real / sa * np.eye(len(C))))
            Yt = spla.block_diag(np.eye(len(C)), 0.5 * np.eye(len(C))) \
                - (F1 @ F1.T) / (4 * shifts[j_shift].real)  \
                - (F2 @ F2.T) / (4 * shifts[j_shift].real)  \
                - (F3 @ F3.T) / 2
            Y = spla.block_diag(Y, Yt)
            EVYt = E.apply(cat_arrays([V.real,
                                       V.imag])).lincomb(np.linalg.inv(Yt))
            RF.axpy(np.sqrt(-2 * shifts[j_shift].real), EVYt[:len(C)])
            K += EVYt.lincomb(F2.T)
            j += 2
        j_shift += 1
        res = np.linalg.norm(RF.gramian(), ord=2)
        logger.info(f'Relative residual at step {j}: {res/init_res:.5e}')
        if j_shift >= shifts.size:
            shifts = iteration_shifts(A, E, B, RF, K, Z, shift_options)
            j_shift = 0
    # transform solution to lrcf
    cf = spla.cholesky(Y)
    Z_cf = Z.lincomb(spla.solve_triangular(cf, np.eye(len(Z))).T)
    return Z_cf
예제 #5
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
예제 #6
0
def samdp(A,
          E,
          B,
          C,
          nwanted,
          init_shifts=None,
          which='LR',
          tol=1e-10,
          imagtol=1e-6,
          conjtol=1e-8,
          dorqitol=1e-4,
          rqitol=1e-10,
          maxrestart=100,
          krestart=20,
          rqi_maxiter=10,
          seed=0):
    """Compute the dominant pole triplets and residues of the transfer function of an LTI system.

    This function uses the subspace accelerated dominant pole (SAMDP) algorithm as described in
    [RM06]_ in Algorithm 2 in order to compute dominant pole triplets and residues of the transfer
    function

    .. math::
        H(s) = C (s E - A)^{-1} B

    of an LTI system. It is possible to take advantage of prior knowledge about the poles
    by specifying shift parameters, which are injected after a new pole has been found.

    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`.
    nwanted
        The number of dominant poles that should be computed.
    init_shifts
        A |NumPy array| containing shifts which are injected after a new pole has been found.
    which
        A string specifying the strategy by which the dominant poles and residues are selected.
        Possible values are:

        - `'LR'`: select poles with largest norm(residual) / abs(Re(pole))
        - `'LS'`: select poles with largest norm(residual) / abs(pole)
        - `'LM'`: select poles with largest norm(residual)
    tol
        Tolerance for the residual of the poles.
    imagtol
        Relative tolerance for imaginary parts of pairs of complex conjugate eigenvalues.
    conjtol
        Tolerance for the residual of the complex conjugate of a pole.
    dorqitol
        If the residual is smaller than dorqitol the two-sided Rayleigh quotient iteration
        is executed.
    rqitol
        Tolerance for the relative change of a pole in the two-sided Rayleigh quotient
        iteration.
    maxrestart
        The maximum number of restarts.
    krestart
        Maximum dimension of search space before performing a restart.
    rqi_maxiter
        Maximum number of iterations for the two-sided Rayleigh quotient iteration.
    seed
        Random seed which is used for computing the initial shift and random restarts.

    Returns
    -------
    poles
        A 1D |NumPy array| containing the computed dominant poles.
    residues
        A 3D |NumPy array| of shape `(len(poles), len(C), len(B))` containing the computed residues.
    rightev
        A |VectorArray| containing the right eigenvectors of the computed poles.
    leftev
        A |VectorArray| containing the left eigenvectors of the computed poles.
    """

    logger = getLogger('pymor.algorithms.samdp.samdp')

    if E is None:
        E = IdentityOperator(A.source)

    assert isinstance(A, Operator) and A.linear
    assert not A.parametric
    assert A.source == A.range
    if E is not None:
        assert isinstance(E, Operator) and E.linear
        assert not E.parametric
        assert E.source == E.range
        assert E.source == A.source
    assert B in A.source
    assert C in A.source

    B_defl = B.copy()
    C_defl = C.copy()

    k = 0
    nrestart = 0
    nr_converged = 0
    np.random.seed(seed)

    X = A.source.empty()
    Q = A.source.empty()
    Qt = A.source.empty()
    Qs = A.source.empty()
    Qts = A.source.empty()
    AX = A.source.empty()
    V = A.source.empty()

    H = np.empty((0, 1))
    G = np.empty((0, 1))
    poles = np.empty(0)

    if init_shifts is None:
        st = np.random.uniform() * 10.j
        shift_nr = 0
        nr_shifts = 0
    else:
        st = init_shifts[0]
        shift_nr = 1
        nr_shifts = len(init_shifts)

    shifts = init_shifts

    while nrestart < maxrestart:
        k += 1

        sEmA = st * E - A
        sEmAB = sEmA.apply_inverse(B_defl)
        Hs = C_defl.inner(sEmAB)

        y_all, _, u_all = spla.svd(Hs)

        u = u_all.conj()[0]
        y = y_all[:, 0]

        x = sEmAB.lincomb(u)
        v = sEmA.apply_inverse_adjoint(C_defl.lincomb(y.T))

        X.append(x)
        V.append(v)
        gram_schmidt(V, atol=0, rtol=0, copy=False)
        gram_schmidt(X, atol=0, rtol=0, copy=False)

        AX.append(A.apply(X[k - 1]))

        if k > 1:
            H = np.hstack((H, V[0:k - 1].inner(AX[k - 1])))
        H = np.vstack((H, V[k - 1].inner(AX)))
        EX = E.apply(X)
        if k > 1:
            G = np.hstack((G, V[0:k - 1].inner(EX[k - 1])))
        G = np.vstack((G, V[k - 1].inner(EX)))

        SH, UR, URt, res = _select_max_eig(H, G, X, V, B_defl, C_defl, which)

        if np.all(res < np.finfo(float).eps):
            st = np.random.uniform() * 10.j
            found = False
        else:
            found = True

        do_rqi = True
        while found:
            theta = SH[0, 0]
            schurvec = X.lincomb(UR[:, 0])
            schurvec.scal(1 / schurvec.norm())
            lschurvec = V.lincomb(URt[:, 0])
            lschurvec.scal(1 / lschurvec.norm())

            st = theta

            nres = (A.apply(schurvec) - (E.apply(schurvec) * theta)).norm()[0]

            logger.info(f'Step: {k}, Theta: {theta:.5e}, Residual: {nres:.5e}')

            if nres < dorqitol and do_rqi:
                schurvec, lschurvec, theta, nres = _twosided_rqi(
                    A, E, schurvec, lschurvec, theta, nres, imagtol, rqitol,
                    rqi_maxiter)
                do_rqi = False
                if np.abs(np.imag(theta)) / np.abs(theta) < imagtol:
                    rres = A.apply(schurvec.real) - E.apply(
                        schurvec.real) * np.real(theta)
                    nrr = rres.norm() / np.abs(np.real(theta))
                    if nrr - nres < np.finfo(float).eps:
                        schurvec = schurvec.real
                        lschurvec = lschurvec.real
                        theta = np.real(theta)
                        nres = nrr
                if nres >= tol:
                    logger.warning(
                        'Two-sided RQI did not reach desired tolerance.')

            elif np.abs(np.imag(theta)) / np.abs(theta) < imagtol:
                rres = A.apply(
                    schurvec.real) - E.apply(schurvec.real) * np.real(theta)
                nrr = rres.norm() / np.abs(np.real(theta))
                if nrr - nres < np.finfo(float).eps:
                    schurvec = schurvec.real
                    lschurvec = lschurvec.real
                    theta = np.real(theta)
                    nres = nrr
            found = nr_converged < nwanted and nres < tol

            if found:
                poles = np.append(poles, theta)
                logger.info(f'Pole: {theta:.5e}')

                Q.append(schurvec)
                Qt.append(lschurvec)
                Esch = E.apply(schurvec)
                Qs.append(Esch)
                Qts.append(E.apply_adjoint(lschurvec))

                nqqt = lschurvec.inner(Esch)[0][0]
                Q[-1].scal(1 / nqqt)
                Qs[-1].scal(1 / nqqt)

                nr_converged += 1

                if k > 1:
                    X = X.lincomb(UR[:, 1:k].T)
                    V = V.lincomb(URt[:, 1:k].T)
                else:
                    X = A.source.empty()
                    V = A.source.empty()

                if np.abs(np.imag(theta)) / np.abs(theta) < imagtol:
                    gram_schmidt(V, atol=0, rtol=0, copy=False)
                    gram_schmidt(X, atol=0, rtol=0, copy=False)

                B_defl -= E.apply(Q[-1].lincomb(Qt[-1].inner(B_defl).T))
                C_defl -= E.apply_adjoint(Qt[-1].lincomb(
                    Q[-1].inner(C_defl).T))

                k -= 1

                cce = theta.conj()
                if np.abs(np.imag(cce)) / np.abs(cce) >= imagtol:

                    ccv = schurvec.conj()
                    ccv.scal(1 / ccv.norm())

                    r = A.apply(ccv) - E.apply(ccv) * cce

                    if r.norm() / np.abs(cce) < conjtol:
                        logger.info(f'Conjugate Pole: {cce:.5e}')
                        poles = np.append(poles, cce)

                        Q.append(ccv)
                        ccvt = lschurvec.conj()
                        Qt.append(ccvt)

                        Esch = E.apply(ccv)
                        Qs.append(Esch)
                        Qts.append(E.apply_adjoint(ccvt))

                        nqqt = ccvt.inner(E.apply(ccv))[0][0]
                        Q[-1].scal(1 / nqqt)
                        Qs[-1].scal(1 / nqqt)

                        gram_schmidt(V, atol=0, rtol=0, copy=False)
                        gram_schmidt(X, atol=0, rtol=0, copy=False)

                        B_defl -= E.apply(Q[-1].lincomb(
                            Qt[-1].inner(B_defl).T))
                        C_defl -= E.apply_adjoint(Qt[-1].lincomb(
                            Q[-1].inner(C_defl).T))

                AX = A.apply(X)
                if k > 0:
                    G = V.inner(E.apply(X))
                    H = V.inner(AX)
                    SH, UR, URt, residues = _select_max_eig(
                        H, G, X, V, B_defl, C_defl, which)
                    found = np.any(res >= np.finfo(float).eps)
                else:
                    G = np.empty((0, 1))
                    H = np.empty((0, 1))
                    found = False

                if nr_converged < nwanted:
                    if found:
                        st = SH[0, 0]
                    else:
                        st = np.random.uniform() * 10.j

                    if shift_nr < nr_shifts:
                        st = shifts[shift_nr]
                        shift_nr += 1
            elif k >= krestart:
                logger.info('Perform restart...')
                EX = E.apply(X)
                RR = AX.lincomb(UR.T) - EX.lincomb(UR.T).lincomb(SH.T)

                minidx = RR.norm().argmin()
                k = 1

                X = X.lincomb(UR[:, minidx])
                V = V.lincomb(URt[:, minidx])

                gram_schmidt(V, atol=0, rtol=0, copy=False)
                gram_schmidt(X, atol=0, rtol=0, copy=False)

                G = V.inner(E.apply(X))
                AX = A.apply(X)
                H = V.inner(AX)
                nrestart += 1

        if k >= krestart:
            logger.info('Perform restart...')
            EX = E.apply(X)
            RR = AX.lincomb(UR.T) - EX.lincomb(UR.T).lincomb(SH.T)

            minidx = RR.norm().argmin()
            k = 1

            X = X.lincomb(UR[:, minidx])
            V = V.lincomb(URt[:, minidx])

            gram_schmidt(V, atol=0, rtol=0, copy=False)
            gram_schmidt(X, atol=0, rtol=0, copy=False)

            G = V.inner(E.apply(X))
            AX = A.apply(X)
            H = V.inner(AX)
            nrestart += 1

        if nr_converged == nwanted or nrestart == maxrestart:
            rightev = Q
            leftev = Qt
            absres = np.empty(len(poles))
            residues = []
            for i in range(len(poles)):
                leftev[i].scal(1 / leftev[i].inner(E.apply(rightev[i]))[0][0])
                residues.append(C.inner(rightev[i]) @ leftev[i].inner(B))
                absres[i] = spla.norm(residues[-1], ord=2)
            residues = np.array(residues)

            if which == 'LR':
                idx = np.argsort(-absres / np.abs(np.real(poles)))
            elif which == 'LS':
                idx = np.argsort(-absres / np.abs(poles))
            elif which == 'LM':
                idx = np.argsort(-absres)
            else:
                raise ValueError('Unknown SAMDP selection strategy.')

            residues = residues[idx]
            poles = poles[idx]
            rightev = rightev[idx]
            leftev = leftev[idx]
            if nr_converged < nwanted:
                logger.warning(
                    'The specified number of poles could not be computed.')
            break

    return poles, residues, rightev, leftev
예제 #7
0
파일: sylvester.py 프로젝트: pymor/pymor
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
예제 #8
0
파일: lradi.py 프로젝트: pymor/pymor
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 the low-rank ADI iteration as described in
    Algorithm 4.3 in [PK16]_.

    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(), 'lradi', None, False)
    logger = getLogger('pymor.algorithms.lradi.solve_lyap_lrcf')

    shift_options = options['shift_options'][options['shifts']]
    if shift_options['type'] == 'projection_shifts':
        init_shifts = projection_shifts_init
        iteration_shifts = projection_shifts
    else:
        raise ValueError('Unknown lradi shift strategy.')

    if E is None:
        E = IdentityOperator(A.source)

    Z = A.source.empty(reserve=len(B) * options['maxiter'])
    W = B.copy()

    j = 0
    j_shift = 0
    shifts = init_shifts(A, E, W, shift_options)
    res = np.linalg.norm(W.gramian(), ord=2)
    init_res = res
    Btol = res * options['tol']

    while res > Btol and j < options['maxiter']:
        if shifts[j_shift].imag == 0:
            AaE = A + shifts[j_shift].real * E
            if not trans:
                V = AaE.apply_inverse(W)
                W -= E.apply(V) * (2 * shifts[j_shift].real)
            else:
                V = AaE.apply_inverse_adjoint(W)
                W -= E.apply_adjoint(V) * (2 * shifts[j_shift].real)
            Z.append(V * np.sqrt(-2 * shifts[j_shift].real))
            j += 1
        else:
            AaE = A + shifts[j_shift] * E
            gs = -4 * shifts[j_shift].real
            d = shifts[j_shift].real / shifts[j_shift].imag
            if not trans:
                V = AaE.apply_inverse(W)
                W += E.apply(V.real + V.imag * d) * gs
            else:
                V = AaE.apply_inverse_adjoint(W).conj()
                W += E.apply_adjoint(V.real + V.imag * d) * gs
            g = np.sqrt(gs)
            Z.append((V.real + V.imag * d) * g)
            Z.append(V.imag * (g * np.sqrt(d**2 + 1)))
            j += 2
        j_shift += 1
        res = np.linalg.norm(W.gramian(), ord=2)
        logger.info(f'Relative residual at step {j}: {res/init_res:.5e}')
        if j_shift >= shifts.size:
            shifts = iteration_shifts(A, E, V, shifts)
            j_shift = 0

    if res > Btol:
        logger.warning(f'Prescribed relative residual tolerance was not achieved '
                       f'({res/init_res:e} > {options["tol"]:e}) after ' f'{options["maxiter"]} ADI steps.')

    return Z