Beispiel #1
0
    def _projection_matrices(self, rom, projection):
        if self.fom.parametric:
            fom = self.fom.with_(**{
                op: getattr(self.fom, op).assemble(mu=self.mu)
                for op in ['A', 'B', 'C', 'D', 'E']
            },
                                 parameter_space=None)
        else:
            fom = self.fom

        self.V, self.W = solve_sylv_schur(fom.A,
                                          rom.A,
                                          E=fom.E,
                                          Er=rom.E,
                                          B=fom.B,
                                          Br=rom.B,
                                          C=fom.C,
                                          Cr=rom.C)

        if projection == 'orth':
            self.V = gram_schmidt(self.V, atol=0, rtol=0)
            self.W = gram_schmidt(self.W, atol=0, rtol=0)
        elif projection == 'biorth':
            self.V, self.W = gram_schmidt_biorth(self.V, self.W, product=fom.E)

        self._pg_reductor = LTIPGReductor(fom, self.W, self.V,
                                          projection == 'biorth')
Beispiel #2
0
    def reduce(self, r=None, tol=None, projection='bfsr'):
        """Generic Balanced Truncation.

        Parameters
        ----------
        r
            Order of the reduced model if `tol` is `None`, maximum order if `tol` is specified.
        tol
            Tolerance for the error bound if `r` is `None`.
        projection
            Projection method used:

            - `'sr'`: square root method
            - `'bfsr'`: balancing-free square root method (default, since it avoids scaling by
              singular values and orthogonalizes the projection matrices, which might make it more
              accurate than the square root method)
            - `'biorth'`: like the balancing-free square root method, except it biorthogonalizes the
              projection matrices (using :func:`~pymor.algorithms.gram_schmidt.gram_schmidt_biorth`)

        Returns
        -------
        rom
            Reduced-order model.
        """
        assert r is not None or tol is not None
        assert r is None or 0 < r < self.fom.order
        assert projection in ('sr', 'bfsr', 'biorth')

        cf, of = self._gramians()
        sv, sU, sV = self._sv_U_V()

        # find reduced order if tol is specified
        if tol is not None:
            error_bounds = self.error_bounds()
            r_tol = np.argmax(error_bounds <= tol) + 1
            r = r_tol if r is None else min(r, r_tol)
        if r > min(len(cf), len(of)):
            raise ValueError(
                'r needs to be smaller than the sizes of Gramian factors.')

        # compute projection matrices
        self.V = cf.lincomb(sV[:r])
        self.W = of.lincomb(sU[:r])
        if projection == 'sr':
            alpha = 1 / np.sqrt(sv[:r])
            self.V.scal(alpha)
            self.W.scal(alpha)
        elif projection == 'bfsr':
            self.V = gram_schmidt(self.V, atol=0, rtol=0)
            self.W = gram_schmidt(self.W, atol=0, rtol=0)
        elif projection == 'biorth':
            self.V, self.W = gram_schmidt_biorth(self.V,
                                                 self.W,
                                                 product=self.fom.E)

        # find reduced-order model
        self._pg_reductor = LTIPGReductor(self.fom, self.W, self.V, projection
                                          in ('sr', 'biorth'))
        rom = self._pg_reductor.reduce()
        return rom
Beispiel #3
0
 def _projection_matrix(self, r, sigma, b, c, projection):
     if self.fom.parametric:
         fom = self.fom.with_(**{
             op: getattr(self.fom, op).assemble(mu=self.mu)
             for op in ['A', 'B', 'C', 'D', 'E']
         },
                              parameter_space=None)
     else:
         fom = self.fom
     if self.version == 'V':
         V = fom.A.source.empty(reserve=r)
     else:
         W = fom.A.source.empty(reserve=r)
     for i in range(r):
         if sigma[i].imag == 0:
             sEmA = sigma[i].real * fom.E - fom.A
             if self.version == 'V':
                 Bb = fom.B.apply(b.real[i])
                 V.append(sEmA.apply_inverse(Bb))
             else:
                 CTc = fom.C.apply_adjoint(c.real[i])
                 W.append(sEmA.apply_inverse_adjoint(CTc))
         elif sigma[i].imag > 0:
             sEmA = sigma[i] * fom.E - fom.A
             if self.version == 'V':
                 Bb = fom.B.apply(b[i])
                 v = sEmA.apply_inverse(Bb)
                 V.append(v.real)
                 V.append(v.imag)
             else:
                 CTc = fom.C.apply_adjoint(c[i].conj())
                 w = sEmA.apply_inverse_adjoint(CTc)
                 W.append(w.real)
                 W.append(w.imag)
     if self.version == 'V':
         self.V = gram_schmidt(
             V,
             atol=0,
             rtol=0,
             product=None if projection == 'orth' else fom.E)
     else:
         self.V = gram_schmidt(
             W,
             atol=0,
             rtol=0,
             product=None if projection == 'orth' else fom.E)
     self._pg_reductor = LTIPGReductor(fom, self.V, self.V,
                                       projection == 'Eorth')
Beispiel #4
0
Datei: h2.py Projekt: pymor/pymor
    def _projection_matrix(self, r, sigma, b, c, projection):
        fom = self.fom
        if self.version == 'V':
            V = fom.A.source.empty(reserve=r)
        else:
            W = fom.A.source.empty(reserve=r)
        for i in range(r):
            if sigma[i].imag == 0:
                sEmA = sigma[i].real * self.fom.E - self.fom.A
                if self.version == 'V':
                    Bb = fom.B.apply(b.real[i])
                    V.append(sEmA.apply_inverse(Bb))
                else:
                    CTc = fom.C.apply_adjoint(c.real[i])
                    W.append(sEmA.apply_inverse_adjoint(CTc))
            elif sigma[i].imag > 0:
                sEmA = sigma[i] * self.fom.E - self.fom.A
                if self.version == 'V':
                    Bb = fom.B.apply(b[i])
                    v = sEmA.apply_inverse(Bb)
                    V.append(v.real)
                    V.append(v.imag)
                else:
                    CTc = fom.C.apply_adjoint(c[i].conj())
                    w = sEmA.apply_inverse_adjoint(CTc)
                    W.append(w.real)
                    W.append(w.imag)

        if self.version == 'V':
            self.V = gram_schmidt(V, atol=0, rtol=0, product=None if projection == 'orth' else fom.E)
        else:
            self.V = gram_schmidt(W, atol=0, rtol=0, product=None if projection == 'orth' else fom.E)

        self._pg_reductor = LTIPGReductor(fom, self.V, self.V, projection == 'Eorth')
Beispiel #5
0
 def _set_V_reductor(self, sigma, b, c, projection):
     fom = (
         self.fom.with_(
             **{op: getattr(self.fom, op).assemble(mu=self.mu)
                for op in ['A', 'B', 'C', 'D', 'E']},
             parameter_space=None,
         )
         if self.fom.parametric
         else self.fom
     )
     if self.version == 'V':
         self.V = tangential_rational_krylov(fom.A, fom.E, fom.B, b, sigma,
                                             orth=False)
         gram_schmidt(self.V, atol=0, rtol=0,
                      product=None if projection == 'orth' else fom.E,
                      copy=False)
     else:
         self.V = tangential_rational_krylov(fom.A, fom.E, fom.C, c, sigma, trans=True,
                                             orth=False)
         gram_schmidt(self.V, atol=0, rtol=0,
                      product=None if projection == 'orth' else fom.E,
                      copy=False)
     self.W = self.V
     self._pg_reductor = LTIPGReductor(fom, self.V, self.V,
                                       projection == 'Eorth')
Beispiel #6
0
    def _projection_matrices(self, rom, projection):
        fom = self.fom
        self.V, self.W = solve_sylv_schur(fom.A,
                                          rom.A,
                                          E=fom.E,
                                          Er=rom.E,
                                          B=fom.B,
                                          Br=rom.B,
                                          C=fom.C,
                                          Cr=rom.C)
        if projection == 'orth':
            self.V = gram_schmidt(self.V, atol=0, rtol=0)
            self.W = gram_schmidt(self.W, atol=0, rtol=0)
        elif projection == 'biorth':
            self.V, self.W = gram_schmidt_biorth(self.V, self.W, product=fom.E)

        self._pg_reductor = LTIPGReductor(fom, self.W, self.V,
                                          projection == 'biorth')
Beispiel #7
0
Datei: bt.py Projekt: pymor/pymor
    def reduce(self, r=None, tol=None, projection='bfsr'):
        """Generic Balanced Truncation.

        Parameters
        ----------
        r
            Order of the reduced model if `tol` is `None`, maximum order if `tol` is specified.
        tol
            Tolerance for the error bound if `r` is `None`.
        projection
            Projection method used:

            - `'sr'`: square root method
            - `'bfsr'`: balancing-free square root method (default, since it avoids scaling by
              singular values and orthogonalizes the projection matrices, which might make it more
              accurate than the square root method)
            - `'biorth'`: like the balancing-free square root method, except it biorthogonalizes the
              projection matrices (using :func:`~pymor.algorithms.gram_schmidt.gram_schmidt_biorth`)

        Returns
        -------
        rom
            Reduced-order model.
        """
        assert r is not None or tol is not None
        assert r is None or 0 < r < self.fom.order
        assert projection in ('sr', 'bfsr', 'biorth')

        cf, of = self._gramians()
        sv, sU, sV = self._sv_U_V()

        # find reduced order if tol is specified
        if tol is not None:
            error_bounds = self.error_bounds()
            r_tol = np.argmax(error_bounds <= tol) + 1
            r = r_tol if r is None else min(r, r_tol)
        if r > min(len(cf), len(of)):
            raise ValueError('r needs to be smaller than the sizes of Gramian factors.')

        # compute projection matrices
        self.V = cf.lincomb(sV[:r])
        self.W = of.lincomb(sU[:r])
        if projection == 'sr':
            alpha = 1 / np.sqrt(sv[:r])
            self.V.scal(alpha)
            self.W.scal(alpha)
        elif projection == 'bfsr':
            self.V = gram_schmidt(self.V, atol=0, rtol=0)
            self.W = gram_schmidt(self.W, atol=0, rtol=0)
        elif projection == 'biorth':
            self.V, self.W = gram_schmidt_biorth(self.V, self.W, product=self.fom.E)

        # find reduced-order model
        self._pg_reductor = LTIPGReductor(self.fom, self.W, self.V, projection in ('sr', 'biorth'))
        rom = self._pg_reductor.reduce()
        return rom
Beispiel #8
0
    def _projection_matrix(self, r, sigma, b, c, projection):
        fom = self.fom
        if self.version == 'V':
            V = fom.A.source.empty(reserve=r)
        else:
            W = fom.A.source.empty(reserve=r)
        for i in range(r):
            if sigma[i].imag == 0:
                sEmA = sigma[i].real * self.fom.E - self.fom.A
                if self.version == 'V':
                    Bb = fom.B.apply(b.real[i])
                    V.append(sEmA.apply_inverse(Bb))
                else:
                    CTc = fom.C.apply_adjoint(c.real[i])
                    W.append(sEmA.apply_inverse_adjoint(CTc))
            elif sigma[i].imag > 0:
                sEmA = sigma[i] * self.fom.E - self.fom.A
                if self.version == 'V':
                    Bb = fom.B.apply(b[i])
                    v = sEmA.apply_inverse(Bb)
                    V.append(v.real)
                    V.append(v.imag)
                else:
                    CTc = fom.C.apply_adjoint(c[i].conj())
                    w = sEmA.apply_inverse_adjoint(CTc)
                    W.append(w.real)
                    W.append(w.imag)

        if self.version == 'V':
            self.V = gram_schmidt(
                V,
                atol=0,
                rtol=0,
                product=None if projection == 'orth' else fom.E)
        else:
            self.V = gram_schmidt(
                W,
                atol=0,
                rtol=0,
                product=None if projection == 'orth' else fom.E)

        self._pg_reductor = LTIPGReductor(fom, self.V, self.V,
                                          projection == 'Eorth')
Beispiel #9
0
Datei: h2.py Projekt: pymor/pymor
    def _projection_matrices(self, rom, projection):
        fom = self.fom
        self.V, self.W = solve_sylv_schur(fom.A, rom.A,
                                          E=fom.E, Er=rom.E,
                                          B=fom.B, Br=rom.B,
                                          C=fom.C, Cr=rom.C)
        if projection == 'orth':
            self.V = gram_schmidt(self.V, atol=0, rtol=0)
            self.W = gram_schmidt(self.W, atol=0, rtol=0)
        elif projection == 'biorth':
            self.V, self.W = gram_schmidt_biorth(self.V, self.W, product=fom.E)

        self._pg_reductor = LTIPGReductor(fom, self.W, self.V, projection == 'biorth')
Beispiel #10
0
 def _set_V_W_reductor(self, rom, projection):
     fom = (
         self.fom.with_(
             **{op: getattr(self.fom, op).assemble(mu=self.mu)
                for op in ['A', 'B', 'C', 'D', 'E']}
         )
         if self.fom.parametric
         else self.fom
     )
     self.V, self.W = solve_sylv_schur(fom.A, rom.A,
                                       E=fom.E, Er=rom.E,
                                       B=fom.B, Br=rom.B,
                                       C=fom.C, Cr=rom.C)
     if projection == 'orth':
         gram_schmidt(self.V, atol=0, rtol=0, copy=False)
         gram_schmidt(self.W, atol=0, rtol=0, copy=False)
     elif projection == 'biorth':
         gram_schmidt_biorth(self.V, self.W, product=fom.E, copy=False)
     self._pg_reductor = LTIPGReductor(fom, self.W, self.V,
                                       projection == 'biorth')
Beispiel #11
0
Datei: bt.py Projekt: pymor/pymor
class GenericBTReductor(BasicInterface):
    """Generic Balanced Truncation reductor.

    Parameters
    ----------
    fom
        The full-order |LTIModel| to reduce.
    """
    def __init__(self, fom):
        assert isinstance(fom, LTIModel)
        self.fom = fom
        self.V = None
        self.W = None
        self._pg_reductor = None
        self._sv_U_V_cache = None

    def _gramians(self):
        """Return low-rank Cholesky factors of Gramians."""
        raise NotImplementedError

    def _sv_U_V(self):
        """Return singular values and vectors."""
        if self._sv_U_V_cache is None:
            cf, of = self._gramians()
            U, sv, Vh = spla.svd(self.fom.E.apply2(of, cf), lapack_driver='gesvd')
            self._sv_U_V_cache = (sv, U.T, Vh)
        return self._sv_U_V_cache

    def error_bounds(self):
        """Returns error bounds for all possible reduced orders."""
        raise NotImplementedError

    def reduce(self, r=None, tol=None, projection='bfsr'):
        """Generic Balanced Truncation.

        Parameters
        ----------
        r
            Order of the reduced model if `tol` is `None`, maximum order if `tol` is specified.
        tol
            Tolerance for the error bound if `r` is `None`.
        projection
            Projection method used:

            - `'sr'`: square root method
            - `'bfsr'`: balancing-free square root method (default, since it avoids scaling by
              singular values and orthogonalizes the projection matrices, which might make it more
              accurate than the square root method)
            - `'biorth'`: like the balancing-free square root method, except it biorthogonalizes the
              projection matrices (using :func:`~pymor.algorithms.gram_schmidt.gram_schmidt_biorth`)

        Returns
        -------
        rom
            Reduced-order model.
        """
        assert r is not None or tol is not None
        assert r is None or 0 < r < self.fom.order
        assert projection in ('sr', 'bfsr', 'biorth')

        cf, of = self._gramians()
        sv, sU, sV = self._sv_U_V()

        # find reduced order if tol is specified
        if tol is not None:
            error_bounds = self.error_bounds()
            r_tol = np.argmax(error_bounds <= tol) + 1
            r = r_tol if r is None else min(r, r_tol)
        if r > min(len(cf), len(of)):
            raise ValueError('r needs to be smaller than the sizes of Gramian factors.')

        # compute projection matrices
        self.V = cf.lincomb(sV[:r])
        self.W = of.lincomb(sU[:r])
        if projection == 'sr':
            alpha = 1 / np.sqrt(sv[:r])
            self.V.scal(alpha)
            self.W.scal(alpha)
        elif projection == 'bfsr':
            self.V = gram_schmidt(self.V, atol=0, rtol=0)
            self.W = gram_schmidt(self.W, atol=0, rtol=0)
        elif projection == 'biorth':
            self.V, self.W = gram_schmidt_biorth(self.V, self.W, product=self.fom.E)

        # find reduced-order model
        self._pg_reductor = LTIPGReductor(self.fom, self.W, self.V, projection in ('sr', 'biorth'))
        rom = self._pg_reductor.reduce()
        return rom

    def reconstruct(self, u):
        """Reconstruct high-dimensional vector from reduced vector `u`."""
        return self._pg_reductor.reconstruct(u)
Beispiel #12
0
class OneSidedIRKAReductor(BasicInterface):
    """One-Sided Iterative Rational Krylov Algorithm reductor.

    Parameters
    ----------
    fom
        The full-order |LTIModel| to reduce.
    version
        Version of the one-sided IRKA:

        - `'V'`: Galerkin projection using the input Krylov subspace,
        - `'W'`: Galerkin projection using the output Krylov subspace.
    """
    def __init__(self, fom, version):
        assert isinstance(fom, LTIModel)
        assert version in ('V', 'W')
        self.fom = fom
        self.version = version
        self.V = None
        self._pg_reductor = None
        self.conv_crit = None
        self.sigmas = None
        self.R = None
        self.L = None
        self.errors = None

    def reduce(self,
               r,
               sigma=None,
               b=None,
               c=None,
               rd0=None,
               tol=1e-4,
               maxit=100,
               num_prev=1,
               force_sigma_in_rhp=False,
               projection='orth',
               conv_crit='sigma',
               compute_errors=False):
        r"""Reduce using one-sided IRKA.

        Parameters
        ----------
        r
            Order of the reduced order model.
        sigma
            Initial interpolation points (closed under conjugation).

            If `None`, interpolation points are log-spaced between 0.1 and 10.
            If `sigma` is an `int`, it is used as a seed to generate it randomly.
            Otherwise, it needs to be a one-dimensional array-like of length `r`.

            `sigma` and `rd0` cannot both be not `None`.
        b
            Initial right tangential directions.

            If `None`, if is chosen as all ones.
            If `b` is an `int`, it is used as a seed to generate it randomly.
            Otherwise, it needs to be a |VectorArray| of length `r` from `fom.B.source`.

            `b` and `rd0` cannot both be not `None`.
        c
            Initial left tangential directions.

            If `None`, if is chosen as all ones.
            If `c` is an `int`, it is used as a seed to generate it randomly.
            Otherwise, it needs to be a |VectorArray| of length `r` from `fom.C.range`.

            `c` and `rd0` cannot both be not `None`.
        rd0
            Initial reduced order model.

            If `None`, then `sigma`, `b`, and `c` are used.
            Otherwise, it needs to be an |LTIModel| of order `r` and it is used to construct
            `sigma`, `b`, and `c`.
        tol
            Tolerance for the largest change in interpolation points.
        maxit
            Maximum number of iterations.
        num_prev
            Number of previous iterations to compare the current iteration to.
            A larger number can avoid occasional cyclic behavior.
        force_sigma_in_rhp
            If 'False`, new interpolation are reflections of reduced order model's poles.
            Otherwise, they are always in the right half-plane.
        projection
            Projection method:

            - `'orth'`: projection matrix is orthogonalized with respect to the Euclidean inner
              product,
            - `'Eorth'`: projection matrix is orthogonalized with respect to the E product.
        conv_crit
            Convergence criterion:

            - `'sigma'`: relative change in interpolation points,
            - `'h2'`: relative :math:`\mathcal{H}_2` distance of reduced order models.
        compute_errors
            Should the relative :math:`\mathcal{H}_2`-errors of intermediate reduced order models be
            computed.

            .. warning::
                Computing :math:`\mathcal{H}_2`-errors is expensive.
                Use this option only if necessary.

        Returns
        -------
        rom
            Reduced |LTIModel| model.
        """
        fom = self.fom
        if not fom.cont_time:
            raise NotImplementedError
        assert 0 < r < fom.order
        assert isinstance(num_prev, int) and num_prev >= 1
        assert projection in ('orth', 'Eorth')
        assert conv_crit in ('sigma', 'h2')

        # initial interpolation points and tangential directions
        assert sigma is None or isinstance(sigma, int) or len(sigma) == r
        assert b is None or isinstance(
            b, int) or b in fom.B.source and len(b) == r
        assert c is None or isinstance(c,
                                       int) or c in fom.C.range and len(c) == r
        assert (rd0 is None or isinstance(rd0, LTIModel) and rd0.order == r
                and rd0.input_space == fom.input_space
                and rd0.output_space == fom.output_space)
        assert sigma is None or rd0 is None
        assert b is None or rd0 is None
        assert c is None or rd0 is None
        if rd0 is not None:
            poles, b, c = _poles_and_tangential_directions(rd0)
            sigma = np.abs(
                poles.real) + poles.imag * 1j if force_sigma_in_rhp else -poles
        else:
            if sigma is None:
                sigma = np.logspace(-1, 1, r)
            elif isinstance(sigma, int):
                np.random.seed(sigma)
                sigma = np.abs(np.random.randn(r))
            if self.version == 'V':
                if b is None:
                    b = fom.B.source.ones(r)
                elif isinstance(b, int):
                    b = fom.B.source.random(r, distribution='normal', seed=b)
            else:
                if c is None:
                    c = fom.C.range.ones(r)
                elif isinstance(c, int):
                    c = fom.C.range.random(r, distribution='normal', seed=c)

        self.logger.info('Starting one-sided IRKA')
        self.conv_crit = []
        self.sigmas = [np.array(sigma)]
        if self.version == 'V':
            self.R = [b]
        else:
            self.L = [c]
        self.errors = [] if compute_errors else None
        # main loop
        for it in range(maxit):
            # interpolatory reduced order model
            self._projection_matrix(r, sigma, b, c, projection)
            rom = self._pg_reductor.reduce()

            # new interpolation points and tangential directions
            poles, b, c = _poles_and_tangential_directions(rom)
            sigma = np.abs(
                poles.real) + poles.imag * 1j if force_sigma_in_rhp else -poles
            self.sigmas.append(sigma)
            if self.version == 'V':
                self.R.append(b)
            else:
                self.L.append(c)

            # compute convergence criterion
            if conv_crit == 'sigma':
                dist = _convergence_criterion(self.sigmas[:-num_prev - 2:-1],
                                              conv_crit)
                self.conv_crit.append(dist)
            elif conv_crit == 'h2':
                if it == 0:
                    rom_list = (num_prev + 1) * [None]
                    rom_list[0] = rom
                    self.conv_crit.append(np.inf)
                else:
                    rom_list[1:] = rom_list[:-1]
                    rom_list[0] = rom
                    dist = _convergence_criterion(rom_list, conv_crit)
                    self.conv_crit.append(dist)

            # report convergence
            self.logger.info(
                f'Convergence criterion in iteration {it + 1}: {self.conv_crit[-1]:e}'
            )
            if compute_errors:
                if np.max(rom.poles().real) < 0:
                    err = fom - rom
                    rel_H2_err = err.h2_norm() / fom.h2_norm()
                else:
                    rel_H2_err = np.inf
                self.errors.append(rel_H2_err)

                self.logger.info(
                    f'Relative H2-error in iteration {it + 1}: {rel_H2_err:e}')

            # check if convergence criterion is satisfied
            if self.conv_crit[-1] < tol:
                break

        # final reduced order model
        self._projection_matrix(r, sigma, b, c, projection)
        rom = self._pg_reductor.reduce()
        return rom

    def _projection_matrix(self, r, sigma, b, c, projection):
        fom = self.fom
        if self.version == 'V':
            V = fom.A.source.empty(reserve=r)
        else:
            W = fom.A.source.empty(reserve=r)
        for i in range(r):
            if sigma[i].imag == 0:
                sEmA = sigma[i].real * self.fom.E - self.fom.A
                if self.version == 'V':
                    Bb = fom.B.apply(b.real[i])
                    V.append(sEmA.apply_inverse(Bb))
                else:
                    CTc = fom.C.apply_adjoint(c.real[i])
                    W.append(sEmA.apply_inverse_adjoint(CTc))
            elif sigma[i].imag > 0:
                sEmA = sigma[i] * self.fom.E - self.fom.A
                if self.version == 'V':
                    Bb = fom.B.apply(b[i])
                    v = sEmA.apply_inverse(Bb)
                    V.append(v.real)
                    V.append(v.imag)
                else:
                    CTc = fom.C.apply_adjoint(c[i].conj())
                    w = sEmA.apply_inverse_adjoint(CTc)
                    W.append(w.real)
                    W.append(w.imag)

        if self.version == 'V':
            self.V = gram_schmidt(
                V,
                atol=0,
                rtol=0,
                product=None if projection == 'orth' else fom.E)
        else:
            self.V = gram_schmidt(
                W,
                atol=0,
                rtol=0,
                product=None if projection == 'orth' else fom.E)

        self._pg_reductor = LTIPGReductor(fom, self.V, self.V,
                                          projection == 'Eorth')

    def reconstruct(self, u):
        """Reconstruct high-dimensional vector from reduced vector `u`."""
        return self._pg_reductor.reconstruct(u)
Beispiel #13
0
Datei: h2.py Projekt: pymor/pymor
class TSIAReductor(BasicInterface):
    """Two-Sided Iteration Algorithm reductor.

    Parameters
    ----------
    fom
        The full-order |LTIModel| to reduce.
    """
    def __init__(self, fom):
        assert isinstance(fom, LTIModel)
        self.fom = fom
        self.V = None
        self.W = None
        self._pg_reductor = None
        self.conv_crit = None
        self.errors = None

    def reduce(self, rom0, tol=1e-4, maxit=100, num_prev=1, projection='orth', conv_crit='sigma',
               compute_errors=False):
        r"""Reduce using TSIA.

        See [XZ11]_ (Algorithm 1) and [BKS11]_.

        In exact arithmetic, TSIA is equivalent to IRKA (under some
        assumptions on the poles of the reduced model). The main
        difference in implementation is that TSIA computes the Schur
        decomposition of the reduced matrices, while IRKA computes the
        eigenvalue decomposition. Therefore, TSIA might behave better
        for non-normal reduced matrices.

        Parameters
        ----------
        rom0
            Initial reduced order model.
        tol
            Tolerance for the convergence criterion.
        maxit
            Maximum number of iterations.
        num_prev
            Number of previous iterations to compare the current
            iteration to. Larger number can avoid occasional cyclic
            behavior of TSIA.
        projection
            Projection method:

            - `'orth'`: projection matrices are orthogonalized with
              respect to the Euclidean inner product
            - `'biorth'`: projection matrices are biorthogolized with
              respect to the E product
        conv_crit
            Convergence criterion:

            - `'sigma'`: relative change in interpolation points
            - `'h2'`: relative :math:`\mathcal{H}_2` distance of
              reduced-order models
        compute_errors
            Should the relative :math:`\mathcal{H}_2`-errors of
            intermediate reduced order models be computed.

            .. warning::
                Computing :math:`\mathcal{H}_2`-errors is expensive. Use
                this option only if necessary.

        Returns
        -------
        rom
            Reduced |LTIModel|.
        """
        fom = self.fom
        assert isinstance(rom0, LTIModel) and rom0.B.source == fom.B.source and rom0.C.range == fom.C.range
        r = rom0.order
        assert 0 < r < fom.order
        assert isinstance(num_prev, int) and num_prev >= 1
        assert projection in ('orth', 'biorth')
        assert conv_crit in ('sigma', 'h2')

        # begin logging
        self.logger.info('Starting TSIA')

        # find initial projection matrices
        self._projection_matrices(rom0, projection)

        data = (num_prev + 1) * [None]
        data[0] = rom0.poles() if conv_crit == 'sigma' else rom0
        self.conv_crit = []
        self.errors = [] if compute_errors else None
        # main loop
        for it in range(maxit):
            # project the full order model
            rom = self._pg_reductor.reduce()

            # compute convergence criterion
            data[1:] = data[:-1]
            data[0] = rom.poles() if conv_crit == 'sigma' else rom
            dist = _convergence_criterion(data, conv_crit)
            self.conv_crit.append(dist)

            # report convergence
            self.logger.info(f'Convergence criterion in iteration {it + 1}: {self.conv_crit[-1]:e}')
            if compute_errors:
                if np.max(rom.poles().real) < 0:
                    err = fom - rom
                    rel_H2_err = err.h2_norm() / fom.h2_norm()
                else:
                    rel_H2_err = np.inf
                self.errors.append(rel_H2_err)

                self.logger.info(f'Relative H2-error in iteration {it + 1}: {rel_H2_err:e}')

            # new projection matrices
            self._projection_matrices(rom, projection)

            # check convergence criterion
            if self.conv_crit[-1] < tol:
                break

        # final reduced order model
        rom = self._pg_reductor.reduce()
        return rom

    def _projection_matrices(self, rom, projection):
        fom = self.fom
        self.V, self.W = solve_sylv_schur(fom.A, rom.A,
                                          E=fom.E, Er=rom.E,
                                          B=fom.B, Br=rom.B,
                                          C=fom.C, Cr=rom.C)
        if projection == 'orth':
            self.V = gram_schmidt(self.V, atol=0, rtol=0)
            self.W = gram_schmidt(self.W, atol=0, rtol=0)
        elif projection == 'biorth':
            self.V, self.W = gram_schmidt_biorth(self.V, self.W, product=fom.E)

        self._pg_reductor = LTIPGReductor(fom, self.W, self.V, projection == 'biorth')

    def reconstruct(self, u):
        """Reconstruct high-dimensional vector from reduced vector `u`."""
        return self._pg_reductor.reconstruct(u)
Beispiel #14
0
class GenericBTReductor(BasicObject):
    """Generic Balanced Truncation reductor.

    Parameters
    ----------
    fom
        The full-order |LTIModel| to reduce.
    mu
        |Parameter|.
    """
    def __init__(self, fom, mu=None):
        assert isinstance(fom, LTIModel)
        self.fom = fom
        self.mu = fom.parse_parameter(mu)
        self.V = None
        self.W = None
        self._pg_reductor = None
        self._sv_U_V_cache = None

    def _gramians(self):
        """Return low-rank Cholesky factors of Gramians."""
        raise NotImplementedError

    def _sv_U_V(self):
        """Return singular values and vectors."""
        if self._sv_U_V_cache is None:
            cf, of = self._gramians()
            U, sv, Vh = spla.svd(self.fom.E.apply2(of, cf, mu=self.mu),
                                 lapack_driver='gesvd')
            self._sv_U_V_cache = (sv, U.T, Vh)
        return self._sv_U_V_cache

    def error_bounds(self):
        """Returns error bounds for all possible reduced orders."""
        raise NotImplementedError

    def reduce(self, r=None, tol=None, projection='bfsr'):
        """Generic Balanced Truncation.

        Parameters
        ----------
        r
            Order of the reduced model if `tol` is `None`, maximum order if `tol` is specified.
        tol
            Tolerance for the error bound if `r` is `None`.
        projection
            Projection method used:

            - `'sr'`: square root method
            - `'bfsr'`: balancing-free square root method (default, since it avoids scaling by
              singular values and orthogonalizes the projection matrices, which might make it more
              accurate than the square root method)
            - `'biorth'`: like the balancing-free square root method, except it biorthogonalizes the
              projection matrices (using :func:`~pymor.algorithms.gram_schmidt.gram_schmidt_biorth`)

        Returns
        -------
        rom
            Reduced-order model.
        """
        assert r is not None or tol is not None
        assert r is None or 0 < r < self.fom.order
        assert projection in ('sr', 'bfsr', 'biorth')

        cf, of = self._gramians()
        sv, sU, sV = self._sv_U_V()

        # find reduced order if tol is specified
        if tol is not None:
            error_bounds = self.error_bounds()
            r_tol = np.argmax(error_bounds <= tol) + 1
            r = r_tol if r is None else min(r, r_tol)
        if r > min(len(cf), len(of)):
            raise ValueError(
                'r needs to be smaller than the sizes of Gramian factors.')

        # compute projection matrices
        self.V = cf.lincomb(sV[:r])
        self.W = of.lincomb(sU[:r])
        if projection == 'sr':
            alpha = 1 / np.sqrt(sv[:r])
            self.V.scal(alpha)
            self.W.scal(alpha)
        elif projection == 'bfsr':
            gram_schmidt(self.V, atol=0, rtol=0, copy=False)
            gram_schmidt(self.W, atol=0, rtol=0, copy=False)
        elif projection == 'biorth':
            gram_schmidt_biorth(self.V, self.W, product=self.fom.E, copy=False)

        # find reduced-order model
        if self.fom.parametric:
            fom_mu = self.fom.with_(**{
                op: getattr(self.fom, op).assemble(mu=self.mu)
                for op in ['A', 'B', 'C', 'D', 'E']
            },
                                    parameter_space=None)
        else:
            fom_mu = self.fom
        self._pg_reductor = LTIPGReductor(fom_mu, self.W, self.V, projection
                                          in ('sr', 'biorth'))
        rom = self._pg_reductor.reduce()
        return rom

    def reconstruct(self, u):
        """Reconstruct high-dimensional vector from reduced vector `u`."""
        return self._pg_reductor.reconstruct(u)
Beispiel #15
0
Datei: h2.py Projekt: pymor/pymor
class OneSidedIRKAReductor(BasicInterface):
    """One-Sided Iterative Rational Krylov Algorithm reductor.

    Parameters
    ----------
    fom
        The full-order |LTIModel| to reduce.
    version
        Version of the one-sided IRKA:

        - `'V'`: Galerkin projection using the input Krylov subspace,
        - `'W'`: Galerkin projection using the output Krylov subspace.
    """
    def __init__(self, fom, version):
        assert isinstance(fom, LTIModel)
        assert version in ('V', 'W')
        self.fom = fom
        self.version = version
        self.V = None
        self._pg_reductor = None
        self.conv_crit = None
        self.sigmas = None
        self.R = None
        self.L = None
        self.errors = None

    def reduce(self, r, sigma=None, b=None, c=None, rd0=None, tol=1e-4, maxit=100, num_prev=1,
               force_sigma_in_rhp=False, projection='orth', conv_crit='sigma',
               compute_errors=False):
        r"""Reduce using one-sided IRKA.

        Parameters
        ----------
        r
            Order of the reduced order model.
        sigma
            Initial interpolation points (closed under conjugation).

            If `None`, interpolation points are log-spaced between 0.1 and 10.
            If `sigma` is an `int`, it is used as a seed to generate it randomly.
            Otherwise, it needs to be a one-dimensional array-like of length `r`.

            `sigma` and `rd0` cannot both be not `None`.
        b
            Initial right tangential directions.

            If `None`, if is chosen as all ones.
            If `b` is an `int`, it is used as a seed to generate it randomly.
            Otherwise, it needs to be a |VectorArray| of length `r` from `fom.B.source`.

            `b` and `rd0` cannot both be not `None`.
        c
            Initial left tangential directions.

            If `None`, if is chosen as all ones.
            If `c` is an `int`, it is used as a seed to generate it randomly.
            Otherwise, it needs to be a |VectorArray| of length `r` from `fom.C.range`.

            `c` and `rd0` cannot both be not `None`.
        rd0
            Initial reduced order model.

            If `None`, then `sigma`, `b`, and `c` are used.
            Otherwise, it needs to be an |LTIModel| of order `r` and it is used to construct
            `sigma`, `b`, and `c`.
        tol
            Tolerance for the largest change in interpolation points.
        maxit
            Maximum number of iterations.
        num_prev
            Number of previous iterations to compare the current iteration to.
            A larger number can avoid occasional cyclic behavior.
        force_sigma_in_rhp
            If 'False`, new interpolation are reflections of reduced order model's poles.
            Otherwise, they are always in the right half-plane.
        projection
            Projection method:

            - `'orth'`: projection matrix is orthogonalized with respect to the Euclidean inner
              product,
            - `'Eorth'`: projection matrix is orthogonalized with respect to the E product.
        conv_crit
            Convergence criterion:

            - `'sigma'`: relative change in interpolation points,
            - `'h2'`: relative :math:`\mathcal{H}_2` distance of reduced order models.
        compute_errors
            Should the relative :math:`\mathcal{H}_2`-errors of intermediate reduced order models be
            computed.

            .. warning::
                Computing :math:`\mathcal{H}_2`-errors is expensive.
                Use this option only if necessary.

        Returns
        -------
        rom
            Reduced |LTIModel| model.
        """
        fom = self.fom
        if not fom.cont_time:
            raise NotImplementedError
        assert 0 < r < fom.order
        assert isinstance(num_prev, int) and num_prev >= 1
        assert projection in ('orth', 'Eorth')
        assert conv_crit in ('sigma', 'h2')

        # initial interpolation points and tangential directions
        assert sigma is None or isinstance(sigma, int) or len(sigma) == r
        assert b is None or isinstance(b, int) or b in fom.B.source and len(b) == r
        assert c is None or isinstance(c, int) or c in fom.C.range and len(c) == r
        assert (rd0 is None
                or isinstance(rd0, LTIModel)
                and rd0.order == r and rd0.input_space == fom.input_space and rd0.output_space == fom.output_space)
        assert sigma is None or rd0 is None
        assert b is None or rd0 is None
        assert c is None or rd0 is None
        if rd0 is not None:
            poles, b, c = _poles_and_tangential_directions(rd0)
            sigma = np.abs(poles.real) + poles.imag * 1j if force_sigma_in_rhp else -poles
        else:
            if sigma is None:
                sigma = np.logspace(-1, 1, r)
            elif isinstance(sigma, int):
                np.random.seed(sigma)
                sigma = np.abs(np.random.randn(r))
            if self.version == 'V':
                if b is None:
                    b = fom.B.source.ones(r)
                elif isinstance(b, int):
                    b = fom.B.source.random(r, distribution='normal', seed=b)
            else:
                if c is None:
                    c = fom.C.range.ones(r)
                elif isinstance(c, int):
                    c = fom.C.range.random(r, distribution='normal', seed=c)

        self.logger.info('Starting one-sided IRKA')
        self.conv_crit = []
        self.sigmas = [np.array(sigma)]
        if self.version == 'V':
            self.R = [b]
        else:
            self.L = [c]
        self.errors = [] if compute_errors else None
        # main loop
        for it in range(maxit):
            # interpolatory reduced order model
            self._projection_matrix(r, sigma, b, c, projection)
            rom = self._pg_reductor.reduce()

            # new interpolation points and tangential directions
            poles, b, c = _poles_and_tangential_directions(rom)
            sigma = np.abs(poles.real) + poles.imag * 1j if force_sigma_in_rhp else -poles
            self.sigmas.append(sigma)
            if self.version == 'V':
                self.R.append(b)
            else:
                self.L.append(c)

            # compute convergence criterion
            if conv_crit == 'sigma':
                dist = _convergence_criterion(self.sigmas[:-num_prev-2:-1], conv_crit)
                self.conv_crit.append(dist)
            elif conv_crit == 'h2':
                if it == 0:
                    rom_list = (num_prev + 1) * [None]
                    rom_list[0] = rom
                    self.conv_crit.append(np.inf)
                else:
                    rom_list[1:] = rom_list[:-1]
                    rom_list[0] = rom
                    dist = _convergence_criterion(rom_list, conv_crit)
                    self.conv_crit.append(dist)

            # report convergence
            self.logger.info(f'Convergence criterion in iteration {it + 1}: {self.conv_crit[-1]:e}')
            if compute_errors:
                if np.max(rom.poles().real) < 0:
                    err = fom - rom
                    rel_H2_err = err.h2_norm() / fom.h2_norm()
                else:
                    rel_H2_err = np.inf
                self.errors.append(rel_H2_err)

                self.logger.info(f'Relative H2-error in iteration {it + 1}: {rel_H2_err:e}')

            # check if convergence criterion is satisfied
            if self.conv_crit[-1] < tol:
                break

        # final reduced order model
        self._projection_matrix(r, sigma, b, c, projection)
        rom = self._pg_reductor.reduce()
        return rom

    def _projection_matrix(self, r, sigma, b, c, projection):
        fom = self.fom
        if self.version == 'V':
            V = fom.A.source.empty(reserve=r)
        else:
            W = fom.A.source.empty(reserve=r)
        for i in range(r):
            if sigma[i].imag == 0:
                sEmA = sigma[i].real * self.fom.E - self.fom.A
                if self.version == 'V':
                    Bb = fom.B.apply(b.real[i])
                    V.append(sEmA.apply_inverse(Bb))
                else:
                    CTc = fom.C.apply_adjoint(c.real[i])
                    W.append(sEmA.apply_inverse_adjoint(CTc))
            elif sigma[i].imag > 0:
                sEmA = sigma[i] * self.fom.E - self.fom.A
                if self.version == 'V':
                    Bb = fom.B.apply(b[i])
                    v = sEmA.apply_inverse(Bb)
                    V.append(v.real)
                    V.append(v.imag)
                else:
                    CTc = fom.C.apply_adjoint(c[i].conj())
                    w = sEmA.apply_inverse_adjoint(CTc)
                    W.append(w.real)
                    W.append(w.imag)

        if self.version == 'V':
            self.V = gram_schmidt(V, atol=0, rtol=0, product=None if projection == 'orth' else fom.E)
        else:
            self.V = gram_schmidt(W, atol=0, rtol=0, product=None if projection == 'orth' else fom.E)

        self._pg_reductor = LTIPGReductor(fom, self.V, self.V, projection == 'Eorth')

    def reconstruct(self, u):
        """Reconstruct high-dimensional vector from reduced vector `u`."""
        return self._pg_reductor.reconstruct(u)
Beispiel #16
0
class GenericBTReductor(BasicInterface):
    """Generic Balanced Truncation reductor.

    Parameters
    ----------
    fom
        The system which is to be reduced.
    """
    def __init__(self, fom):
        assert isinstance(fom, LTIModel)
        self.fom = fom
        self.V = None
        self.W = None
        self.sv = None
        self.sU = None
        self.sV = None

    def gramians(self):
        """Return low-rank Cholesky factors of Gramians."""
        raise NotImplementedError

    def sv_U_V(self):
        """Return singular values and vectors."""
        if self.sv is None or self.sU is None or self.sV is None:
            cf, of = self.gramians()
            U, sv, Vh = spla.svd(self.fom.E.apply2(of, cf), lapack_driver='gesvd')
            self.sv = sv
            self.sU = U.T
            self.sV = Vh
        return self.sv, self.sU, self.sV

    def error_bounds(self):
        """Returns error bounds for all possible reduced orders."""
        raise NotImplementedError

    def reduce(self, r=None, tol=None, projection='bfsr'):
        """Generic Balanced Truncation.

        Parameters
        ----------
        r
            Order of the reduced model if `tol` is `None`, maximum order
            if `tol` is specified.
        tol
            Tolerance for the error bound if `r` is `None`.
        projection
            Projection method used:

            - `'sr'`: square root method
            - `'bfsr'`: balancing-free square root method (default,
              since it avoids scaling by singular values and
              orthogonalizes the projection matrices, which might make
              it more accurate than the square root method)
            - `'biorth'`: like the balancing-free square root method,
              except it biorthogonalizes the projection matrices (using
              :func:`~pymor.algorithms.gram_schmidt.gram_schmidt_biorth`)

        Returns
        -------
        rom
            Reduced system.
        """
        assert r is not None or tol is not None
        assert r is None or 0 < r < self.fom.order
        assert projection in ('sr', 'bfsr', 'biorth')

        cf, of = self.gramians()
        sv, sU, sV = self.sv_U_V()

        # find reduced order if tol is specified
        if tol is not None:
            error_bounds = self.error_bounds()
            r_tol = np.argmax(error_bounds <= tol) + 1
            r = r_tol if r is None else min(r, r_tol)

        if r > min(len(cf), len(of)):
            raise ValueError('r needs to be smaller than the sizes of Gramian factors.')

        # compute projection matrices and find the reduced model
        self.V = cf.lincomb(sV[:r])
        self.W = of.lincomb(sU[:r])
        if projection == 'sr':
            alpha = 1 / np.sqrt(sv[:r])
            self.V.scal(alpha)
            self.W.scal(alpha)
        elif projection == 'bfsr':
            self.V = gram_schmidt(self.V, atol=0, rtol=0)
            self.W = gram_schmidt(self.W, atol=0, rtol=0)
        elif projection == 'biorth':
            self.V, self.W = gram_schmidt_biorth(self.V, self.W, product=self.fom.E)

        self.pg_reductor = LTIPGReductor(self.fom, self.W, self.V, projection in ('sr', 'biorth'))
        rom = self.pg_reductor.reduce()

        return rom

    def reconstruct(self, u):
        """Reconstruct high-dimensional vector from reduced vector `u`."""
        self.pg_reductor.reconstruct(u)
Beispiel #17
0
class TSIAReductor(BasicInterface):
    """Two-Sided Iteration Algorithm reductor.

    Parameters
    ----------
    fom
        The full-order |LTIModel| to reduce.
    """
    def __init__(self, fom):
        assert isinstance(fom, LTIModel)
        self.fom = fom
        self.V = None
        self.W = None
        self._pg_reductor = None
        self.conv_crit = None
        self.errors = None

    def reduce(self,
               rom0,
               tol=1e-4,
               maxit=100,
               num_prev=1,
               projection='orth',
               conv_crit='sigma',
               compute_errors=False):
        r"""Reduce using TSIA.

        See [XZ11]_ (Algorithm 1) and [BKS11]_.

        In exact arithmetic, TSIA is equivalent to IRKA (under some
        assumptions on the poles of the reduced model). The main
        difference in implementation is that TSIA computes the Schur
        decomposition of the reduced matrices, while IRKA computes the
        eigenvalue decomposition. Therefore, TSIA might behave better
        for non-normal reduced matrices.

        Parameters
        ----------
        rom0
            Initial reduced order model.
        tol
            Tolerance for the convergence criterion.
        maxit
            Maximum number of iterations.
        num_prev
            Number of previous iterations to compare the current
            iteration to. Larger number can avoid occasional cyclic
            behavior of TSIA.
        projection
            Projection method:

            - `'orth'`: projection matrices are orthogonalized with
              respect to the Euclidean inner product
            - `'biorth'`: projection matrices are biorthogolized with
              respect to the E product
        conv_crit
            Convergence criterion:

            - `'sigma'`: relative change in interpolation points
            - `'h2'`: relative :math:`\mathcal{H}_2` distance of
              reduced-order models
        compute_errors
            Should the relative :math:`\mathcal{H}_2`-errors of
            intermediate reduced order models be computed.

            .. warning::
                Computing :math:`\mathcal{H}_2`-errors is expensive. Use
                this option only if necessary.

        Returns
        -------
        rom
            Reduced |LTIModel|.
        """
        fom = self.fom
        assert isinstance(
            rom0, LTIModel
        ) and rom0.B.source == fom.B.source and rom0.C.range == fom.C.range
        r = rom0.order
        assert 0 < r < fom.order
        assert isinstance(num_prev, int) and num_prev >= 1
        assert projection in ('orth', 'biorth')
        assert conv_crit in ('sigma', 'h2')

        # begin logging
        self.logger.info('Starting TSIA')

        # find initial projection matrices
        self._projection_matrices(rom0, projection)

        data = (num_prev + 1) * [None]
        data[0] = rom0.poles() if conv_crit == 'sigma' else rom0
        self.conv_crit = []
        self.errors = [] if compute_errors else None
        # main loop
        for it in range(maxit):
            # project the full order model
            rom = self._pg_reductor.reduce()

            # compute convergence criterion
            data[1:] = data[:-1]
            data[0] = rom.poles() if conv_crit == 'sigma' else rom
            dist = _convergence_criterion(data, conv_crit)
            self.conv_crit.append(dist)

            # report convergence
            self.logger.info(
                f'Convergence criterion in iteration {it + 1}: {self.conv_crit[-1]:e}'
            )
            if compute_errors:
                if np.max(rom.poles().real) < 0:
                    err = fom - rom
                    rel_H2_err = err.h2_norm() / fom.h2_norm()
                else:
                    rel_H2_err = np.inf
                self.errors.append(rel_H2_err)

                self.logger.info(
                    f'Relative H2-error in iteration {it + 1}: {rel_H2_err:e}')

            # new projection matrices
            self._projection_matrices(rom, projection)

            # check convergence criterion
            if self.conv_crit[-1] < tol:
                break

        # final reduced order model
        rom = self._pg_reductor.reduce()
        return rom

    def _projection_matrices(self, rom, projection):
        fom = self.fom
        self.V, self.W = solve_sylv_schur(fom.A,
                                          rom.A,
                                          E=fom.E,
                                          Er=rom.E,
                                          B=fom.B,
                                          Br=rom.B,
                                          C=fom.C,
                                          Cr=rom.C)
        if projection == 'orth':
            self.V = gram_schmidt(self.V, atol=0, rtol=0)
            self.W = gram_schmidt(self.W, atol=0, rtol=0)
        elif projection == 'biorth':
            self.V, self.W = gram_schmidt_biorth(self.V, self.W, product=fom.E)

        self._pg_reductor = LTIPGReductor(fom, self.W, self.V,
                                          projection == 'biorth')

    def reconstruct(self, u):
        """Reconstruct high-dimensional vector from reduced vector `u`."""
        return self._pg_reductor.reconstruct(u)