Пример #1
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
Пример #2
0
    def reduce(self, r, projection='bfsr'):
        """Reduce using SOBTfv.

        Parameters
        ----------
        r
            Order of the reduced model.
        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

        Returns
        -------
        rom
            Reduced-order |SecondOrderModel|.
        """
        assert 0 < r < self.fom.order
        assert projection in ('sr', 'bfsr', 'biorth')

        # compute all necessary Gramian factors
        pcf = self.fom.gramian('pc_lrcf', mu=self.mu)
        pof = self.fom.gramian('po_lrcf', mu=self.mu)

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

        # find necessary SVDs
        _, sp, Vp = spla.svd(pof.inner(pcf), lapack_driver='gesvd')

        # compute projection matrices
        self.V = pcf.lincomb(Vp[:r])
        if projection == 'sr':
            alpha = 1 / np.sqrt(sp[:r])
            self.V.scal(alpha)
        elif projection == 'bfsr':
            self.V = gram_schmidt(self.V, atol=0, rtol=0)
        elif projection == 'biorth':
            self.V = gram_schmidt(self.V, product=self.fom.M, atol=0, rtol=0)
        self.W = self.V

        # find the reduced model
        if self.fom.parametric:
            fom_mu = self.fom.with_(**{
                op: getattr(self.fom, op).assemble(mu=self.mu)
                for op in ['M', 'E', 'K', 'B', 'Cp', 'Cv']
            },
                                    parameter_space=None)
        else:
            fom_mu = self.fom
        self._pg_reductor = SOLTIPGReductor(fom_mu, self.W, self.V,
                                            projection == 'biorth')
        rom = self._pg_reductor.reduce()
        return rom
Пример #3
0
    def reduce(self, r, projection='bfsr'):
        """Reduce using GenericSOBTpv.

        Parameters
        ----------
        r
            Order of the reduced model.
        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

        Returns
        -------
        rom
            Reduced-order |SecondOrderModel|.
        """
        assert 0 < r < self.fom.order
        assert projection in ('sr', 'bfsr', 'biorth')

        # compute all necessary Gramian factors
        gramians = self._gramians()

        if r > min(len(g) for g in gramians):
            raise ValueError(
                'r needs to be smaller than the sizes of Gramian factors.')

        # compute projection matrices
        self.V, self.W, singular_values = self._projection_matrices_and_singular_values(
            r, gramians)
        if projection == 'sr':
            alpha = 1 / np.sqrt(singular_values[: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.M)

        # find the reduced model
        if self.fom.parametric:
            fom_mu = self.fom.with_(**{
                op: getattr(self.fom, op).assemble(mu=self.mu)
                for op in ['M', 'E', 'K', 'B', 'Cp', 'Cv']
            },
                                    parameter_space=None)
        else:
            fom_mu = self.fom
        self._pg_reductor = SOLTIPGReductor(fom_mu, self.W, self.V,
                                            projection == 'biorth')
        rom = self._pg_reductor.reduce()
        return rom
Пример #4
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')
Пример #5
0
def extend_basis(U, basis, product=None, method='gram_schmidt', pod_modes=1, pod_orthonormalize=True, copy_U=True):
    assert method in ('trivial', 'gram_schmidt', 'pod')

    basis_length = len(basis)

    if method == 'trivial':
        remove = set()
        for i in range(len(U)):
            if np.any(almost_equal(U[i], basis)):
                remove.add(i)
        basis.append(U[[i for i in range(len(U)) if i not in remove]],
                     remove_from_other=(not copy_U))
    elif method == 'gram_schmidt':
        basis.append(U, remove_from_other=(not copy_U))
        gram_schmidt(basis, offset=basis_length, product=product, copy=False, check=False)
    elif method == 'pod':
        U_proj_err = U - basis.lincomb(U.inner(basis, product))

        basis.append(pod(U_proj_err, modes=pod_modes, product=product, orthonormalize=False)[0])

        if pod_orthonormalize:
            gram_schmidt(basis, offset=basis_length, product=product, copy=False, check=False)

    if len(basis) <= basis_length:
        raise ExtensionError
Пример #6
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')
Пример #7
0
Файл: h2.py Проект: 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')
Пример #8
0
def extend_basis(U, basis, product=None, method='gram_schmidt', pod_modes=1, pod_orthonormalize=True, copy_U=True):
    assert method in ('trivial', 'gram_schmidt', 'pod')

    basis_length = len(basis)

    if method == 'trivial':
        remove = set()
        for i in range(len(U)):
            if np.any(almost_equal(U[i], basis)):
                remove.add(i)
        basis.append(U[[i for i in range(len(U)) if i not in remove]],
                     remove_from_other=(not copy_U))
    elif method == 'gram_schmidt':
        basis.append(U, remove_from_other=(not copy_U))
        gram_schmidt(basis, offset=basis_length, product=product, copy=False, check=False)
    elif method == 'pod':
        U_proj_err = U - basis.lincomb(U.inner(basis, product))

        basis.append(pod(U_proj_err, modes=pod_modes, product=product, orth_tol=np.inf)[0])

        if pod_orthonormalize:
            gram_schmidt(basis, offset=basis_length, product=product, copy=False, check=False)

    if len(basis) <= basis_length:
        raise ExtensionError
Пример #9
0
def test_gram_schmidt():
    for i in (1, 32):
        b = NumpyVectorArray(np.identity(i, dtype=np.float))
        a = gram_schmidt(b)
        assert np.all(almost_equal(b, a))
    c = NumpyVectorArray([[1.0, 0], [0., 0]])
    a = gram_schmidt(c)
    assert (a.data == np.array([[1.0, 0]])).all()
Пример #10
0
def test_gram_schmidt():
    for i in (1, 32):
        b = NumpyVectorArray(np.identity(i, dtype=np.float))
        a = gram_schmidt(b)
        assert np.all(almost_equal(b, a))
    c = NumpyVectorArray([[1.0, 0], [0., 0]])
    a = gram_schmidt(c)
    assert (a.data == np.array([[1.0, 0]])).all()
Пример #11
0
    def reduce(self, r, projection='bfsr'):
        """Reduce using SOBTfv.

        Parameters
        ----------
        r
            Order of the reduced model.
        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

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

        # compute all necessary Gramian factors
        pcf = self.fom.gramian('pc_lrcf')
        pof = self.fom.gramian('po_lrcf')

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

        # find necessary SVDs
        _, sp, Vp = spla.svd(pof.inner(pcf))

        # compute projection matrices and find the reduced model
        self.V = pcf.lincomb(Vp[:r])
        if projection == 'sr':
            alpha = 1 / np.sqrt(sp[:r])
            self.V.scal(alpha)
            self.bases_are_biorthonormal = False
        elif projection == 'bfsr':
            self.V = gram_schmidt(self.V, atol=0, rtol=0)
            self.bases_are_biorthonormal = False
        elif projection == 'biorth':
            self.V = gram_schmidt(self.V, product=self.fom.M, atol=0, rtol=0)
            self.bases_are_biorthonormal = True

        self.W = self.V

        self.pg_reductor = SOLTIPGReductor(self.fom, self.W, self.V,
                                           projection == 'biorth')

        rom = self.pg_reductor.reduce()

        return rom
Пример #12
0
    def reduce(self, r, projection='bfsr'):
        """Reduce using SOBTfv.

        Parameters
        ----------
        r
            Order of the reduced model.
        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

        Returns
        -------
        rd
            Reduced system.
        """
        assert 0 < r < self.d.n
        assert projection in ('sr', 'bfsr', 'biorth')

        # compute all necessary Gramian factors
        pcf = self.d.gramian('pcf')
        pof = self.d.gramian('pof')

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

        # find necessary SVDs
        _, sp, Vp = spla.svd(pof.inner(pcf))

        # compute projection matrices and find the reduced model
        self.V = pcf.lincomb(Vp[:r])
        if projection == 'sr':
            alpha = 1 / np.sqrt(sp[:r])
            self.V.scal(alpha)
            self.bases_are_biorthonormal = False
        elif projection == 'bfsr':
            self.V = gram_schmidt(self.V, atol=0, rtol=0)
            self.bases_are_biorthonormal = False
        elif projection == 'biorth':
            self.V = gram_schmidt(self.V, product=self.d.M, atol=0, rtol=0)
            self.bases_are_biorthonormal = True

        self.W = self.V

        self.pg_reductor = GenericPGReductor(self.d, self.W, self.V, projection == 'biorth', product=self.d.M)

        rd = self.pg_reductor.reduce()

        return rd
Пример #13
0
Файл: bt.py Проект: 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
Пример #14
0
def test_gram_schmidt(vector_array):
    U = vector_array

    V = U.copy()
    onb = gram_schmidt(U, copy=True)
    assert np.all(almost_equal(U, V))
    assert np.allclose(onb.dot(onb), np.eye(len(onb)))
    assert np.all(almost_equal(U, onb.lincomb(U.dot(onb)), rtol=1e-13))

    onb2 = gram_schmidt(U, copy=False)
    assert np.all(almost_equal(onb, onb2))
    assert np.all(almost_equal(onb, U))
Пример #15
0
def test_gram_schmidt(vector_array):
    U = vector_array

    V = U.copy()
    onb = gram_schmidt(U, copy=True)
    assert np.all(almost_equal(U, V))
    assert np.allclose(onb.dot(onb), np.eye(len(onb)))
    assert np.all(almost_equal(U, onb.lincomb(U.dot(onb)), rtol=1e-13))

    onb2 = gram_schmidt(U, copy=False)
    assert np.all(almost_equal(onb, onb2))
    assert np.all(almost_equal(onb, U))
Пример #16
0
def test_gram_schmidt_with_product(operator_with_arrays_and_products):
    _, _, U, _, p, _ = operator_with_arrays_and_products

    V = U.copy()
    onb = gram_schmidt(U, product=p, copy=True)
    assert np.all(almost_equal(U, V))
    assert np.allclose(p.apply2(onb, onb), np.eye(len(onb)))
    assert np.all(almost_equal(U, onb.lincomb(p.apply2(U, onb)), rtol=1e-13))

    onb2 = gram_schmidt(U, product=p, copy=False)
    assert np.all(almost_equal(onb, onb2))
    assert np.all(almost_equal(onb, U))
Пример #17
0
def test_gram_schmidt_with_product(operator_with_arrays_and_products):
    _, _, U, _, p, _ = operator_with_arrays_and_products

    V = U.copy()
    onb = gram_schmidt(U, product=p, copy=True)
    assert np.all(almost_equal(U, V))
    assert np.allclose(p.apply2(onb, onb), np.eye(len(onb)))
    assert np.all(almost_equal(U, onb.lincomb(p.apply2(onb, U).T), rtol=1e-13))

    onb2 = gram_schmidt(U, product=p, copy=False)
    assert np.all(almost_equal(onb, onb2))
    assert np.all(almost_equal(onb, U))
Пример #18
0
Файл: h2.py Проект: 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')
Пример #19
0
def test_gram_schmidt_with_R(vector_array):
    U = vector_array

    V = U.copy()
    onb, R = gram_schmidt(U, return_R=True, copy=True)
    assert np.all(almost_equal(U, V))
    assert np.allclose(onb.dot(onb), np.eye(len(onb)))
    assert np.all(almost_equal(U, onb.lincomb(U.dot(onb)), rtol=1e-13))
    assert np.all(almost_equal(V, onb.lincomb(R.T)))

    onb2, R2 = gram_schmidt(U, return_R=True, copy=False)
    assert np.all(almost_equal(onb, onb2))
    assert np.all(R == R2)
    assert np.all(almost_equal(onb, U))
Пример #20
0
def test_gram_schmidt_with_R(vector_array):
    U = vector_array

    V = U.copy()
    onb, R = gram_schmidt(U, return_R=True, copy=True)
    assert np.all(almost_equal(U, V))
    assert np.allclose(onb.dot(onb), np.eye(len(onb)))
    assert np.all(almost_equal(U, onb.lincomb(U.dot(onb)), rtol=1e-13))
    assert np.all(almost_equal(V, onb.lincomb(R.T)))

    onb2, R2 = gram_schmidt(U, return_R=True, copy=False)
    assert np.all(almost_equal(onb, onb2))
    assert np.all(R == R2)
    assert np.all(almost_equal(onb, U))
Пример #21
0
    def reduce(self, r, projection='bfsr'):
        """Reduce using SOBTp.

        Parameters
        ----------
        r
            Order of the reduced model.
        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

        Returns
        -------
        rd
            Reduced system.
        """
        assert 0 < r < self.d.n
        assert projection in ('sr', 'bfsr', 'biorth')

        # compute all necessary Gramian factors
        gramians = self.gramians()

        if r > min(len(g) for g in gramians):
            raise ValueError('r needs to be smaller than the sizes of Gramian factors.')

        # compute projection matrices and find the reduced model
        self.V, self.W, singular_values = self.projection_matrices_and_singular_values(r, gramians)
        if projection == 'sr':
            alpha = 1 / np.sqrt(singular_values[: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.d.M)

        self.pg_reductor = GenericPGReductor(self.d, self.W, self.V, projection == 'biorth', product=self.d.M)

        rd = self.pg_reductor.reduce()

        return rd
Пример #22
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')
Пример #23
0
def projection_shifts_init(A, E, B, shift_options):
    """Find starting shift parameters for low-rank ADI iteration using
    Galerkin projection on spaces spanned by LR-ADI iterates.

    See :cite:`PK16`, pp. 92-95.

    Parameters
    ----------
    A
        The |Operator| A from the corresponding Lyapunov equation.
    E
        The |Operator| E from the corresponding Lyapunov equation.
    B
        The |VectorArray| B from the corresponding Lyapunov equation.
    shift_options
        The shift options to use (see :func:`lyap_lrcf_solver_options`).

    Returns
    -------
    shifts
        A |NumPy array| containing a set of stable shift parameters.
    """
    random_state = get_random_state(seed=shift_options['init_seed'])
    for i in range(shift_options['init_maxiter']):
        Q = gram_schmidt(B, atol=0, rtol=0)
        shifts = spla.eigvals(A.apply2(Q, Q), E.apply2(Q, Q))
        shifts = shifts[shifts.real < 0]
        if shifts.size == 0:
            # use random subspace instead of span{B} (with same dimensions)
            B = B.random(len(B), distribution='normal', random_state=random_state)
        else:
            return shifts
    raise RuntimeError('Could not generate initial shifts for low-rank ADI iteration.')
Пример #24
0
def _extend_arnoldi(A, E, V, H, f, p):
    """Extend an existing Arnoldi factorization."""

    k = len(V)

    res = f.norm()[0]
    H = np.pad(H, ((0, p), (0, p)))
    H[k, k - 1] = res
    v = f * (1 / res)
    V = V.copy()
    V.append(v)

    for i in range(k, k + p):
        v = E.apply_inverse(A.apply(v))
        V.append(v)

        _, R = gram_schmidt(V,
                            return_R=True,
                            atol=0,
                            rtol=0,
                            offset=len(V) - 1,
                            copy=False)
        H[:i + 2, i] = R[:k + p, i + 1]

        v = V[-1]

    return V[:k + p], H, v * R[k + p, k + p]
Пример #25
0
    def extend_bases(self, mu, printing=True, U=None, P=None):
        if self.unique_basis:
            U, P = self.extend_unique_basis(mu, U, P)
            return U, P

        if U is None:
            U = self.fom.solve(mu)
        if P is None:
            P = self.fom.solve_dual(mu, U=U)
        try:
            self.primal_reductor.extend_basis(U)
            # self.non_assembled_primal_reductor.extend_basis(U)
        except:
            pass
        self.primal_rom = self.primal_reductor.reduce()
        if self.non_assembled_primal_rom is not None:
            self.non_assembled_primal_rom = self.non_assembled_primal_reductor.reduce(
            )
        self.bases['RB'] = self.primal_reductor.bases['RB']
        self.RBPrimal = self.bases['RB']
        self.RBDual.append(P)
        self.RBDual = gram_schmidt(self.RBDual, product=self.opt_product)
        an, bn = len(self.RBPrimal), len(self.RBDual)
        self.dual_intermediate_fom, self.dual_rom, self.dual_reductor = self._build_dual_models(
        )
        self.dual = self.dual_reductor
        self.bases['DU'] = self.dual_reductor.bases['RB']

        if printing:
            print(
                'Enrichment completed... length of Bases are {} and {}'.format(
                    an, bn))
        return U, P
Пример #26
0
def projection_shifts_init(A, E, B, shift_options):
    """Find starting shift parameters for low-rank ADI iteration using
    Galerkin projection on spaces spanned by LR-ADI iterates.

    See [PK16]_, pp. 92-95.

    Parameters
    ----------
    A
        The |Operator| A from the corresponding Lyapunov equation.
    E
        The |Operator| E from the corresponding Lyapunov equation.
    B
        The |VectorArray| B from the corresponding Lyapunov equation.
    shift_options
        The shift options to use (see :func:`lyap_solver_options`).

    Returns
    -------
    shifts
        A |NumPy array| containing a set of stable shift parameters.
    """
    for i in range(shift_options['init_maxiter']):
        Q = gram_schmidt(B, atol=0, rtol=0)
        shifts = spla.eigvals(A.apply2(Q, Q), E.apply2(Q, Q))
        shifts = shifts[np.real(shifts) < 0]
        if shifts.size == 0:
            # use random subspace instead of span{B} (with same dimensions)
            if shift_options['init_seed'] is not None:
                np.random.seed(shift_options['init_seed'])
                np.random.seed(np.random.random() + i)
            B = B.space.make_array(np.random.rand(len(B), B.space.dim))
        else:
            return shifts
    raise RuntimeError('Could not generate initial shifts for low-rank ADI iteration.')
Пример #27
0
def test_gram_schmidt(vector_array):
    U = vector_array
    # TODO assumption here masks a potential issue with the algorithm
    #      where it fails in del instead of a proper error
    assume(len(U) > 1 or not contains_zero_vector(U))

    V = U.copy()
    onb = gram_schmidt(U, copy=True)
    assert np.all(almost_equal(U, V))
    assert np.allclose(onb.dot(onb), np.eye(len(onb)))
    # TODO maybe raise tolerances again
    assert np.all(
        almost_equal(U, onb.lincomb(onb.dot(U).T), atol=1e-13, rtol=1e-13))

    onb2 = gram_schmidt(U, copy=False)
    assert np.all(almost_equal(onb, onb2))
    assert np.all(almost_equal(onb, U))
Пример #28
0
def test_project_array():
    np.random.seed(123)
    U = NumpyVectorSpace.from_numpy(np.random.random((2, 10)))
    basis = NumpyVectorSpace.from_numpy(np.random.random((3, 10)))
    U_p = project_array(U, basis, orthonormal=False)
    onb = gram_schmidt(basis)
    U_p2 = project_array(U, onb, orthonormal=True)
    assert np.all(relative_error(U_p, U_p2) < 1e-10)
Пример #29
0
def test_project_array():
    np.random.seed(123)
    U = NumpyVectorSpace.from_numpy(np.random.random((2, 10)))
    basis = NumpyVectorSpace.from_numpy(np.random.random((3, 10)))
    U_p = project_array(U, basis, orthonormal=False)
    onb = gram_schmidt(basis)
    U_p2 = project_array(U, onb, orthonormal=True)
    assert np.all(relative_error(U_p, U_p2) < 1e-10)
Пример #30
0
def test_project_array(arrays):
    U, basis = arrays
    U_p = project_array(U, basis, orthonormal=False)
    onb = gram_schmidt(basis)
    U_p2 = project_array(U, onb, orthonormal=True)
    err = relative_error(U_p, U_p2)
    tol = np.finfo(np.float64).eps * np.linalg.cond(basis.gramian()) * 100.
    assert np.all(err < tol)
Пример #31
0
def gram_schmidt_basis_extension(basis, U, product=None, copy_basis=True, copy_U=True):
    """Extend basis using Gram-Schmidt orthonormalization.

    Parameters
    ----------
    basis
        |VectorArray| containing the basis to extend.
    U
        |VectorArray| containing the new basis vectors.
    product
        The scalar product w.r.t. which to orthonormalize; if `None`, the Euclidean
        product is used.
    copy_basis
        If `copy_basis` is `False`, the old basis is extended in-place.
    copy_U
        If `copy_U` is `False`, the new basis vectors are removed from `U`.

    Returns
    -------
    new_basis
        The extended basis.
    extension_data
        Dict containing the following fields:

            :hierarchic: `True` if `new_basis` contains `basis` as its first vectors.

    Raises
    ------
    ExtensionError
        Gram-Schmidt orthonormalization fails. This is the case when no
        vector in `U` is linearly independent from the basis.
    """
    if basis is None:
        basis = U.empty(reserve=len(U))

    basis_length = len(basis)

    new_basis = basis.copy() if copy_basis else basis
    new_basis.append(U, remove_from_other=(not copy_U))
    gram_schmidt(new_basis, offset=basis_length, product=product, copy=False)

    if len(new_basis) <= basis_length:
        raise ExtensionError

    return new_basis, {'hierarchic': True}
Пример #32
0
def test_project_array(bases):
    U = bases[0][:-2]
    basis = bases[1]
    U_p = project_array(U, basis, orthonormal=False)
    onb = gram_schmidt(basis)
    U_p2 = project_array(U, onb, orthonormal=True)
    err = relative_error(U_p, U_p2)
    tol = 3e-10
    assert np.all(err < tol)
Пример #33
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')
Пример #34
0
def test_project_array_with_product():
    np.random.seed(123)
    U = NumpyVectorSpace.from_numpy(np.random.random((1, 10)))
    basis = NumpyVectorSpace.from_numpy(np.random.random((3, 10)))
    product = np.random.random((10, 10))
    product = NumpyMatrixOperator(product.T.dot(product))
    U_p = project_array(U, basis, product=product, orthonormal=False)
    onb = gram_schmidt(basis, product=product)
    U_p2 = project_array(U, onb, product=product, orthonormal=True)
    assert np.all(relative_error(U_p, U_p2, product) < 1e-10)
Пример #35
0
def test_project_array_with_product():
    np.random.seed(123)
    U = NumpyVectorSpace.from_numpy(np.random.random((1, 10)))
    basis = NumpyVectorSpace.from_numpy(np.random.random((3, 10)))
    product = np.random.random((10, 10))
    product = NumpyMatrixOperator(product.T.dot(product))
    U_p = project_array(U, basis, product=product, orthonormal=False)
    onb = gram_schmidt(basis, product=product)
    U_p2 = project_array(U, onb, product=product, orthonormal=True)
    assert np.all(relative_error(U_p, U_p2, product) < 1e-10)
Пример #36
0
def test_gram_schmidt_with_R(vector_array):
    U = vector_array
    # TODO assumption here masks a potential issue with the algorithm
    #      where it fails in del instead of a proper error
    assume(len(U) > 1 or not contains_zero_vector(U))

    V = U.copy()
    onb, R = gram_schmidt(U, return_R=True, copy=True)
    assert np.all(almost_equal(U, V))
    assert np.allclose(onb.dot(onb), np.eye(len(onb)))
    lc = onb.lincomb(onb.dot(U).T)
    rtol = atol = 1e-13
    assert np.all(almost_equal(U, lc, rtol=rtol, atol=atol))
    assert np.all(almost_equal(V, onb.lincomb(R.T), rtol=rtol, atol=atol))

    onb2, R2 = gram_schmidt(U, return_R=True, copy=False)
    assert np.all(almost_equal(onb, onb2))
    assert np.all(R == R2)
    assert np.all(almost_equal(onb, U))
Пример #37
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')
Пример #38
0
def projection_shifts(A, E, V, prev_shifts):
    """Find further shift parameters for low-rank ADI iteration using
    Galerkin projection on spaces spanned by LR-ADI iterates.

    See [PK16]_, pp. 92-95.

    Parameters
    ----------
    A
        The |Operator| A from the corresponding Lyapunov equation.
    E
        The |Operator| E from the corresponding Lyapunov equation.
    V
        A |VectorArray| representing the currently computed iterate.
    prev_shifts
        A |NumPy array| containing the set of all previously used shift
        parameters.

    Returns
    -------
    shifts
        A |NumPy array| containing a set of stable shift parameters.
    """
    if prev_shifts[-1].imag != 0:
        Q = gram_schmidt(cat_arrays([V.real, V.imag]), atol=0, rtol=0)
    else:
        Q = gram_schmidt(V, atol=0, rtol=0)

    Ap = A.apply2(Q, Q)
    Ep = E.apply2(Q, Q)

    shifts = spla.eigvals(Ap, Ep)
    shifts.imag[abs(shifts.imag) < np.finfo(float).eps] = 0
    shifts = shifts[np.real(shifts) < 0]
    if shifts.size == 0:
        return prev_shifts
    else:
        if np.any(shifts.imag != 0):
            shifts = shifts[np.abs(shifts).argsort()]
        else:
            shifts.sort()
        return shifts
Пример #39
0
def projection_shifts(A, E, V, prev_shifts):
    """Find further shift parameters for low-rank ADI iteration using
    Galerkin projection on spaces spanned by LR-ADI iterates.

    See [PK16]_, pp. 92-95.

    Parameters
    ----------
    A
        The |Operator| A from the corresponding Lyapunov equation.
    E
        The |Operator| E from the corresponding Lyapunov equation.
    V
        A |VectorArray| representing the currently computed iterate.
    prev_shifts
        A |NumPy array| containing the set of all previously used shift
        parameters.

    Returns
    -------
    shifts
        A |NumPy array| containing a set of stable shift parameters.
    """
    if prev_shifts[-1].imag != 0:
        Q = gram_schmidt(cat_arrays([V.real, V.imag]), atol=0, rtol=0)
    else:
        Q = gram_schmidt(V, atol=0, rtol=0)

    Ap = A.apply2(Q, Q)
    Ep = E.apply2(Q, Q)

    shifts = spla.eigvals(Ap, Ep)
    shifts.imag[abs(shifts.imag) < np.finfo(float).eps] = 0
    shifts = shifts[np.real(shifts) < 0]
    if shifts.size == 0:
        return prev_shifts
    else:
        if np.any(shifts.imag != 0):
            shifts = shifts[np.abs(shifts).argsort()]
        else:
            shifts.sort()
        return shifts
Пример #40
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')
Пример #41
0
def create_bases3(gq, lq, basis_size, q, transfer='robin', silent=True):
    # nicht-adaptive Basiserstellung mit power-iteration
    if not silent:
        print("creating bases")
    bases = {}
    for space in gq["spaces"]:
        ldict = lq[space]
        # Basis mit Shift-Loesung initialisieren:
        if transfer == 'dirichlet':
            lsol = ldict["local_solution_dirichlet"]
        else:
            lsol = ldict["local_solution_robin"]
        product = ldict["range_product"]
        if transfer == 'dirichlet':
            transop = NumpyMatrixOperator(ldict["transfer_matrix_dirichlet"])
        else:
            transop = NumpyMatrixOperator(ldict["transfer_matrix_robin"])
        basis = rrf(transop, ldict["source_product"], product, q, basis_size, True)
        basis.append(lsol)
        gram_schmidt(basis, product, copy=False)
        bases[space] = basis
    return bases
Пример #42
0
def create_bases2(gq, lq, basis_size, transfer='robin', silent=True):
    # nicht-adaptive Basiserstellung (Algorithmus 4)
    if not silent:
        print("creating bases")
    bases = {}
    for space in gq["spaces"]:
        ldict = lq[space]
        # Basis mit Shift-Loesung initialisieren:
        if transfer == 'dirichlet':
            lsol = ldict["local_solution_dirichlet"]
        else:
            lsol = ldict["local_solution_robin"]
        product = ldict["range_product"]
        if transfer == 'dirichlet':
            transop = ldict["dirichlet_transfer"]
        else:
            transop = ldict["robin_transfer"]
        basis = rrf(transop, ldict["source_product"], product, 0, basis_size, True)
        basis.append(lsol)
        gram_schmidt(basis, product, copy=False)
        bases[space] = basis
    return bases
Пример #43
0
def create_bases(gq, lq, num_testvecs, transfer='robin', testlimit=None,
                 target_accuracy=1e-3, max_failure_probability=1e-15, silent=True, calC=True):
    # adaptive Basiserstellung (Algorithmus 3)
    # Berechnung der Konstanten:
    if calC:
        if not silent:
            print("calculating constants")
        # calculate_lambda_min(gq, lq)
        calculate_Psi_norm(gq, lq)
        calculate_continuity_constant(gq, lq)
        calculate_inf_sup_constant2(gq, lq)
        calculate_csis(gq, lq)
    if not silent:
        print("creating bases")
    # Basisgenerierung:
    bases = {}
    for space in gq["spaces"]:
        ldict = lq[space]
        # Basis mit Shift-Loesung initialisieren:
        if transfer == 'dirichlet':
            lsol = ldict["local_solution_dirichlet"]
        else:
            lsol = ldict["local_solution_robin"]
        product = ldict["range_product"]
        if transfer == 'dirichlet':
            transop = ldict["dirichlet_transfer"]
        else:
            transop = ldict["robin_transfer"]

        tol_i = target_accuracy*gq["inf_sup_constant"] / \
            (2*4 * gq["continuity_constant"]) / (ldict["csi"]*ldict["Psi_norm"])
        local_failure_tolerance = max_failure_probability / ((gq["coarse_grid_resolution"] - 1)**2.)
        basis = adaptive_rrf(transop, ldict["source_product"], product, tol_i,
                             local_failure_tolerance, num_testvecs, True)
        basis.append(lsol)
        gram_schmidt(basis, product, copy=False)
        bases[space] = basis
    return bases
Пример #44
0
def rrf(A, source_product=None, range_product=None, q=2, l=8, iscomplex=False):
    """Randomized range approximation of `A`.

    This is an implementation of Algorithm 4.4 in [HMT11]_.

    Given the |Operator| `A`, the return value of this method is the |VectorArray|
    `Q` whose vectors form an approximate orthonomal basis for the range of `A`.

    Parameters
    ----------
    A
        The |Operator| A.
    source_product
        Inner product |Operator| of the source of A.
    range_product
        Inner product |Operator| of the range of A.
    q
        The number of power iterations.
    l
        The block size of the normalized power iterations.
    iscomplex
        If `True`, the random vectors are chosen complex.

    Returns
    -------
    Q
        |VectorArray| which contains the basis, whose span approximates the range of A.
    """

    assert source_product is None or isinstance(source_product,
                                                OperatorInterface)
    assert range_product is None or isinstance(range_product,
                                               OperatorInterface)
    assert isinstance(A, OperatorInterface)

    R = A.source.random(l, distribution='normal')
    if iscomplex:
        R += 1j * A.source.random(l, distribution='normal')
    Q = A.apply(R)
    gram_schmidt(Q, range_product, atol=0, rtol=0, copy=False)

    for i in range(q):
        Q = A.apply_adjoint(Q)
        gram_schmidt(Q, source_product, atol=0, rtol=0, copy=False)
        Q = A.apply(Q)
        gram_schmidt(Q, range_product, atol=0, rtol=0, copy=False)

    return Q
Пример #45
0
def rrf(A, source_product=None, range_product=None, q=2, l=8, iscomplex=False):
    """Randomized range approximation of `A`.

    This is an implementation of Algorithm 4.4 in [HMT11]_.

    Given the |Operator| `A`, the return value of this method is the |VectorArray|
    `Q` whose vectors form an approximate orthonomal basis for the range of `A`.

    Parameters
    ----------
    A
        The |Operator| A.
    source_product
        Inner product |Operator| of the source of A.
    range_product
        Inner product |Operator| of the range of A.
    q
        The number of power iterations.
    l
        The block size of the normalized power iterations.
    iscomplex
        If `True`, the random vectors are chosen complex.

    Returns
    -------
    Q
        |VectorArray| which contains the basis, whose span approximates the range of A.
    """

    assert source_product is None or isinstance(source_product, OperatorInterface)
    assert range_product is None or isinstance(range_product, OperatorInterface)
    assert isinstance(A, OperatorInterface)

    R = A.source.random(l, distribution='normal')
    if iscomplex:
        R += 1j*A.source.random(l, distribution='normal')
    Q = A.apply(R)
    gram_schmidt(Q, range_product, atol=0, rtol=0, copy=False)

    for i in range(q):
        Q = A.apply_adjoint(Q)
        gram_schmidt(Q, source_product, atol=0, rtol=0, copy=False)
        Q = A.apply(Q)
        gram_schmidt(Q, range_product, atol=0, rtol=0, copy=False)

    return Q
Пример #46
0
def ei_greedy(
    U, error_norm=None, target_error=None, max_interpolation_dofs=None, projection="orthogonal", product=None
):
    """Generate data for empirical interpolation by a greedy search (EI-Greedy algorithm).

    Given a |VectorArray| `U`, this method generates a collateral basis and
    interpolation DOFs for empirical interpolation of the vectors contained in `U`.
    The returned objects can also be used to instantiate an |EmpiricalInterpolatedOperator|.

    The interpolation data is generated by a greedy search algorithm, adding in each
    loop the worst approximated vector in `U` to the collateral basis.

    Parameters
    ----------
    U
        A |VectorArray| of vectors to interpolate.
    error_norm
        Norm w.r.t. which to calculate the interpolation error. If `None`, the Euclidean norm
        is used.
    target_error
        Stop the greedy search if the largest approximation error is below this threshold.
    max_interpolation_dofs
        Stop the greedy search if the number of interpolation DOF (= dimension of the collateral
        basis) reaches this value.
    projection
        If `ei`, compute the approximation error by comparing the given vector by its
        interpolant. If `orthogonal`, compute the error by comparing with the orthogonal projection
        onto the span of the collateral basis.
    product
        If `projection == 'orthogonal'`, the product which is used to perform the projection.
        If `None`, the Euclidean product is used.

    Returns
    -------
    interpolation_dofs
        |NumPy array| of the DOFs at which the vectors are evaluated.
    collateral_basis
        |VectorArray| containing the generated collateral basis.
    data
        Dict containing the following fields:

            :errors:                Sequence of maximum approximation errors during
                                    greedy search.
            :triangularity_errors:  Sequence of maximum absolute values of interoplation
                                    matrix coefficients in the upper triangle (should
                                    be near zero).
    """

    assert projection in ("orthogonal", "ei")
    assert isinstance(U, VectorArrayInterface)

    logger = getLogger("pymor.algorithms.ei.ei_greedy")
    logger.info("Generating Interpolation Data ...")

    interpolation_dofs = np.zeros((0,), dtype=np.int32)
    collateral_basis = U.empty()
    max_errs = []
    triangularity_errs = []

    if projection == "orthogonal":
        ERR = U.copy()
        onb_collateral_basis = collateral_basis.empty()
    else:
        ERR = U

    # main loop
    while True:
        errs = ERR.l2_norm() if error_norm is None else error_norm(ERR)
        max_err_ind = np.argmax(errs)
        max_err = errs[max_err_ind]

        if len(interpolation_dofs) >= max_interpolation_dofs:
            logger.info("Maximum number of interpolation DOFs reached. Stopping extension loop.")
            logger.info(
                "Final maximum {} error with {} interpolation DOFs: {}".format(
                    "projection" if projection else "interpolation", len(interpolation_dofs), max_err
                )
            )
            break

        logger.info(
            "Maximum {} error with {} interpolation DOFs: {}".format(
                "projection" if projection else "interpolation", len(interpolation_dofs), max_err
            )
        )

        if target_error is not None and max_err <= target_error:
            logger.info("Target error reached! Stopping extension loop.")
            break

        # compute new interpolation dof and collateral basis vector
        new_vec = U.copy(ind=max_err_ind)
        new_dof = new_vec.amax()[0][0]
        if new_dof in interpolation_dofs:
            logger.info("DOF {} selected twice for interplation! Stopping extension loop.".format(new_dof))
            break
        new_dof_value = new_vec.components([new_dof])[0, 0]
        if new_dof_value == 0.0:
            logger.info(
                "DOF {} selected for interpolation has zero maximum error! Stopping extension loop.".format(new_dof)
            )
            break
        new_vec *= 1 / new_dof_value
        interpolation_dofs = np.hstack((interpolation_dofs, new_dof))
        collateral_basis.append(new_vec)
        max_errs.append(max_err)

        # update U and ERR
        new_dof_values = U.components([new_dof])
        U.axpy(-new_dof_values[:, 0], new_vec)
        if projection == "orthogonal":
            onb_collateral_basis.append(new_vec)
            gram_schmidt(onb_collateral_basis, offset=len(onb_collateral_basis) - 1, copy=False)
            coeffs = ERR.dot(onb_collateral_basis, o_ind=len(onb_collateral_basis) - 1)
            ERR.axpy(-coeffs[:, 0], onb_collateral_basis, x_ind=len(onb_collateral_basis) - 1)

    interpolation_matrix = collateral_basis.components(interpolation_dofs).T
    triangularity_errors = np.abs(interpolation_matrix - np.tril(interpolation_matrix))
    for d in range(1, len(interpolation_matrix) + 1):
        triangularity_errs.append(np.max(triangularity_errors[:d, :d]))

    logger.info("Interpolation matrix is not lower triangular with maximum error of {}".format(triangularity_errs[-1]))
    logger.info("")

    data = {"errors": max_errs, "triangularity_errors": triangularity_errs}

    return interpolation_dofs, collateral_basis, data
Пример #47
0
    def extend_basis(self, U, method='gram_schmidt', pod_modes=1, pod_orthonormalize=True, copy_U=True):
        """Extend basis by new vectors.

        Parameters
        ----------
        U
            |VectorArray| containing the new basis vectors.
        method
            Basis extension method to use. The following methods are available:

                :trivial:      Vectors in `U` are appended to the basis. Duplicate vectors
                               in the sense of :func:`~pymor.algorithms.basic.almost_equal`
                               are removed.
                :gram_schmidt: New basis vectors are orthonormalized w.r.t. to the old
                               basis using the :func:`~pymor.algorithms.gram_schmidt.gram_schmidt`
                               algorithm.
                :pod:          Append the first POD modes of the defects of the projections
                               of the vectors in U onto the existing basis
                               (e.g. for use in POD-Greedy algorithm).

            .. warning::
                In case of the `'gram_schmidt'` and `'pod'` extension methods, the existing reduced
                basis is assumed to be orthonormal w.r.t. the given inner product.

        pod_modes
            In case `method == 'pod'`, the number of POD modes that shall be appended to
            the basis.
        pod_orthonormalize
            If `True` and `method == 'pod'`, re-orthonormalize the new basis vectors obtained
            by the POD in order to improve numerical accuracy.
        copy_U
            If `copy_U` is `False`, the new basis vectors might be removed from `U`.

        Raises
        ------
        ExtensionError
            Raised when the selected extension method does not yield a basis of increased
            dimension.
        """
        assert method in ('trivial', 'gram_schmidt', 'pod')

        basis_length = len(self.RB)

        if method == 'trivial':
            remove = set()
            for i in range(len(U)):
                if np.any(almost_equal(U[i], self.RB)):
                    remove.add(i)
            self.RB.append(U[[i for i in range(len(U)) if i not in remove]],
                           remove_from_other=(not copy_U))
        elif method == 'gram_schmidt':
            self.RB.append(U, remove_from_other=(not copy_U))
            gram_schmidt(self.RB, offset=basis_length, product=self.product, copy=False)
        elif method == 'pod':
            if self.product is None:
                U_proj_err = U - self.RB.lincomb(U.dot(self.RB))
            else:
                U_proj_err = U - self.RB.lincomb(self.product.apply2(U, self.RB))

            self.RB.append(pod(U_proj_err, modes=pod_modes, product=self.product, orthonormalize=False)[0])

            if pod_orthonormalize:
                gram_schmidt(self.RB, offset=basis_length, product=self.product, copy=False)

        if len(self.RB) <= basis_length:
            raise ExtensionError
Пример #48
0
    def reduce(self, r, projection='bfsr'):
        """Reduce using SOBT.

        Parameters
        ----------
        r
            Order of the reduced model.
        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

        Returns
        -------
        rd
            Reduced system.
        """
        assert 0 < r < self.d.n
        assert projection in ('sr', 'bfsr', 'biorth')

        # compute all necessary Gramian factors
        pcf = self.d.gramian('pcf')
        pof = self.d.gramian('pof')
        vcf = self.d.gramian('vcf')
        vof = self.d.gramian('vof')

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

        # find necessary SVDs
        Up, sp, Vp = spla.svd(pof.inner(pcf))
        Up = Up.T
        Uv, sv, Vv = spla.svd(vof.inner(vcf, product=self.d.M))
        Uv = Uv.T

        # compute projection matrices and find the reduced model
        self.V1 = pcf.lincomb(Vp[:r])
        self.W1 = pof.lincomb(Up[:r])
        self.V2 = vcf.lincomb(Vv[:r])
        self.W2 = vof.lincomb(Uv[:r])
        if projection == 'sr':
            alpha1 = 1 / np.sqrt(sp[:r])
            self.V1.scal(alpha1)
            self.W1.scal(alpha1)
            alpha2 = 1 / np.sqrt(sv[:r])
            self.V2.scal(alpha2)
            self.W2.scal(alpha2)
            W1TV1invW1TV2 = self.W1.inner(self.V2)
            projected_ops = {'M': IdentityOperator(NumpyVectorSpace(r, self.d.state_space.id))}
        elif projection == 'bfsr':
            self.V1 = gram_schmidt(self.V1, atol=0, rtol=0)
            self.W1 = gram_schmidt(self.W1, atol=0, rtol=0)
            self.V2 = gram_schmidt(self.V2, atol=0, rtol=0)
            self.W2 = gram_schmidt(self.W2, atol=0, rtol=0)
            W1TV1invW1TV2 = spla.solve(self.W1.inner(self.V1), self.W1.inner(self.V2))
            projected_ops = {'M': project(self.d.M, range_basis=self.W2, source_basis=self.V2)}
        elif projection == 'biorth':
            self.V1, self.W1 = gram_schmidt_biorth(self.V1, self.W1)
            self.V2, self.W2 = gram_schmidt_biorth(self.V2, self.W2, product=self.d.M)
            W1TV1invW1TV2 = self.W1.inner(self.V2)
            projected_ops = {'M': IdentityOperator(NumpyVectorSpace(r, self.d.state_space.id))}

        projected_ops.update({'E': project(self.d.E,
                                           range_basis=self.W2,
                                           source_basis=self.V2),
                              'K': project(self.d.K,
                                           range_basis=self.W2,
                                           source_basis=self.V1.lincomb(W1TV1invW1TV2.T)),
                              'B': project(self.d.B,
                                           range_basis=self.W2,
                                           source_basis=None),
                              'Cp': project(self.d.Cp,
                                            range_basis=None,
                                            source_basis=self.V1.lincomb(W1TV1invW1TV2.T)),
                              'Cv': project(self.d.Cv,
                                            range_basis=None,
                                            source_basis=self.V2)})

        rd = self.d.with_(operators=projected_ops,
                          visualizer=None, estimator=None,
                          cache_region=None, name=self.d.name + '_reduced')
        rd.disable_logging()

        return rd
Пример #49
0
def ei_greedy(U, error_norm=None, atol=None, rtol=None, max_interpolation_dofs=None,
              projection='ei', product=None, copy=True, pool=dummy_pool):
    """Generate data for empirical interpolation using EI-Greedy algorithm.

    Given a |VectorArray| `U`, this method generates a collateral basis and
    interpolation DOFs for empirical interpolation of the vectors contained in `U`.
    The returned objects can be used to instantiate an |EmpiricalInterpolatedOperator|
    (with `triangular=True`).

    The interpolation data is generated by a greedy search algorithm, where in each
    loop iteration the worst approximated vector in `U` is added to the collateral basis.

    Parameters
    ----------
    U
        A |VectorArray| of vectors to interpolate.
    error_norm
        Norm w.r.t. which to calculate the interpolation error. If `None`, the Euclidean norm
        is used.
    atol
        Stop the greedy search if the largest approximation error is below this threshold.
    rtol
        Stop the greedy search if the largest relative approximation error is below this threshold.
    max_interpolation_dofs
        Stop the greedy search if the number of interpolation DOF (= dimension of the collateral
        basis) reaches this value.
    projection
        If `'ei'`, compute the approximation error by comparing the given vectors by their
        interpolants. If `'orthogonal'`, compute the error by comparing with the orthogonal projection
        onto the span of the collateral basis.
    product
        If `projection == 'orthogonal'`, the inner product which is used to perform the projection.
        If `None`, the Euclidean product is used.
    copy
        If `False`, `U` will be modified during executing of the algorithm.
    pool
        If not `None`, the |WorkerPool| to use for parallelization.

    Returns
    -------
    interpolation_dofs
        |NumPy array| of the DOFs at which the vectors are evaluated.
    collateral_basis
        |VectorArray| containing the generated collateral basis.
    data
        Dict containing the following fields:

            :errors:                Sequence of maximum approximation errors during
                                    greedy search.
            :triangularity_errors:  Sequence of maximum absolute values of interoplation
                                    matrix coefficients in the upper triangle (should
                                    be near zero).
    """

    assert projection in ('orthogonal', 'ei')

    if pool:  # dispatch to parallel implemenation
        if projection == 'ei':
            pass
        elif projection == 'orthogonal':
            raise ValueError('orthogonal projection not supported in parallel implementation')
        else:
            assert False
        assert isinstance(U, (VectorArrayInterface, RemoteObjectInterface))
        with RemoteObjectManager() as rom:
            if isinstance(U, VectorArrayInterface):
                U = rom.manage(pool.scatter_array(U))
            return _parallel_ei_greedy(U, error_norm=error_norm, atol=atol, rtol=rtol,
                                       max_interpolation_dofs=max_interpolation_dofs, copy=copy, pool=pool)

    assert isinstance(U, VectorArrayInterface)

    logger = getLogger('pymor.algorithms.ei.ei_greedy')
    logger.info('Generating Interpolation Data ...')

    interpolation_dofs = np.zeros((0,), dtype=np.int32)
    collateral_basis = U.empty()
    max_errs = []
    triangularity_errs = []

    if copy:
        U = U.copy()

    if projection == 'orthogonal':
        ERR = U.copy()
        onb_collateral_basis = collateral_basis.empty()
    else:
        ERR = U

    errs = ERR.l2_norm() if error_norm is None else error_norm(ERR)
    max_err_ind = np.argmax(errs)
    initial_max_err = max_err = errs[max_err_ind]

    # main loop
    while True:
        if max_interpolation_dofs is not None and len(interpolation_dofs) >= max_interpolation_dofs:
            logger.info('Maximum number of interpolation DOFs reached. Stopping extension loop.')
            logger.info('Final maximum {} error with {} interpolation DOFs: {}'.format(
                'projection' if projection else 'interpolation', len(interpolation_dofs), max_err))
            break

        logger.info('Maximum {} error with {} interpolation DOFs: {}'
                    .format('projection' if projection else 'interpolation',
                            len(interpolation_dofs), max_err))

        if atol is not None and max_err <= atol:
            logger.info('Absolute error tolerance reached! Stopping extension loop.')
            break

        if rtol is not None and max_err / initial_max_err <= rtol:
            logger.info('Relative error tolerance reached! Stopping extension loop.')
            break

        # compute new interpolation dof and collateral basis vector
        new_vec = U.copy(ind=max_err_ind)
        new_dof = new_vec.amax()[0][0]
        if new_dof in interpolation_dofs:
            logger.info('DOF {} selected twice for interplation! Stopping extension loop.'.format(new_dof))
            break
        new_dof_value = new_vec.components([new_dof])[0, 0]
        if new_dof_value == 0.:
            logger.info('DOF {} selected for interpolation has zero maximum error! Stopping extension loop.'
                        .format(new_dof))
            break
        new_vec *= 1 / new_dof_value
        interpolation_dofs = np.hstack((interpolation_dofs, new_dof))
        collateral_basis.append(new_vec)
        max_errs.append(max_err)

        # update U and ERR
        new_dof_values = U.components([new_dof])
        U.axpy(-new_dof_values[:, 0], new_vec)
        if projection == 'orthogonal':
            onb_collateral_basis.append(new_vec)
            gram_schmidt(onb_collateral_basis, offset=len(onb_collateral_basis) - 1, copy=False)
            coeffs = ERR.dot(onb_collateral_basis, o_ind=len(onb_collateral_basis) - 1)
            ERR.axpy(-coeffs[:, 0], onb_collateral_basis, x_ind=len(onb_collateral_basis) - 1)
        errs = ERR.l2_norm() if error_norm is None else error_norm(ERR)
        max_err_ind = np.argmax(errs)
        max_err = errs[max_err_ind]

    interpolation_matrix = collateral_basis.components(interpolation_dofs).T
    triangularity_errors = np.abs(interpolation_matrix - np.tril(interpolation_matrix))
    for d in range(1, len(interpolation_matrix) + 1):
        triangularity_errs.append(np.max(triangularity_errors[:d, :d]))

    if len(triangularity_errs) > 0:
        logger.info('Interpolation matrix is not lower triangular with maximum error of {}'
                    .format(triangularity_errs[-1]))

    data = {'errors': max_errs, 'triangularity_errors': triangularity_errs}

    return interpolation_dofs, collateral_basis, data
Пример #50
0
def estimate_image(operators=(), vectors=(),
                   domain=None, extends=False, orthonormalize=True, product=None,
                   riesz_representatives=False):
    """Estimate the image of given operators for all mu.

    Let `operators` be a list of |Operators| with common source and domain, and let
    `vectors` be a list of |VectorArrays| or vector-like |Operators| in the range
    of these operators. Given a |VectorArray| `domain` of vectors in the source of the
    operators, this algorithms determines a |VectorArray| `image` of range vectors
    such that the linear span of `image` contains:

    - `op.apply(U, mu=mu)` for all operators `op` in `operators`, for all possible |Parameters|
      `mu` and for all |VectorArrays| `U` contained in the linear span of `domain`,
    - `U` for all |VectorArrays| in `vectors`,
    - `v.as_vector(mu)` for all |Operators| in `vectors` and all possible |Parameters| `mu`.

    The algorithm will try to choose `image` as small as possible. However, no optimality
    is guaranteed.

    Parameters
    ----------
    operators
        See above.
    vectors
        See above.
    domain
        See above. If `None`, an empty `domain` |VectorArray| is assumed.
    extends
        For some operators, e.g. |EmpiricalInterpolatedOperator|, as well as for all
        elements of `vectors`, `image` is estimated independently from the choice of
        `domain`.  If `extends` is `True`, such operators are ignored. (This is useful
        in case these vectors have already been obtained by earlier calls to this
        function.)
    orthonormalize
        Compute an orthonormal basis for the linear span of `image` using the
        :func:`~pymor.algorithms.gram_schmidt.gram_schmidt` algorithm.
    product
        Inner product |Operator| w.r.t. which to orthonormalize.
    riesz_representatives
        If `True`, compute Riesz representatives of the vectors in `image` before
        orthonormalizing. (Useful for norm computation when the range of the
        `operators` is a dual space.)

    Returns
    -------
    The |VectorArray| `image`.

    Raises
    ------
    ImageCollectionError
        Is raised when for a given |Operator| no image estimate is possible.
    """
    assert operators or vectors
    domain_space = operators[0].source if operators else None
    image_space = operators[0].range if operators \
        else vectors[0].space if isinstance(vectors[0], VectorArrayInterface) \
        else vectors[0].range if vectors[0].range != NumpyVectorSpace(1) \
        else vectors[0].source
    assert all(
        isinstance(v, VectorArrayInterface) and v in image_space or
        v.source == NumpyVectorSpace(1) and v.range == image_space and v.linear or
        v.range == NumpyVectorSpace(1) and v.source == image_space and v.linear
        for v in vectors
    )
    assert all(op.source == domain_space and op.range == image_space for op in operators)
    assert domain is None or domain_space is None or domain in domain_space
    assert product is None or product.source == product.range == image_space

    def collect_operator_ranges(op, source, image):
        if isinstance(op, (LincombOperator, SelectionOperator)):
            for o in op.operators:
                collect_operator_ranges(o, source, image)
        elif isinstance(op, EmpiricalInterpolatedOperator):
            if hasattr(op, 'collateral_basis') and not extends:
                image.append(op.collateral_basis)
        elif isinstance(op, Concatenation):
            firstrange = op.first.range.empty()
            collect_operator_ranges(op.first, source, firstrange)
            collect_operator_ranges(op.second, firstrange, image)
        elif op.linear and not op.parametric:
            image.append(op.apply(source))
        else:
            raise ImageCollectionError(op)

    def collect_vector_ranges(op, image):
        if isinstance(op, (LincombOperator, SelectionOperator)):
            for o in op.operators:
                collect_vector_ranges(o, image)
        elif isinstance(op, AdjointOperator):
            if op.source not in image_space:
                raise ImageCollectionError(op)  # Not implemented
            operator = Concatenation(op.range_product, op.operator) if op.range_product else op.operator
            collect_operator_ranges(operator, NumpyVectorArray(np.ones(1)), image)
        elif op.linear and not op.parametric:
            image.append(op.as_vector())
        else:
            raise ImageCollectionError(op)

    image = image_space.empty()
    if not extends:
        for f in vectors:
            collect_vector_ranges(f, image)

    if operators and domain is None:
        domain = domain_space.empty()
    for op in operators:
        collect_operator_ranges(op, domain, image)

    if riesz_representatives and product:
        image = product.apply_inverse(image)

    if orthonormalize:
        gram_schmidt(image, product=product, copy=False)

    return image
Пример #51
0
def estimate_image_hierarchical(operators=(), vectors=(), domain=None, extends=None,
                                orthonormalize=True, product=None, riesz_representatives=False):
    """Estimate the image of given operators for all mu.

    This is an extended version of :func:`estimate_image`, which calls
    :func:`estimate_image` individually for each vector of `domain`.

    As a result, the vectors in the returned `image` |VectorArray| will
    be ordered by the `domain` vector they correspond to (starting with
    vectors which correspond to the `functionals` and to the |Operators| for
    which the image is estimated independently from `domain`).

    This function also returns an `image_dims` list, such that the first
    `image_dims[i+1]` vectors of `image` correspond to the first `i`
    vectors of `domain` (the first `image_dims[0]` vectors correspond
    to `vectors` and to the |Operators| with fixed image estimate).

    Parameters
    ----------
    operators
        See :func:`estimate_image`.
    vectors
        See :func:`estimate_image`.
    domain
        See :func:`estimate_image`.
    extends
        When additional vectors have been appended to the `domain` |VectorArray|
        after :func:`estimate_image_hierarchical` has been called, and
        :func:`estimate_image_hierarchical` shall be called again for the extended
        `domain` array, `extends` can be set to `(image, image_dims)`, where
        `image`, `image_dims` are the return values of the last
        :func:`estimate_image_hierarchical` call. The old `domain` vectors will
        then be skipped during computation and `image`, `image_dims` will be
        modified in-place.
    orthonormalize
        See :func:`estimate_image`.
    product
        See :func:`estimate_image`.
    riesz_representatives
        See :func:`estimate_image`.

    Returns
    -------
    image
        See above.
    image_dims
        See above.

    Raises
    ------
    ImageCollectionError
        Is raised when for a given |Operator| no image estimate is possible.
    """
    assert operators or vectors
    domain_space = operators[0].source if operators else None
    image_space = operators[0].range if operators \
        else vectors[0].space if isinstance(vectors[0], VectorArrayInterface) \
        else vectors[0].range if vectors[0].range != NumpyVectorSpace(1) \
        else vectors[0].source
    assert all(
        isinstance(v, VectorArrayInterface) and v in image_space or
        v.source == NumpyVectorSpace(1) and v.range == image_space and v.linear or
        v.range == NumpyVectorSpace(1) and v.source == image_space and v.linear
        for v in vectors
    )
    assert all(op.source == domain_space and op.range == image_space for op in operators)
    assert domain is None or domain_space is None or domain in domain_space
    assert product is None or product.source == product.range == image_space
    assert extends is None or len(extends) == 2

    logger = getLogger('pymor.algorithms.image.estimate_image_hierarchical')

    if operators and domain is None:
        domain = domain_space.empty()

    if extends:
        image = extends[0]
        image_dims = extends[1]
        ind_range = list(range(len(image_dims) - 1, len(domain))) if operators else list(range(len(image_dims) - 1, 0))
    else:
        image = image_space.empty()
        image_dims = []
        ind_range = list(range(-1, len(domain))) if operators else [-1]

    for i in ind_range:
        logger.info('Estimating image for basis vector {} ...'.format(i))
        if i == -1:
            new_image = estimate_image(operators, vectors, None, extends=False,
                                       orthonormalize=False, product=product,
                                       riesz_representatives=riesz_representatives)
        else:
            new_image = estimate_image(operators, [], domain.copy(i), extends=True,
                                       orthonormalize=False, product=product,
                                       riesz_representatives=riesz_representatives)

        gram_schmidt_offset = len(image)
        image.append(new_image, remove_from_other=True)
        if orthonormalize:
            with logger.block('Orthonormalizing ...'):
                gram_schmidt(image, offset=gram_schmidt_offset, product=product, copy=False)
            image_dims.append(len(image))

    return image, image_dims
Пример #52
0
def projection_shifts(A, E, Z, W, prev_shifts, shift_options):
    """Find further shift parameters for low-rank ADI iteration using
    Galerkin projection on spaces spanned by LR-ADI iterates.

    See [PK16]_, pp. 92-95.

    Parameters
    ----------
    A
        The |Operator| A from the corresponding Lyapunov equation.
    E
        The |Operator| E from the corresponding Lyapunov equation.
    Z
        A |VectorArray| representing the currently computed low-rank solution factor.
    W
        A |VectorArray| representing the currently computed low-rank residual factor.
    prev_shifts
        A |NumPy array| containing the set of all previously used shift parameters.
    shift_options
        The shift options to use (see :func:`lyap_solver_options`).

    Returns
    -------
    shifts
        A |NumPy array| containing a set of stable shift parameters.
    """
    u = shift_options['z_columns']
    L = prev_shifts.size
    r = len(W)
    d = L - u
    if d < 0:
        u = L
        d = 0
    if prev_shifts[-u].imag < 0:
        u = u + 1

    Vu = Z[-u * r:]  # last u matrices V added to solution factor Z

    if shift_options['implicit_subspace']:
        B = np.zeros((u, u))
        G = np.zeros((u, 1))
        Ir = np.eye(r)
        iC = np.where(np.imag(prev_shifts) > 0)[0]  # complex shifts indices (first shift of complex pair)
        iR = np.where(np.isreal(prev_shifts))[0]  # real shifts indices
        iC = iC[iC >= d]
        iR = iR[iR >= d]
        i = 0

        while i < u:
            rS = iR[iR < d + i]
            cS = iC[iC < d + i]
            rp = prev_shifts[d + i].real
            cp = prev_shifts[d + i].imag
            G[i, 0] = np.sqrt(-2 * rp)
            if cp == 0:
                B[i, i] = rp
                if rS.size > 0:
                    B[i, rS - d] = -2 * np.sqrt(rp*np.real(prev_shifts[rS]))
                if cS.size > 0:
                    B[i, cS - d] = -2 * np.sqrt(2*rp*np.real(prev_shifts[cS]))
                i = i + 1
            else:
                sri = np.sqrt(rp**2+cp**2)
                B[i: i + 2, i: i + 2] = [[2*rp, -sri], [sri, 0]]
                if rS.size > 0:
                    B[i, rS - d] = -2 * np.sqrt(2*rp*np.real(prev_shifts[rS]))
                if cS.size > 0:
                    B[i, cS - d] = -4 * np.sqrt(rp*np.real(prev_shifts[cS]))
                i = i + 2
        B = spla.kron(B, Ir)
        G = spla.kron(G, Ir)

        s, v = spla.svd(Vu.gramian(), full_matrices=False)[1:3]
        P = v.T.dot(np.diag(1. / np.sqrt(s)))
        Q = Vu.to_numpy().T.dot(P)

        E_V = E.apply(Vu).to_numpy().T
        T = Q.T.dot(E_V)
        Ap = Q.T.dot(W.to_numpy().T).dot(G.T).dot(P) + T.dot(B.dot(P))
        Ep = T.dot(P)
    else:
        Q = gram_schmidt(Vu, atol=0, rtol=0)
        Ap = A.apply2(Q, Q)
        Ep = E.apply2(Q, Q)

    shifts = spla.eigvals(Ap, Ep)
    shifts = shifts[np.real(shifts) < 0]
    if shifts.size == 0:
        return np.concatenate((prev_shifts, prev_shifts))
    else:
        return np.concatenate((prev_shifts, shifts))
Пример #53
0
def estimate_image(operators=(), vectors=(),
                   domain=None, extends=False, orthonormalize=True, product=None,
                   riesz_representatives=False):
    """Estimate the image of given |Operators| for all mu.

    Let `operators` be a list of |Operators| with common source and range, and let
    `vectors` be a list of |VectorArrays| or vector-like |Operators| in the range
    of these operators. Given a |VectorArray| `domain` of vectors in the source of the
    operators, this algorithms determines a |VectorArray| `image` of range vectors
    such that the linear span of `image` contains:

    - `op.apply(U, mu=mu)` for all operators `op` in `operators`, for all possible |Parameters|
      `mu` and for all |VectorArrays| `U` contained in the linear span of `domain`,
    - `U` for all |VectorArrays| in `vectors`,
    - `v.as_range_array(mu)` for all |Operators| in `vectors` and all possible |Parameters| `mu`.

    The algorithm will try to choose `image` as small as possible. However, no optimality
    is guaranteed. The image estimation algorithm is specified by :class:`CollectOperatorRangeRules`
    and :class:`CollectVectorRangeRules`.

    Parameters
    ----------
    operators
        See above.
    vectors
        See above.
    domain
        See above. If `None`, an empty `domain` |VectorArray| is assumed.
    extends
        For some operators, e.g. |EmpiricalInterpolatedOperator|, as well as for all
        elements of `vectors`, `image` is estimated independently from the choice of
        `domain`.  If `extends` is `True`, such operators are ignored. (This is useful
        in case these vectors have already been obtained by earlier calls to this
        function.)
    orthonormalize
        Compute an orthonormal basis for the linear span of `image` using the
        :func:`~pymor.algorithms.gram_schmidt.gram_schmidt` algorithm.
    product
        Inner product |Operator| w.r.t. which to orthonormalize.
    riesz_representatives
        If `True`, compute Riesz representatives of the vectors in `image` before
        orthonormalizing (useful for dual norm computation when the range of the
        `operators` is a dual space).

    Returns
    -------
    The |VectorArray| `image`.

    Raises
    ------
    ImageCollectionError
        Is raised when for a given |Operator| no image estimate is possible.
    """
    assert operators or vectors
    domain_space = operators[0].source if operators else None
    image_space = operators[0].range if operators \
        else vectors[0].space if isinstance(vectors[0], VectorArrayInterface) \
        else vectors[0].range
    assert all(op.source == domain_space and op.range == image_space for op in operators)
    assert all(
        isinstance(v, VectorArrayInterface) and (
            v in image_space
        ) or
        isinstance(v, OperatorInterface) and (
            v.range == image_space and isinstance(v.source, NumpyVectorSpace) and v.linear
        )
        for v in vectors
    )
    assert domain is None or domain_space is None or domain in domain_space
    assert product is None or product.source == product.range == image_space

    image = image_space.empty()
    if not extends:
        rules = CollectVectorRangeRules(image)
        for v in vectors:
            try:
                rules.apply(v)
            except NoMatchingRuleError as e:
                raise ImageCollectionError(e.obj)

    if operators and domain is None:
        domain = domain_space.empty()
    for op in operators:
        rules = CollectOperatorRangeRules(domain, image, extends)
        try:
            rules.apply(op)
        except NoMatchingRuleError as e:
            raise ImageCollectionError(e.obj)

    if riesz_representatives and product:
        image = product.apply_inverse(image)

    if orthonormalize:
        gram_schmidt(image, product=product, copy=False)

    return image
Пример #54
0
def adaptive_rrf(A, source_product=None, range_product=None, tol=1e-4,
                 failure_tolerance=1e-15, num_testvecs=20, lambda_min=None, iscomplex=False):
    r"""Adaptive randomized range approximation of `A`.

    This is an implementation of Algorithm 1 in [BS18]_.

    Given the |Operator| `A`, the return value of this method is the |VectorArray|
    `B` with the property

    .. math::
        \Vert A - P_{span(B)} A \Vert \leq tol

    with a failure probability smaller than `failure_tolerance`, where the norm denotes the
    operator norm. The inner product of the range of `A` is given by `range_product` and
    the inner product of the source of `A` is given by `source_product`.

    Parameters
    ----------
    A
        The |Operator| A.
    source_product
        Inner product |Operator| of the source of A.
    range_product
        Inner product |Operator| of the range of A.
    tol
        Error tolerance for the algorithm.
    failure_tolerance
        Maximum failure probability.
    num_testvecs
        Number of test vectors.
    lambda_min
        The smallest eigenvalue of source_product.
        If `None`, the smallest eigenvalue is computed using scipy.
    iscomplex
        If `True`, the random vectors are chosen complex.

    Returns
    -------
    B
        |VectorArray| which contains the basis, whose span approximates the range of A.
    """

    assert source_product is None or isinstance(source_product, OperatorInterface)
    assert range_product is None or isinstance(range_product, OperatorInterface)
    assert isinstance(A, OperatorInterface)

    B = A.range.empty()

    R = A.source.random(num_testvecs, distribution='normal')
    if iscomplex:
        R += 1j*A.source.random(num_testvecs, distribution='normal')

    if source_product is None:
        lambda_min = 1
    elif lambda_min is None:
        def mv(v):
            return source_product.apply(source_product.source.from_numpy(v)).to_numpy()

        def mvinv(v):
            return source_product.apply_inverse(source_product.range.from_numpy(v)).to_numpy()
        L = LinearOperator((source_product.source.dim, source_product.range.dim), matvec=mv)
        Linv = LinearOperator((source_product.range.dim, source_product.source.dim), matvec=mvinv)
        lambda_min = eigsh(L, sigma=0, which="LM", return_eigenvectors=False, k=1, OPinv=Linv)[0]

    testfail = failure_tolerance / min(A.source.dim, A.range.dim)
    testlimit = np.sqrt(2. * lambda_min) * erfinv(testfail**(1. / num_testvecs)) * tol
    maxnorm = np.inf
    M = A.apply(R)

    while(maxnorm > testlimit):
        basis_length = len(B)
        v = A.source.random(distribution='normal')
        if iscomplex:
            v += 1j*A.source.random(distribution='normal')
        B.append(A.apply(v))
        gram_schmidt(B, range_product, atol=0, rtol=0, offset=basis_length, copy=False)
        M -= B.lincomb(B.inner(M, range_product).T)
        maxnorm = np.max(M.norm(range_product))

    return B
Пример #55
0
def arnoldi(A, E, b, sigma, trans=False):
    r"""Rational Arnoldi algorithm.

    If `trans == False`, using Arnoldi process, computes a real
    orthonormal basis for the rational Krylov subspace

    .. math::
        \mathrm{span}\{(\sigma_1 E - A)^{-1} b, (\sigma_2 E - A)^{-1} b, \ldots,
        (\sigma_r E - A)^{-1} b\},

    otherwise, computes the same for

    .. math::
        \mathrm{span}\{(\sigma_1 E - A)^{-T} b^T, (\sigma_2 E - A)^{-T} b^T,
        \ldots, (\sigma_r E - A)^{-T} b^T\}.

    Interpolation points in `sigma` are allowed to repeat (in any
    order). Then, in the above expression,

    .. math::
        \underbrace{(\sigma_i E - A)^{-1} b, \ldots,
        (\sigma_i E - A)^{-1} b}_{m \text{ times}}

    is replaced by

    .. math::
        (\sigma_i E - A)^{-1} b, (\sigma_i E - A)^{-2} b, \ldots,
        (\sigma_i E - A)^{-m} b.

    Analogously for the `trans == True` case.

    Parameters
    ----------
    A
        Real |Operator| A.
    E
        Real |Operator| E.
    b
        Real vector-like operator (if trans is False) or functional (if
        trans is True).
    sigma
        Interpolation points (closed under conjugation).
    trans
        Boolean, see above.

    Returns
    -------
    V
        Projection matrix.
    """
    assert not trans and b.source.dim == 1 or trans and b.range.dim == 1

    r = len(sigma)
    V = A.source.empty(reserve=r)

    v = b.as_vector()
    v.scal(1 / v.l2_norm()[0])

    for i in range(r):
        if sigma[i].imag == 0:
            sEmA = sigma[i].real * E - A

            if not trans:
                v = sEmA.apply_inverse(v)
            else:
                v = sEmA.apply_inverse_adjoint(v)

            V.append(v)
            V = gram_schmidt(V, atol=0, rtol=0, offset=len(V) - 1, copy=False)
            v = V[-1]
        elif sigma[i].imag > 0:
            sEmA = sigma[i] * E - A

            if not trans:
                v = sEmA.apply_inverse(v)
            else:
                v = sEmA.apply_inverse_adjoint(v)

            V.append(v.real)
            V.append(v.imag)
            V = gram_schmidt(V, atol=0, rtol=0, offset=len(V) - 2, copy=False)
            v = V[-1]

    return V
Пример #56
0
def pod_basis_extension(basis, U, count=1, copy_basis=True, product=None, orthonormalize=True):
    """Extend basis with the first `count` POD modes of the projection of `U` onto the
    orthogonal complement of the basis.

    Note that the provided basis is assumed to be orthonormal w.r.t. the provided
    scalar product!

    Parameters
    ----------
    basis
        |VectorArray| containing the basis to extend. The basis is expected to be
        orthonormal w.r.t. `product`.
    U
        |VectorArray| containing the vectors to which the POD is applied.
    count
        Number of POD modes that are to be appended to the basis.
    product
        The scalar product w.r.t. which to orthonormalize; if `None`, the Euclidean
        product is used.
    copy_basis
        If `copy_basis` is `False`, the old basis is extended in-place.
    orthonormalize
        If `True`, re-orthonormalize the new basis vectors obtained by the POD
        in order to improve numerical accuracy.

    Returns
    -------
    new_basis
        The extended basis.
    extension_data
        Dict containing the following fields:

            :hierarchic: `True` if `new_basis` contains `basis` as its first vectors.

    Raises
    ------
    ExtensionError
        POD produces no new vectors. This is the case when no vector in `U`
        is linearly independent from the basis.
    """
    if basis is None:
        return pod(U, modes=count, product=product)[0], {'hierarchic': True}

    basis_length = len(basis)

    new_basis = basis.copy() if copy_basis else basis

    if product is None:
        U_proj_err = U - basis.lincomb(U.dot(basis))
    else:
        U_proj_err = U - basis.lincomb(product.apply2(U, basis))

    new_basis.append(pod(U_proj_err, modes=count, product=product, orthonormalize=False)[0])

    if orthonormalize:
        gram_schmidt(new_basis, offset=len(basis), product=product, copy=False)

    if len(new_basis) <= basis_length:
        raise ExtensionError

    return new_basis, {'hierarchic': True}
Пример #57
0
def reduce_residual(operator, functional=None, RB=None, product=None, extends=None):
    """Generic reduced basis residual reductor.

    Given an operator and a functional, the concatenation of residual operator
    with the Riesz isomorphism is given by::

        riesz_residual.apply(U, mu)
            == product.apply_inverse(operator.apply(U, mu) - functional.as_vector(mu))

    This reductor determines a low-dimensional subspace of image of a reduced
    basis space under `riesz_residual`, computes an orthonormal basis `residual_range`
    of this range spaces and then returns the Petrov-Galerkin projection ::

        projected_riesz_residual
            === riesz_residual.projected(range_basis=residual_range, source_basis=RB)

    of the `riesz_residual` operator. Given an reduced basis coefficient vector `u`,
    the dual norm of the residual can then be computed as ::

        projected_riesz_residual.apply(u, mu).l2_norm()

    Moreover, a `reconstructor` is provided such that ::

        reconstructor.reconstruct(projected_riesz_residual.apply(u, mu))
            == riesz_residual.apply(RB.lincomb(u), mu)

    Parameters
    ----------
    operator
        See definition of `riesz_residual`.
    functional
        See definition of `riesz_residual`. If `None`, zero right-hand side is assumed.
    RB
        |VectorArray| containing a basis of the reduced space onto which to project.
    product
        Scalar product |Operator| w.r.t. which to compute the Riesz representatives.
    extends
        Set by :meth:`~pymor.algorithms.greedy.greedy` to the result of the
        last reduction in case the basis extension was `hierarchic`. Used to prevent
        re-computation of `residual_range` basis vectors already obtained from previous
        reductions.

    Returns
    -------
    projected_riesz_residual
        See above.
    reconstructor
        See above.
    reduction_data
        Additional data produced by the reduction process. (Compare the `extends` parameter.)
    """
    assert functional is None \
        or functional.range == NumpyVectorSpace(1) and functional.source == operator.source and functional.linear
    assert RB is None or RB in operator.source
    assert product is None or product.source == product.range == operator.range
    assert extends is None or len(extends) == 3

    logger = getLogger('pymor.reductors.reduce_residual')

    if RB is None:
        RB = operator.source.empty()

    if extends and isinstance(extends[0], NonProjectedResidualOperator):
        extends = None
    if extends:
        residual_range = extends[1].RB.copy()
        residual_range_dims = list(extends[2]['residual_range_dims'])
        ind_range = range(extends[0].source.dim, len(RB))
    else:
        residual_range = operator.range.empty()
        residual_range_dims = []
        ind_range = range(-1, len(RB))

    class CollectionError(Exception):
        def __init__(self, op):
            super(CollectionError, self).__init__()
            self.op = op

    def collect_operator_ranges(op, source, ind, residual_range):
        if isinstance(op, (LincombOperator, SelectionOperator)):
            for o in op.operators:
                collect_operator_ranges(o, source, ind, residual_range)
        elif isinstance(op, EmpiricalInterpolatedOperator):
            if hasattr(op, 'collateral_basis') and ind == -1:
                residual_range.append(op.collateral_basis)
        elif isinstance(op, Concatenation):
            firstrange = op.first.range.empty()
            collect_operator_ranges(op.first, source, ind, firstrange)
            for j in range(len(firstrange)):
                collect_operator_ranges(op.second, firstrange, j, residual_range)
        elif op.linear and not op.parametric:
            if ind >= 0:
                residual_range.append(op.apply(source, ind=ind))
        else:
            raise CollectionError(op)

    def collect_functional_ranges(op, residual_range):
        if isinstance(op, (LincombOperator, SelectionOperator)):
            for o in op.operators:
                collect_functional_ranges(o, residual_range)
        elif isinstance(op, AdjointOperator):
            operator = Concatenation(op.range_product, op.operator) if op.range_product else op.operator
            collect_operator_ranges(operator, NumpyVectorArray(np.ones(1)), 0, residual_range)
        elif op.linear and not op.parametric:
            residual_range.append(op.as_vector())
        else:
            raise CollectionError(op)

    for i in ind_range:
        logger.info('Computing residual range for basis vector {}...'.format(i))
        new_residual_range = operator.range.empty()
        try:
            if i == -1:
                collect_functional_ranges(functional, new_residual_range)
            collect_operator_ranges(operator, RB, i, new_residual_range)
        except CollectionError as e:
            logger.warn('Cannot compute range of {}. Evaluation will be slow.'.format(e.op))
            operator = operator.projected(None, RB)
            return (NonProjectedResidualOperator(operator, functional, product),
                    NonProjectedReconstructor(product),
                    {})

        if product:
            logger.info('Computing Riesz representatives for basis vector {}...'.format(i))
            new_residual_range = product.apply_inverse(new_residual_range)

        gram_schmidt_offset = len(residual_range)
        residual_range.append(new_residual_range)
        logger.info('Orthonormalizing ...')
        gram_schmidt(residual_range, offset=gram_schmidt_offset, product=product, copy=False)
        residual_range_dims.append(len(residual_range))

    logger.info('Projecting ...')
    operator = operator.projected(residual_range, RB, product=None)  # the product always cancels out.
    functional = functional.projected(None, residual_range, product=None)

    return (ResidualOperator(operator, functional),
            GenericRBReconstructor(residual_range),
            {'residual_range_dims': residual_range_dims})
Пример #58
0
def pod(A, modes=None, product=None, tol=4e-8, symmetrize=False, orthonormalize=True,
        check=True, check_tol=1e-10):
    """Proper orthogonal decomposition of `A`.

    If the |VectorArray| `A` is viewed as a linear map ::

        A: R^(len(A)) ---> R^(dim(A))

    then the return value of this method is simply the |VectorArray| of left-singular
    vectors of the singular value decomposition of `A` with the scalar product
    on R^(dim(A) given by `product` and the scalar product on R^(len(A)) being
    the Euclidean product.

    Parameters
    ----------
    A
        The |VectorArray| for which the POD is to be computed.
    modes
        If not `None` only the first `modes` POD modes (singular vectors) are
        returned.
    products
        Scalar product |Operator| w.r.t. which the POD is computed.
    tol
        Singular values smaller than this value multiplied by the largest singular
        value are ignored.
    symmetrize
        If `True`, symmetrize the gramian again before proceeding.
    orthonormalize
        If `True`, orthonormalize the computed POD modes again using
        :func:`algorithms.gram_schmidt.gram_schmidt`.
    check
        If `True`, check the computed POD modes for orthonormality.
    check_tol
        Tolerance for the orthonormality check.

    Returns
    -------
    POD
        |VectorArray| of POD modes.
    SVALS
        Sequence of singular values.
    """

    assert isinstance(A, VectorArrayInterface)
    assert len(A) > 0
    assert modes is None or modes <= len(A)
    assert product is None or isinstance(product, OperatorInterface)

    B = A.gramian() if product is None else product.apply2(A, A)

    if symmetrize:     # according to rbmatlab this is necessary due to rounding
        B = B + B.T
        B *= 0.5

    eigvals = None if modes is None else (len(B) - modes, len(B) - 1)

    EVALS, EVECS = eigh(B, overwrite_a=True, turbo=True, eigvals=eigvals)
    EVALS = EVALS[::-1]
    EVECS = EVECS.T[::-1, :]  # is this a view? yes it is!

    above_tol = np.where(EVALS >= tol ** 2 * EVALS[0])[0]
    if len(above_tol) == 0:
        return type(A).empty(A.dim)
    last_above_tol = above_tol[-1]

    SVALS = np.sqrt(EVALS[:last_above_tol + 1])
    EVECS = EVECS[:last_above_tol + 1]

    POD = A.lincomb(EVECS / SVALS[:, np.newaxis])

    if orthonormalize:
        POD = gram_schmidt(POD, product=product, copy=False)

    if check:
        if not product and not float_cmp_all(POD.dot(POD), np.eye(len(POD)),
                                             atol=check_tol, rtol=0.):
            err = np.max(np.abs(POD.dot(POD) - np.eye(len(POD))))
            raise AccuracyError('result not orthogonal (max err={})'.format(err))
        elif product and not float_cmp_all(product.apply2(POD, POD), np.eye(len(POD)),
                                           atol=check_tol, rtol=0.):
            err = np.max(np.abs(product.apply2(POD, POD) - np.eye(len(POD))))
            raise AccuracyError('result not orthogonal (max err={})'.format(err))
        if len(POD) < len(EVECS):
            raise AccuracyError('additional orthonormalization removed basis vectors')

    return POD, SVALS
Пример #59
0
    def reduce(self, sigma, b, c, projection='orth'):
        """Bitangential Hermite interpolation.

        Parameters
        ----------
        sigma
            Interpolation points (closed under conjugation), list of
            length `r`.
        b
            Right tangential directions, |VectorArray| of length `r`
            from `self._B_source`.
        c
            Left tangential directions, |VectorArray| of length `r` from
            `self._C_range`.
        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

        Returns
        -------
        rd
            Reduced discretization.
        """
        r = len(sigma)
        assert b in self._B_source and len(b) == r
        assert c in self._C_range and len(c) == r
        assert projection in ('orth', 'biorth')

        # rescale tangential directions (to avoid overflow or underflow)
        if b.dim > 1:
            b.scal(1 / b.l2_norm())
        else:
            b = self._B_source.from_numpy(np.ones((r, 1)))
        if c.dim > 1:
            c.scal(1 / c.l2_norm())
        else:
            c = self._C_range.from_numpy(np.ones((r, 1)))

        # compute projection matrices
        self.V = self._K_source.empty(reserve=r)
        self.W = self._K_source.empty(reserve=r)
        for i in range(r):
            if sigma[i].imag == 0:
                Bb = self._B_apply(sigma[i].real, b.real[i])
                self.V.append(self._K_apply_inverse(sigma[i].real, Bb))

                CTc = self._C_apply_adjoint(sigma[i].real, c.real[i])
                self.W.append(self._K_apply_inverse_adjoint(sigma[i].real, CTc))
            elif sigma[i].imag > 0:
                Bb = self._B_apply(sigma[i], b[i])
                v = self._K_apply_inverse(sigma[i], Bb)
                self.V.append(v.real)
                self.V.append(v.imag)

                CTc = self._C_apply_adjoint(sigma[i], c[i].conj())
                w = self._K_apply_inverse_adjoint(sigma[i], CTc)
                self.W.append(w.real)
                self.W.append(w.imag)

        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=self._product)

        self.pg_reductor = GenericPGReductor(self.d, self.W, self.V, projection == 'biorth', product=self._product)

        rd = self.pg_reductor.reduce()
        return rd