Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

# This file is part of the pyMOR project (http://www.pymor.org). 

# Copyright Holders: Rene Milk, Stephan Rave, Felix Schindler 

# License: BSD 2-Clause License (http://opensource.org/licenses/BSD-2-Clause) 

 

from __future__ import absolute_import, division, print_function 

 

import numpy as np 

 

from pymor import defaults 

from pymor.core.exceptions import AccuracyError, ExtensionError 

from pymor.la import NumpyVectorArray 

from pymor.operators import NumpyMatrixOperator 

from pymor.tools import float_cmp_all 

 

 

def numpy_gram_schmidt_basis_extension(basis, U, product=None): 

    '''Extend basis using Gram-Schmidt orthonormalization. 

 

    Parameters 

    ---------- 

    basis 

        The basis to extend. 

    U 

        The new basis vector. 

    product 

        The scalar product w.r.t. which to orthonormalize; if None, the l2-scalar 

        product on the coefficient vector is used. 

 

    Returns 

    ------- 

    The new basis. 

 

    Raises 

    ------ 

    ExtensionError 

        Gram-Schmidt orthonormalization fails. Usually this is the case when U 

        is not linearily independent from the basis. However this can also happen 

        due to rounding errors ... 

    ''' 

    if basis is None: 

        basis = NumpyVectorArray(np.zeros((0, U.dim))) 

    assert isinstance(basis, NumpyVectorArray) 

    assert isinstance(U, NumpyVectorArray) 

    if product is not None: 

        assert isinstance(product, NumpyMatrixOperator) 

        product = product._matrix 

 

    new_basis = np.empty((len(basis) + 1, basis.dim)) 

    new_basis[:-1, :] = basis.data 

    new_basis[-1, :] = U.data 

    new_basis = numpy_gram_schmidt(new_basis, row_offset=len(basis), product=product) 

 

    if len(new_basis) <= len(basis): 

        raise ExtensionError 

 

    return NumpyVectorArray(new_basis) 

 

 

def numpy_gram_schmidt(A, product=None, tol=None, row_offset=0, find_row_duplicates=True, find_col_duplicates=False, 

                       check=None, check_tol=None): 

    '''Orthonormnalize a matrix using the Gram-Schmidt algorithm. 

 

    Parameters 

    ---------- 

    A 

        The matrix which is to be orthonormalized. 

    product 

        The scalar product w.r.t. which to orthonormalize. Either a `DiscreteLinearOperator` 

        or a square matrix. 

    tol 

        Tolerance to determine a linear dependent row. 

    row_offset 

        Assume that the first `row_offset` rows are already orthogonal and start the 

        algorithm at the `row_offset + 1`-th row. 

    find_row_duplicates 

        If `True`, eliminate duplicate rows before the main loop. 

    find_col_duplicates 

        If `True`, eliminate duplicate columns before the main loop. 

    check 

        If `True`, check if the resulting matrix is really orthonormal. If `None`, use 

        `defaults.gram_schmidt_check`. 

    check_tol 

        Tolerance for the check. If `None`, `defaults.gram_schmidt_check_tol` is used. 

 

 

    Returns 

    ------- 

    The orthonormalized matrix. 

    ''' 

 

    A = A.copy() 

    tol = defaults.gram_schmidt_tol if tol is None else tol 

    check = defaults.gram_schmidt_tol if check is None else check 

    check_tol = check_tol or defaults.gram_schmidt_check_tol 

 

    # find duplicate rows since in some circumstances these cannot be detected in the main loop 

    # (is this really needed or is in this cases the tolerance poorly chosen anyhow) 

    if find_row_duplicates: 

        for i in xrange(A.shape[0]): 

            for j in xrange(max(row_offset, i + 1), A.shape[0]): 

                if float_cmp_all(A[i], A[j]): 

                    A[j] = 0 

 

    # find duplicate columns 

    if find_col_duplicates: 

        for i in xrange(A.shape[1]): 

            for j in xrange(i, A.shape[1]): 

                if float_cmp_all(A[:, i], A[:, j]): 

                    A[:, j] = 0 

 

    # main loop 

    for i in xrange(A.shape[0]): 

 

        if i >= row_offset: 

            if product is None: 

                norm = np.sqrt(np.sum(A[i] ** 2)) 

            else: 

                norm = np.sqrt(product.apply2(A[i], A[i])) 

 

            if norm < tol: 

                A[i] = 0 

            else: 

                A[i] = A[i] / norm 

 

        j = max(row_offset, i + 1) 

        if product is None: 

            p = np.sum(A[j:] * A[i], axis=-1) 

        else: 

            p = product.apply2(A[j:], A[i], pairwise=False) 

 

        A[j:] -= p[..., np.newaxis] * A[i] 

 

    rows = np.logical_not(np.all(A == 0, axis=-1)) 

    A = A[rows] 

 

    if check: 

        if not product and not float_cmp_all(A.dot(A.T), np.eye(A.shape[0]), check_tol): 

            err = np.max(np.abs(A.dot(A.T) - np.eye(A.shape[0]))) 

            raise AccuracyError('result not orthogonal (max err={})'.format(err)) 

        elif product and not float_cmp_all(product.apply2(A, A, pairwise=False), np.eye(len(A)), check_tol): 

            err = np.max(np.abs(product.apply2(A, A, pairwise=False) - np.eye(len(A)))) 

            raise AccuracyError('result not orthogonal (max err={})'.format(err)) 

 

    return A