Ejemplo n.º 1
0
    def _check_orthonormality(self, basis, offset=0):
        if not self.check_orthonormality or basis not in self.products:
            return

        U = self.bases[basis]
        product = self.products.get(basis, None)
        error_matrix = U[offset:].inner(U, product)
        error_matrix[:len(U) - offset, offset:] -= np.eye(len(U) - offset)
        if error_matrix.size > 0:
            err = np.max(np.abs(error_matrix))
            if err >= self.check_tol:
                raise AccuracyError(f"result not orthogonal (max err={err})")
Ejemplo n.º 2
0
def pod(A,
        modes=None,
        product=None,
        rtol=4e-8,
        atol=0.,
        l2_err=0.,
        symmetrize=False,
        orthonormalize=True,
        check=True,
        check_tol=1e-10):
    """Proper orthogonal decomposition of `A`.

    Viewing the |VectorArray| `A` as a `A.dim` x `len(A)` matrix,
    the return value of this method is the |VectorArray| of left-singular
    vectors of the singular value decomposition of `A`, where the inner product
    on R^(`dim(A)`) is given by `product` and the inner product on R^(`len(A)`)
    is the Euclidean inner 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.
    product
        Inner product |Operator| w.r.t. which the POD is computed.
    rtol
        Singular values smaller than this value multiplied by the largest singular
        value are ignored.
    atol
        Singular values smaller than this value are ignored.
    l2_err
        Do not return more modes than needed to bound the l2-approximation
        error by this value. I.e. the number of returned modes is at most ::

            argmin_N { sum_{n=N+1}^{infty} s_n^2 <= l2_err^2 }

        where `s_n` denotes the n-th singular value.
    symmetrize
        If `True`, symmetrize the Gramian again before proceeding.
    orthonormalize
        If `True`, orthonormalize the computed POD modes again using
        the :func:`~pymor.algorithms.gram_schmidt.gram_schmidt` algorithm.
    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)

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

    with logger.block('Computing Gramian ({} vectors) ...'.format(len(A))):
        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

    with logger.block('Computing eigenvalue decomposition ...'):
        eigvals = None if (modes is None or l2_err > 0.) 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!

        tol = max(rtol**2 * EVALS[0], atol**2)
        above_tol = np.where(EVALS >= tol)[0]
        if len(above_tol) == 0:
            return A.space.empty(), np.array([])
        last_above_tol = above_tol[-1]

        errs = np.concatenate((np.cumsum(EVALS[::-1])[::-1], [0.]))
        below_err = np.where(errs <= l2_err**2)[0]
        first_below_err = below_err[0]

        selected_modes = min(first_below_err, last_above_tol + 1)
        if modes is not None:
            selected_modes = min(selected_modes, modes)

        SVALS = np.sqrt(EVALS[:selected_modes])
        EVECS = EVECS[:selected_modes]

    with logger.block(
            'Computing left-singular vectors ({} vectors) ...'.format(
                len(EVECS))):
        POD = A.lincomb(EVECS / SVALS[:, np.newaxis])

    if orthonormalize:
        with logger.block('Re-orthonormalizing POD modes ...'):
            POD = gram_schmidt(POD, product=product, copy=False)

    if check:
        logger.info('Checking orthonormality ...')
        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
Ejemplo n.º 3
0
def gram_schmidt_biorth(V,
                        W,
                        product=None,
                        reiterate=True,
                        reiteration_threshold=1e-1,
                        check=True,
                        check_tol=1e-3,
                        copy=True):
    """Biorthonormalize a pair of |VectorArrays| using the biorthonormal Gram-Schmidt process.

    See Algorithm 1 in [BKS11]_.

    Parameters
    ----------
    V, W
        The |VectorArrays| which are to be biorthonormalized.
    product
        The inner product |Operator| w.r.t. which to biorthonormalize.
        If `None`, the Euclidean product is used.
    reiterate
        If `True`, orthonormalize again if the norm of the orthogonalized vector is
        much smaller than the norm of the original vector.
    reiteration_threshold
        If `reiterate` is `True`, re-orthonormalize if the ratio between the norms of
        the orthogonalized vector and the original vector is smaller than this value.
    check
        If `True`, check if the resulting |VectorArray| is really orthonormal.
    check_tol
        Tolerance for the check.
    copy
        If `True`, create a copy of `V` and `W` instead of modifying `V` and `W` in-place.


    Returns
    -------
    The biorthonormalized |VectorArrays|.
    """
    assert V.space == W.space
    assert len(V) == len(W)

    logger = getLogger('pymor.algorithms.gram_schmidt.gram_schmidt_biorth')

    if copy:
        V = V.copy()
        W = W.copy()

    # main loop
    for i in range(len(V)):
        # calculate norm of V[i]
        initial_norm = V[i].norm(product)[0]

        # project V[i]
        if i == 0:
            V[0].scal(1 / initial_norm)
        else:
            norm = initial_norm
            # If reiterate is True, reiterate as long as the norm of the vector changes
            # strongly during projection.
            while True:
                for j in range(i):
                    # project by (I - V[j] * W[j]^T * E)
                    p = W[j].pairwise_inner(V[i], product)[0]
                    V[i].axpy(-p, V[j])

                # calculate new norm
                old_norm, norm = norm, V[i].norm(product)[0]

                # check if reorthogonalization should be done
                if reiterate and norm < reiteration_threshold * old_norm:
                    logger.info(f"Projecting vector V[{i}] again")
                else:
                    V[i].scal(1 / norm)
                    break

        # calculate norm of W[i]
        initial_norm = W[i].norm(product)[0]

        # project W[i]
        if i == 0:
            W[0].scal(1 / initial_norm)
        else:
            norm = initial_norm
            # If reiterate is True, reiterate as long as the norm of the vector changes
            # strongly during projection.
            while True:
                for j in range(i):
                    # project by (I - W[j] * V[j]^T * E)
                    p = V[j].pairwise_inner(W[i], product)[0]
                    W[i].axpy(-p, W[j])

                # calculate new norm
                old_norm, norm = norm, W[i].norm(product)[0]

                # check if reorthogonalization should be done
                if reiterate and norm < reiteration_threshold * old_norm:
                    logger.info(f"Projecting vector W[{i}] again")
                else:
                    W[i].scal(1 / norm)
                    break

        # rescale V[i]
        p = W[i].pairwise_inner(V[i], product)[0]
        V[i].scal(1 / p)

    if check:
        error_matrix = W.inner(V, product)
        error_matrix -= np.eye(len(V))
        if error_matrix.size > 0:
            err = np.max(np.abs(error_matrix))
            if err >= check_tol:
                raise AccuracyError(f"result not biorthogonal (max err={err})")

    return V, W
Ejemplo n.º 4
0
def gram_schmidt(A,
                 product=None,
                 return_R=False,
                 atol=1e-13,
                 rtol=1e-13,
                 offset=0,
                 reiterate=True,
                 reiteration_threshold=9e-1,
                 check=True,
                 check_tol=1e-3,
                 copy=True):
    """Orthonormalize a |VectorArray| using the modified Gram-Schmidt algorithm.

    Parameters
    ----------
    A
        The |VectorArray| which is to be orthonormalized.
    product
        The inner product |Operator| w.r.t. which to orthonormalize.
        If `None`, the Euclidean product is used.
    return_R
        If `True`, the R matrix from QR decomposition is returned.
    atol
        Vectors of norm smaller than `atol` are removed from the array.
    rtol
        Relative tolerance used to detect linear dependent vectors
        (which are then removed from the array).
    offset
        Assume that the first `offset` vectors are already orthonormal and start the
        algorithm at the `offset + 1`-th vector.
    reiterate
        If `True`, orthonormalize again if the norm of the orthogonalized vector is
        much smaller than the norm of the original vector.
    reiteration_threshold
        If `reiterate` is `True`, re-orthonormalize if the ratio between the norms of
        the orthogonalized vector and the original vector is smaller than this value.
    check
        If `True`, check if the resulting |VectorArray| is really orthonormal.
    check_tol
        Tolerance for the check.
    copy
        If `True`, create a copy of `A` instead of modifying `A` in-place.

    Returns
    -------
    Q
        The orthonormalized |VectorArray|.
    R
        The upper-triangular/trapezoidal matrix (if `compute_R` is `True`).
    """

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

    if copy:
        A = A.copy()

    # main loop
    R = np.eye(len(A))
    remove = []  # indices of to be removed vectors
    for i in range(offset, len(A)):
        # first calculate norm
        initial_norm = A[i].norm(product)[0]

        if initial_norm < atol:
            logger.info(f"Removing vector {i} of norm {initial_norm}")
            remove.append(i)
            continue

        if i == 0:
            A[0].scal(1 / initial_norm)
            R[i, i] = initial_norm
        else:
            norm = initial_norm
            # If reiterate is True, reiterate as long as the norm of the vector changes
            # strongly during orthogonalization (due to Andreas Buhr).
            while True:
                # orthogonalize to all vectors left
                for j in range(i):
                    if j in remove:
                        continue
                    p = A[j].pairwise_inner(A[i], product)[0]
                    A[i].axpy(-p, A[j])
                    common_dtype = np.promote_types(R.dtype, type(p))
                    R = R.astype(common_dtype, copy=False)
                    R[j, i] += p

                # calculate new norm
                old_norm, norm = norm, A[i].norm(product)[0]

                # remove vector if it got too small
                if norm < rtol * initial_norm:
                    logger.info(f"Removing linearly dependent vector {i}")
                    remove.append(i)
                    break

                # check if reorthogonalization should be done
                if reiterate and norm < reiteration_threshold * old_norm:
                    logger.info(f"Orthonormalizing vector {i} again")
                else:
                    A[i].scal(1 / norm)
                    R[i, i] = norm
                    break

    if remove:
        del A[remove]
        R = np.delete(R, remove, axis=0)

    if check:
        error_matrix = A[offset:len(A)].inner(A, product)
        error_matrix[:len(A) - offset,
                     offset:len(A)] -= np.eye(len(A) - offset)
        if error_matrix.size > 0:
            err = np.max(np.abs(error_matrix))
            if err >= check_tol:
                raise AccuracyError(f"result not orthogonal (max err={err})")

    if return_R:
        return A, R
    else:
        return A
Ejemplo n.º 5
0
def gram_schmidt(A,
                 product=None,
                 atol=1e-13,
                 rtol=1e-13,
                 offset=0,
                 find_duplicates=True,
                 reiterate=True,
                 reiteration_threshold=1e-1,
                 check=True,
                 check_tol=1e-3,
                 copy=True):
    """Orthonormalize a |VectorArray| using the stabilized Gram-Schmidt algorithm.

    Parameters
    ----------
    A
        The |VectorArray| which is to be orthonormalized.
    product
        The scalar product w.r.t. which to orthonormalize, given as a linear
        |Operator|. If `None` the Euclidean product is used.
    atol
        Vectors of norm smaller than `atol` are removed from the array.
    rtol
        Relative tolerance used to detect linear dependent vectors
        (which are then removed from the array).
    offset
        Assume that the first `offset` vectors are already orthogonal and start the
        algorithm at the `offset + 1`-th vector.
    reiterate
        If `True`, orthonormalize again if the norm of the orthogonalized vector is
        much smaller than the norm of the original vector.
    reiteration_threshold
        If `reiterate` is `True`, re-orthonormalize if the ratio between the norms of
        the orthogonalized vector and the original vector is smaller than this value.
    check
        If `True`, check if the resulting VectorArray is really orthonormal.
    check_tol
        Tolerance for the check.
    copy
        If `True`, create a copy of `A` instead of modifying `A` itself.


    Returns
    -------
    The orthonormalized |VectorArray|.
    """

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

    if copy:
        A = A.copy()

    # main loop
    remove = []
    for i in range(offset, len(A)):
        # first calculate norm
        if product is None:
            initial_norm = A.l2_norm(ind=i)[0]
        else:
            initial_norm = np.sqrt(
                product.pairwise_apply2(A, A, V_ind=i, U_ind=i))[0]

        if initial_norm < atol:
            logger.info("Removing vector {} of norm {}".format(
                i, initial_norm))
            remove.append(i)
            continue

        if i == 0:
            A.scal(1 / initial_norm, ind=0)

        else:
            first_iteration = True
            norm = initial_norm
            # If reiterate is True, reiterate as long as the norm of the vector changes
            # strongly during orthonormalization (due to Andreas Buhr).
            while first_iteration or reiterate and norm / old_norm < reiteration_threshold:

                if first_iteration:
                    first_iteration = False
                else:
                    logger.info('Orthonormalizing vector {} again'.format(i))

                # orthogonalize to all vectors left
                for j in range(i):
                    if j in remove:
                        continue
                    if product is None:
                        p = A.pairwise_dot(A, ind=i, o_ind=j)[0]
                    else:
                        p = product.pairwise_apply2(A, A, V_ind=i, U_ind=j)[0]
                    A.axpy(-p, A, ind=i, x_ind=j)

                # calculate new norm
                if product is None:
                    old_norm, norm = norm, A.l2_norm(ind=i)[0]
                else:
                    old_norm, norm = norm, np.sqrt(
                        product.pairwise_apply2(A, A, V_ind=i, U_ind=i))[0]

                # remove vector if it got too small:
                if norm / initial_norm < rtol:
                    logger.info(
                        "Removing linear dependent vector {}".format(i))
                    remove.append(i)
                    break

            if norm > 0:
                A.scal(1 / norm, ind=i)

    if remove:
        A.remove(remove)

    if check:
        if product:
            error_matrix = product.apply2(A,
                                          A,
                                          V_ind=list(range(offset, len(A))))
        else:
            error_matrix = A.dot(A, ind=list(range(offset, len(A))))
        error_matrix[:len(A) - offset,
                     offset:len(A)] -= np.eye(len(A) - offset)
        if error_matrix.size > 0:
            err = np.max(np.abs(error_matrix))
            if err >= check_tol:
                raise AccuracyError(
                    'result not orthogonal (max err={})'.format(err))

    return A
Ejemplo n.º 6
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:`la.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, pairwise=False)

    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, pairwise=False), np.eye(len(POD)),
                                             atol=check_tol, rtol=0.):
            err = np.max(np.abs(POD.dot(POD, pairwise=False) - np.eye(len(POD))))
            raise AccuracyError('result not orthogonal (max err={})'.format(err))
        elif product and not float_cmp_all(product.apply2(POD, POD, pairwise=False), np.eye(len(POD)),
                                           atol=check_tol, rtol=0.):
            err = np.max(np.abs(product.apply2(POD, POD, pairwise=False) - 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
Ejemplo n.º 7
0
def gram_schmidt_biorth(V,
                        W,
                        product=None,
                        reiterate=True,
                        reiteration_threshold=1e-1,
                        check=True,
                        check_tol=1e-3,
                        copy=True):
    """Biorthonormalize a pair of |VectorArrays| using the biorthonormal Gram-Schmidt process.

    See Algorithm 1 in [BKS11]_.

    .. [BKS11]  P. Benner, M. Köhler, J. Saak,
                Sparse-Dense Sylvester Equations in :math:`\mathcal{H}_2`-Model Order Reduction,
                Max Planck Institute Magdeburg Preprint, available from http://www.mpi-magdeburg.mpg.de/preprints/,
                2011.

    Parameters
    ----------
    V, W
        The |VectorArrays| which are to be biorthonormalized.
    product
        The inner product |Operator| w.r.t. which to biorthonormalize.
        If `None`, the Euclidean product is used.
    reiterate
        If `True`, orthonormalize again if the norm of the orthogonalized vector is
        much smaller than the norm of the original vector.
    reiteration_threshold
        If `reiterate` is `True`, re-orthonormalize if the ratio between the norms of
        the orthogonalized vector and the original vector is smaller than this value.
    check
        If `True`, check if the resulting |VectorArray| is really orthonormal.
    check_tol
        Tolerance for the check.
    copy
        If `True`, create a copy of `V` and `W` instead of modifying `V` and `W` in-place.


    Returns
    -------
    The biorthonormalized |VectorArrays|.
    """
    assert V.space == W.space
    assert len(V) == len(W)

    logger = getLogger('pymor.algorithms.gram_schmidt.gram_schmidt_biorth')

    if copy:
        V = V.copy()
        W = W.copy()

    # main loop
    for i in range(len(V)):
        # calculate norm of V[i]
        if product is None:
            initial_norm = V[i].l2_norm()[0]
        else:
            initial_norm = np.sqrt(product.pairwise_apply2(V[i], V[i]))[0]

        # project V[i]
        if i == 0:
            V[0].scal(1 / initial_norm)
        else:
            first_iteration = True
            norm = initial_norm
            # If reiterate is True, reiterate as long as the norm of the vector changes
            # strongly during projection.
            while first_iteration or reiterate and norm / old_norm < reiteration_threshold:
                if first_iteration:
                    first_iteration = False
                else:
                    logger.info('Projecting vector V[{}] again'.format(i))

                for j in range(i):
                    # project by (I - V[j] * W[j]^T * E)
                    if product is None:
                        p = W[j].pairwise_dot(V[i])[0]
                    else:
                        p = product.pairwise_apply2(W[j], V[i])[0]
                    V[i].axpy(-p, V[j])

                # calculate new norm
                if product is None:
                    old_norm, norm = norm, V[i].l2_norm()[0]
                else:
                    old_norm, norm = norm, np.sqrt(
                        product.pairwise_apply2(V[i], V[i])[0])

            if norm > 0:
                V[i].scal(1 / norm)

        # calculate norm of W[i]
        if product is None:
            initial_norm = W[i].l2_norm()[0]
        else:
            initial_norm = np.sqrt(product.pairwise_apply2(W[i], W[i]))[0]

        # project W[i]
        if i == 0:
            W[0].scal(1 / initial_norm)
        else:
            first_iteration = True
            norm = initial_norm
            # If reiterate is True, reiterate as long as the norm of the vector changes
            # strongly during projection.
            while first_iteration or reiterate and norm / old_norm < reiteration_threshold:
                if first_iteration:
                    first_iteration = False
                else:
                    logger.info('Projecting vector W[{}] again'.format(i))

                for j in range(i):
                    # project by (I - W[j] * V[j]^T * E)
                    if product is None:
                        p = V[j].pairwise_dot(W[i])[0]
                    else:
                        p = product.pairwise_apply2(V[j], W[i])[0]
                    W[i].axpy(-p, W[j])

                # calculate new norm
                if product is None:
                    old_norm, norm = norm, W[i].l2_norm()[0]
                else:
                    old_norm, norm = norm, np.sqrt(
                        product.pairwise_apply2(W[i], W[i])[0])

            if norm > 0:
                W[i].scal(1 / norm)

        # rescale V[i]
        if product is None:
            p = W[i].pairwise_dot(V[i])[0]
        else:
            p = product.pairwise_apply2(W[i], V[i])[0]
        V[i].scal(1 / p)

    if check:
        if product:
            error_matrix = product.apply2(W, V)
        else:
            error_matrix = W.dot(V)
        error_matrix -= np.eye(len(V))
        if error_matrix.size > 0:
            err = np.max(np.abs(error_matrix))
            if err >= check_tol:
                raise AccuracyError(
                    'Result not biorthogonal (max err={})'.format(err))

    return V, W