feectools 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- feectools/__init__.py +0 -0
- feectools/accelerate/__init__.py +0 -0
- feectools/accelerate/accelerate.py +220 -0
- feectools/accelerate/compile_psydac.mk +52 -0
- feectools/api/__init__.py +0 -0
- feectools/api/essential_bc.py +122 -0
- feectools/api/fem_bilinear_form.py +2226 -0
- feectools/api/fem_common.py +286 -0
- feectools/api/fem_sum_form.py +123 -0
- feectools/api/settings.py +82 -0
- feectools/core/__init__.py +11 -0
- feectools/core/bsplines.py +1107 -0
- feectools/core/bsplines_kernels.py +1349 -0
- feectools/core/field_evaluation_kernels.py +5015 -0
- feectools/core/tests/__init__.py +0 -0
- feectools/core/tests/test_bsplines.py +263 -0
- feectools/core/tests/test_bsplines_kernel.py +40 -0
- feectools/core/tests/test_bsplines_pyccel.py +752 -0
- feectools/ddm/__init__.py +3 -0
- feectools/ddm/basic.py +78 -0
- feectools/ddm/blocking_data_exchanger.py +348 -0
- feectools/ddm/cart.py +1835 -0
- feectools/ddm/interface_data_exchanger.py +122 -0
- feectools/ddm/mpi.py +109 -0
- feectools/ddm/nonblocking_data_exchanger.py +331 -0
- feectools/ddm/partition.py +207 -0
- feectools/ddm/petsc.py +112 -0
- feectools/ddm/tests/__init__.py +0 -0
- feectools/ddm/tests/test_cart_1d.py +138 -0
- feectools/ddm/tests/test_cart_2d.py +164 -0
- feectools/ddm/tests/test_cart_3d.py +158 -0
- feectools/ddm/tests/test_multicart_2d.py +173 -0
- feectools/ddm/tests/test_partition.py +124 -0
- feectools/ddm/utilities.py +24 -0
- feectools/feec/__init__.py +0 -0
- feectools/feec/derivatives.py +780 -0
- feectools/feec/dof_kernels.py +210 -0
- feectools/feec/global_geometric_projectors.py +1073 -0
- feectools/feec/hodge.py +148 -0
- feectools/fem/__init__.py +0 -0
- feectools/fem/basic.py +465 -0
- feectools/fem/grid.py +181 -0
- feectools/fem/partitioning.py +344 -0
- feectools/fem/projectors.py +160 -0
- feectools/fem/splines.py +559 -0
- feectools/fem/tensor.py +1393 -0
- feectools/fem/tests/__init__.py +0 -0
- feectools/fem/tests/analytical_profiles_1d.py +100 -0
- feectools/fem/tests/analytical_profiles_base.py +34 -0
- feectools/fem/tests/splines_error_bounds.py +155 -0
- feectools/fem/tests/test_spline_histopolation.py +120 -0
- feectools/fem/tests/test_spline_interpolation.py +182 -0
- feectools/fem/tests/test_splines.py +184 -0
- feectools/fem/tests/test_splines_par.py +46 -0
- feectools/fem/tests/test_vector_spaces.py +150 -0
- feectools/fem/tests/utilities.py +47 -0
- feectools/fem/vector.py +729 -0
- feectools/linalg/__init__.py +0 -0
- feectools/linalg/basic.py +1386 -0
- feectools/linalg/block.py +1451 -0
- feectools/linalg/direct_solvers.py +201 -0
- feectools/linalg/fft.py +258 -0
- feectools/linalg/kernels/__init__.py +0 -0
- feectools/linalg/kernels/axpy_kernels.py +57 -0
- feectools/linalg/kernels/inner_kernels.py +100 -0
- feectools/linalg/kernels/matvec_kernels.py +206 -0
- feectools/linalg/kernels/stencil2IJV_kernels.py +227 -0
- feectools/linalg/kernels/stencil2coo_kernels.py +179 -0
- feectools/linalg/kernels/transpose_kernels.py +263 -0
- feectools/linalg/kron.py +911 -0
- feectools/linalg/solvers.py +1914 -0
- feectools/linalg/sparse.py +114 -0
- feectools/linalg/stencil.py +2923 -0
- feectools/linalg/stencil_dot_kernels.py +317 -0
- feectools/linalg/stencil_transpose_kernels.py +372 -0
- feectools/linalg/tests/__init__.py +0 -0
- feectools/linalg/tests/test_block.py +1588 -0
- feectools/linalg/tests/test_fft.py +106 -0
- feectools/linalg/tests/test_kron_stencil_matrix.py +114 -0
- feectools/linalg/tests/test_linalg.py +1065 -0
- feectools/linalg/tests/test_matrix_free.py +128 -0
- feectools/linalg/tests/test_solvers.py +213 -0
- feectools/linalg/tests/test_stencil_interface_matrix.py +379 -0
- feectools/linalg/tests/test_stencil_vector.py +1036 -0
- feectools/linalg/tests/test_stencil_vector_space.py +440 -0
- feectools/linalg/topetsc.py +522 -0
- feectools/linalg/utilities.py +200 -0
- feectools/utilities/__init__.py +0 -0
- feectools/utilities/quadratures.py +113 -0
- feectools/utilities/utils.py +166 -0
- feectools/version.py +1 -0
- feectools-0.1.0.dist-info/METADATA +66 -0
- feectools-0.1.0.dist-info/RECORD +98 -0
- feectools-0.1.0.dist-info/WHEEL +5 -0
- feectools-0.1.0.dist-info/entry_points.txt +3 -0
- feectools-0.1.0.dist-info/licenses/AUTHORS +22 -0
- feectools-0.1.0.dist-info/licenses/LICENSE +21 -0
- feectools-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1065 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
from feectools.linalg.block import BlockLinearOperator, BlockVector, BlockVectorSpace
|
|
5
|
+
from feectools.linalg.basic import LinearOperator, ZeroOperator, IdentityOperator, ComposedLinearOperator, SumLinearOperator, PowerLinearOperator, ScaledLinearOperator
|
|
6
|
+
from feectools.linalg.stencil import StencilVectorSpace, StencilVector, StencilMatrix
|
|
7
|
+
from feectools.linalg.solvers import ConjugateGradient, inverse
|
|
8
|
+
from feectools.ddm.cart import DomainDecomposition, CartDecomposition
|
|
9
|
+
|
|
10
|
+
#===============================================================================
|
|
11
|
+
|
|
12
|
+
n1array = [2, 7]
|
|
13
|
+
n2array = [2, 3]
|
|
14
|
+
p1array = [1, 3]
|
|
15
|
+
p2array = [1, 3]
|
|
16
|
+
|
|
17
|
+
def array_equal(a, b):
|
|
18
|
+
return np.array_equal(a.toarray(), b.toarray())
|
|
19
|
+
|
|
20
|
+
def sparse_equal(a, b):
|
|
21
|
+
return (a.tosparse() != b.tosparse()).nnz == 0
|
|
22
|
+
|
|
23
|
+
def assert_pos_def(A):
|
|
24
|
+
assert isinstance(A, LinearOperator)
|
|
25
|
+
A_array = A.toarray()
|
|
26
|
+
assert np.all(np.linalg.eigvals(A_array) > 0)
|
|
27
|
+
|
|
28
|
+
def compute_global_starts_ends(domain_decomposition, npts):
|
|
29
|
+
ndims = len(npts)
|
|
30
|
+
global_starts = [None]*ndims
|
|
31
|
+
global_ends = [None]*ndims
|
|
32
|
+
|
|
33
|
+
for axis in range(ndims):
|
|
34
|
+
es = domain_decomposition.global_element_starts[axis]
|
|
35
|
+
ee = domain_decomposition.global_element_ends [axis]
|
|
36
|
+
|
|
37
|
+
global_ends [axis] = ee.copy()
|
|
38
|
+
global_ends [axis][-1] = npts[axis]-1
|
|
39
|
+
global_starts[axis] = np.array([0] + (global_ends[axis][:-1]+1).tolist())
|
|
40
|
+
|
|
41
|
+
return global_starts, global_ends
|
|
42
|
+
|
|
43
|
+
def get_StencilVectorSpace(npts, pads, periods):
|
|
44
|
+
assert len(npts) == len(pads) == len(periods)
|
|
45
|
+
shifts = [1] * len(npts)
|
|
46
|
+
D = DomainDecomposition(npts, periods=periods)
|
|
47
|
+
global_starts, global_ends = compute_global_starts_ends(D, npts)
|
|
48
|
+
C = CartDecomposition(D, npts, global_starts, global_ends, pads=pads, shifts=shifts)
|
|
49
|
+
V = StencilVectorSpace(C)
|
|
50
|
+
return V
|
|
51
|
+
|
|
52
|
+
def get_positive_definite_StencilMatrix(V):
|
|
53
|
+
|
|
54
|
+
np.random.seed(2)
|
|
55
|
+
assert isinstance(V, StencilVectorSpace)
|
|
56
|
+
[n1, n2] = V._npts
|
|
57
|
+
[p1, p2] = V._pads
|
|
58
|
+
[P1, P2] = V._periods
|
|
59
|
+
assert (P1 == False) and (P2 == False)
|
|
60
|
+
|
|
61
|
+
S = StencilMatrix(V, V)
|
|
62
|
+
|
|
63
|
+
for i in range(0, p1+1):
|
|
64
|
+
if i != 0:
|
|
65
|
+
for j in range(-p2, p2+1):
|
|
66
|
+
S[:, :, i, j] = 2*np.random.random()-1
|
|
67
|
+
else:
|
|
68
|
+
for j in range(1, p2+1):
|
|
69
|
+
S[:, :, i, j] = 2*np.random.random()-1
|
|
70
|
+
S += S.T
|
|
71
|
+
S[:, :, 0, 0] = ((n1 * n2) - 1) / np.random.random()
|
|
72
|
+
S /= S[0, 0, 0, 0]
|
|
73
|
+
S.remove_spurious_entries()
|
|
74
|
+
|
|
75
|
+
return S
|
|
76
|
+
|
|
77
|
+
#===============================================================================
|
|
78
|
+
# SERIAL TESTS
|
|
79
|
+
#===============================================================================
|
|
80
|
+
@pytest.mark.parametrize('n1', n1array)
|
|
81
|
+
@pytest.mark.parametrize('n2', n2array)
|
|
82
|
+
@pytest.mark.parametrize('p1', p1array)
|
|
83
|
+
@pytest.mark.parametrize('p2', p2array)
|
|
84
|
+
|
|
85
|
+
def test_square_stencil_basic(n1, n2, p1, p2, P1=False, P2=False):
|
|
86
|
+
|
|
87
|
+
# 1. Initiate square LOs S,S1 (StencilMatrix), I (IdentityOperator), Z (ZeroOperator) and a Stencilvector v
|
|
88
|
+
# 2. Test general basic operations
|
|
89
|
+
# 3. Test special cases
|
|
90
|
+
|
|
91
|
+
###
|
|
92
|
+
### 1. Initiation
|
|
93
|
+
###
|
|
94
|
+
|
|
95
|
+
# Initiate StencilVectorSpace
|
|
96
|
+
V = get_StencilVectorSpace([n1, n2], [p1, p2], [P1, P2])
|
|
97
|
+
|
|
98
|
+
# Initiate Linear Operators
|
|
99
|
+
Z = ZeroOperator(V, V)
|
|
100
|
+
I = IdentityOperator(V, V)
|
|
101
|
+
S = StencilMatrix(V, V)
|
|
102
|
+
S1 = StencilMatrix(V, V)
|
|
103
|
+
# a non-symmetric StencilMatrix for transpose testing
|
|
104
|
+
S2 = StencilMatrix(V, V)
|
|
105
|
+
|
|
106
|
+
# Initiate a StencilVector
|
|
107
|
+
v = StencilVector(V)
|
|
108
|
+
for i in range(n1):
|
|
109
|
+
for j in range(n2):
|
|
110
|
+
v[i,j] = 1
|
|
111
|
+
|
|
112
|
+
nonzero_values = dict()
|
|
113
|
+
for k1 in range(-p1,p1+1):
|
|
114
|
+
for k2 in range(-p2,p2+1):
|
|
115
|
+
nonzero_values[k1,k2] = 1 + k1*n2 + k2
|
|
116
|
+
for k1 in range(-p1,p1+1):
|
|
117
|
+
for k2 in range(-p2,p2+1):
|
|
118
|
+
if k1==0:
|
|
119
|
+
if k2<0:
|
|
120
|
+
nonzero_values[k1,k2] = nonzero_values[-k1,-k2]
|
|
121
|
+
elif k1<0:
|
|
122
|
+
nonzero_values[k1,k2] = nonzero_values[-k1,-k2]
|
|
123
|
+
|
|
124
|
+
for k1 in range(-p1,p1+1):
|
|
125
|
+
for k2 in range(-p2,p2+1):
|
|
126
|
+
S[:,:,k1,k2] = nonzero_values[k1,k2]
|
|
127
|
+
S.remove_spurious_entries()
|
|
128
|
+
Sa = S.toarray()
|
|
129
|
+
|
|
130
|
+
nonzero_values1 = dict()
|
|
131
|
+
for k1 in range(-p1,p1+1):
|
|
132
|
+
for k2 in range(-p2,p2+1):
|
|
133
|
+
if k1 == 0:
|
|
134
|
+
if k2 == 0:
|
|
135
|
+
nonzero_values1[k1,k2] = 1
|
|
136
|
+
else:
|
|
137
|
+
nonzero_values1[k1,k2] = 0
|
|
138
|
+
else:
|
|
139
|
+
nonzero_values1[k1,k2] = 0
|
|
140
|
+
for k1 in range(-p1,p1+1):
|
|
141
|
+
for k2 in range(-p2,p2+1):
|
|
142
|
+
S1[:,:,k1,k2] = nonzero_values1[k1,k2]
|
|
143
|
+
S1.remove_spurious_entries()
|
|
144
|
+
S1a = S1.toarray()
|
|
145
|
+
|
|
146
|
+
nonzero_values2 = dict()
|
|
147
|
+
for k1 in range(-p1,p1+1):
|
|
148
|
+
for k2 in range(-p2,p2+1):
|
|
149
|
+
nonzero_values2[k1,k2] = 1 + k1*n2 + k2
|
|
150
|
+
for k1 in range(-p1,p1+1):
|
|
151
|
+
for k2 in range(-p2,p2+1):
|
|
152
|
+
if k1==0:
|
|
153
|
+
if k2<0:
|
|
154
|
+
nonzero_values2[k1,k2] = 0
|
|
155
|
+
elif k1<0:
|
|
156
|
+
nonzero_values2[k1,k2] = 0
|
|
157
|
+
for k1 in range(-p1,p1+1):
|
|
158
|
+
for k2 in range(-p2,p2+1):
|
|
159
|
+
S2[:,:,k1,k2] = nonzero_values2[k1,k2]
|
|
160
|
+
S2.remove_spurious_entries()
|
|
161
|
+
S2a = S2.toarray()
|
|
162
|
+
|
|
163
|
+
# Construct exact matrices by hand
|
|
164
|
+
A1 = np.zeros( S.shape )
|
|
165
|
+
for i1 in range(n1):
|
|
166
|
+
for i2 in range(n2):
|
|
167
|
+
for k1 in range(-p1,p1+1):
|
|
168
|
+
for k2 in range(-p2,p2+1):
|
|
169
|
+
j1 = (i1+k1) % n1
|
|
170
|
+
j2 = (i2+k2) % n2
|
|
171
|
+
i = i1*(n2) + i2
|
|
172
|
+
j = j1*(n2) + j2
|
|
173
|
+
if (P1 or 0 <= i1+k1 < n1) and (P2 or 0 <= i2+k2 < n2):
|
|
174
|
+
A1[i,j] = nonzero_values[k1,k2]
|
|
175
|
+
|
|
176
|
+
A2 = np.zeros( S.shape )
|
|
177
|
+
for i1 in range(n1):
|
|
178
|
+
for i2 in range(n2):
|
|
179
|
+
for k1 in range(-p1,p1+1):
|
|
180
|
+
for k2 in range(-p2,p2+1):
|
|
181
|
+
j1 = (i1+k1) % n1
|
|
182
|
+
j2 = (i2+k2) % n2
|
|
183
|
+
i = i1*(n2) + i2
|
|
184
|
+
j = j1*(n2) + j2
|
|
185
|
+
if (P1 or 0 <= i1+k1 < n1) and (P2 or 0 <= i2+k2 < n2):
|
|
186
|
+
A2[i,j] = nonzero_values1[k1,k2]
|
|
187
|
+
|
|
188
|
+
# Check shape and data in 2D array
|
|
189
|
+
assert np.array_equal(v.toarray(), np.ones(n1 * n2))
|
|
190
|
+
|
|
191
|
+
assert Sa.shape == S.shape
|
|
192
|
+
assert np.array_equal( Sa, A1 )
|
|
193
|
+
assert S1a.shape == S1.shape
|
|
194
|
+
assert np.array_equal( S1a, A2 )
|
|
195
|
+
|
|
196
|
+
###
|
|
197
|
+
### 2. Test general basic operations
|
|
198
|
+
### Addition, Substraction, Negation, Multiplication, Composition, Raising to a Power, Transposing
|
|
199
|
+
###
|
|
200
|
+
|
|
201
|
+
## ___Addition and Substraction, also Negation___
|
|
202
|
+
|
|
203
|
+
# Adding and Substracting StencilMatrices returns a StencilMatrix
|
|
204
|
+
assert isinstance(S + S1, StencilMatrix)
|
|
205
|
+
assert isinstance(S - S1, StencilMatrix)
|
|
206
|
+
|
|
207
|
+
# Adding and Substracting StencilMatrices and other LOs returns a SumLinearOperator object
|
|
208
|
+
assert isinstance(S + I, SumLinearOperator)
|
|
209
|
+
assert isinstance(I + S, SumLinearOperator)
|
|
210
|
+
assert isinstance(S - I, SumLinearOperator)
|
|
211
|
+
assert isinstance(I - S, SumLinearOperator)
|
|
212
|
+
|
|
213
|
+
# Negating a StencilMatrix works as intended
|
|
214
|
+
assert isinstance(-S, StencilMatrix)
|
|
215
|
+
assert array_equal((-S).dot(v), -S.dot(v))
|
|
216
|
+
|
|
217
|
+
## ___Multiplication, Composition, Raising to a Power___
|
|
218
|
+
|
|
219
|
+
# Multiplying and Dividing a StencilMatrix by a scalar returns a StencilMatrix
|
|
220
|
+
assert isinstance(np.pi * S, StencilMatrix)
|
|
221
|
+
assert isinstance(S * np.pi, StencilMatrix)
|
|
222
|
+
assert isinstance(S / np.pi, StencilMatrix)
|
|
223
|
+
|
|
224
|
+
# Composing StencilMatrices works
|
|
225
|
+
assert isinstance(S @ S1, ComposedLinearOperator)
|
|
226
|
+
|
|
227
|
+
# Raising a StencilMatrix to a power works
|
|
228
|
+
assert isinstance(S**3, PowerLinearOperator)
|
|
229
|
+
|
|
230
|
+
## ___Transposing___
|
|
231
|
+
|
|
232
|
+
assert not np.array_equal(S2a, S2a.T) # using a nonsymmetric matrix throughout
|
|
233
|
+
assert isinstance(S2.T, StencilMatrix)
|
|
234
|
+
assert np.array_equal(S2.T.toarray(), S2a.T)
|
|
235
|
+
assert np.array_equal(S2.T.T.toarray(), S2a)
|
|
236
|
+
|
|
237
|
+
###
|
|
238
|
+
### 3. Test special cases
|
|
239
|
+
### Add. und Sub. with ZeroO's, Composition with Zero- and IdentityO's, Raising to the power of 0 and 1
|
|
240
|
+
###
|
|
241
|
+
|
|
242
|
+
## ___Addition and Substraction with ZeroOperators___
|
|
243
|
+
|
|
244
|
+
# Adding a ZeroOperator does not change the StencilMatrix
|
|
245
|
+
assert (S + Z) is S
|
|
246
|
+
assert (Z + S) is S
|
|
247
|
+
|
|
248
|
+
# Substracting a ZeroOperator and substracting from a ZeroOperator work as intended
|
|
249
|
+
assert (S - Z) is S
|
|
250
|
+
assert array_equal(-S, Z - S)
|
|
251
|
+
|
|
252
|
+
## ___Composing with Zero- and IdentityOperators___
|
|
253
|
+
|
|
254
|
+
# Composing a StencilMatrix with a ZeroOperator returns a ZeroOperator
|
|
255
|
+
assert isinstance(S @ Z, ZeroOperator)
|
|
256
|
+
assert isinstance(Z @ S, ZeroOperator)
|
|
257
|
+
|
|
258
|
+
# Composing a StencilMatrix with the IdentityOperator does not change the object
|
|
259
|
+
assert (S @ I) is S
|
|
260
|
+
assert (I @ S) is S
|
|
261
|
+
|
|
262
|
+
## ___Raising to the power of 0 and 1___
|
|
263
|
+
|
|
264
|
+
# Raising a StencilMatrix to the power of 1 or 0 does not change the object / returns an IdentityOperator
|
|
265
|
+
assert S**1 is S
|
|
266
|
+
assert isinstance(S**0, IdentityOperator)
|
|
267
|
+
|
|
268
|
+
#===============================================================================
|
|
269
|
+
@pytest.mark.parametrize('n1', n1array)
|
|
270
|
+
@pytest.mark.parametrize('n2', n2array)
|
|
271
|
+
@pytest.mark.parametrize('p1', p1array)
|
|
272
|
+
@pytest.mark.parametrize('p2', p2array)
|
|
273
|
+
|
|
274
|
+
def test_square_block_basic(n1, n2, p1, p2, P1=False, P2=False):
|
|
275
|
+
|
|
276
|
+
# 1. Initiate square LOs S,S1 (StencilMatrix), Z (ZeroOperator) and a Stencilvector v
|
|
277
|
+
# Initiate square LOs B,B1 (BlockLO), BZ (ZeroOperator), BI (IdentityOperator) and a BlockVector vb
|
|
278
|
+
# 2. Test general basic operations
|
|
279
|
+
# 3. Test special cases
|
|
280
|
+
|
|
281
|
+
# Initiate StencilVectorSpace
|
|
282
|
+
V = get_StencilVectorSpace([n1, n2], [p1, p2], [P1, P2])
|
|
283
|
+
|
|
284
|
+
# Initiate Linear Operators
|
|
285
|
+
Z = ZeroOperator(V, V)
|
|
286
|
+
S = StencilMatrix(V, V)
|
|
287
|
+
S1 = StencilMatrix(V, V)
|
|
288
|
+
# a non-symmetric StencilMatrix for transpose testing
|
|
289
|
+
S2 = StencilMatrix(V,V)
|
|
290
|
+
|
|
291
|
+
# Initiate a StencilVector
|
|
292
|
+
v = StencilVector(V)
|
|
293
|
+
for i in range(n1):
|
|
294
|
+
for j in range(n2):
|
|
295
|
+
v[i,j] = 1
|
|
296
|
+
|
|
297
|
+
nonzero_values = dict()
|
|
298
|
+
for k1 in range(-p1,p1+1):
|
|
299
|
+
for k2 in range(-p2,p2+1):
|
|
300
|
+
nonzero_values[k1,k2] = 1 + k1*n2 + k2
|
|
301
|
+
for k1 in range(-p1,p1+1):
|
|
302
|
+
for k2 in range(-p2,p2+1):
|
|
303
|
+
if k1==0:
|
|
304
|
+
if k2<0:
|
|
305
|
+
nonzero_values[k1,k2] = nonzero_values[-k1,-k2]
|
|
306
|
+
elif k1<0:
|
|
307
|
+
nonzero_values[k1,k2] = nonzero_values[-k1,-k2]
|
|
308
|
+
|
|
309
|
+
for k1 in range(-p1,p1+1):
|
|
310
|
+
for k2 in range(-p2,p2+1):
|
|
311
|
+
S[:,:,k1,k2] = nonzero_values[k1,k2]
|
|
312
|
+
S.remove_spurious_entries()
|
|
313
|
+
|
|
314
|
+
nonzero_values1 = dict()
|
|
315
|
+
for k1 in range(-p1,p1+1):
|
|
316
|
+
for k2 in range(-p2,p2+1):
|
|
317
|
+
if k1 == 0:
|
|
318
|
+
if k2 == 0:
|
|
319
|
+
nonzero_values1[k1,k2] = 1
|
|
320
|
+
else:
|
|
321
|
+
nonzero_values1[k1,k2] = 0
|
|
322
|
+
else:
|
|
323
|
+
nonzero_values1[k1,k2] = 0
|
|
324
|
+
for k1 in range(-p1,p1+1):
|
|
325
|
+
for k2 in range(-p2,p2+1):
|
|
326
|
+
S1[:,:,k1,k2] = nonzero_values1[k1,k2]
|
|
327
|
+
S1.remove_spurious_entries()
|
|
328
|
+
|
|
329
|
+
nonzero_values2 = dict()
|
|
330
|
+
for k1 in range(-p1,p1+1):
|
|
331
|
+
for k2 in range(-p2,p2+1):
|
|
332
|
+
nonzero_values2[k1,k2] = 1 + k1*n2 + k2
|
|
333
|
+
for k1 in range(-p1,p1+1):
|
|
334
|
+
for k2 in range(-p2,p2+1):
|
|
335
|
+
if k1==0:
|
|
336
|
+
if k2<0:
|
|
337
|
+
nonzero_values2[k1,k2] = 0
|
|
338
|
+
elif k1<0:
|
|
339
|
+
nonzero_values2[k1,k2] = 0
|
|
340
|
+
for k1 in range(-p1,p1+1):
|
|
341
|
+
for k2 in range(-p2,p2+1):
|
|
342
|
+
S2[:,:,k1,k2] = nonzero_values2[k1,k2]
|
|
343
|
+
S2.remove_spurious_entries()
|
|
344
|
+
|
|
345
|
+
# Initiate a BlockVectorSpace
|
|
346
|
+
Vb = BlockVectorSpace(V,V)
|
|
347
|
+
|
|
348
|
+
# Initiate BlockLOs and LOs acting on BlockVectorSpaces
|
|
349
|
+
BZ = ZeroOperator(Vb, Vb)
|
|
350
|
+
BI = IdentityOperator(Vb, Vb)
|
|
351
|
+
B = BlockLinearOperator(Vb, Vb, ((S, None), (None, S)))
|
|
352
|
+
B1 = BlockLinearOperator(Vb, Vb, ((S1, None), (Z, S1)))
|
|
353
|
+
B2 = BlockLinearOperator(Vb, Vb, ((S2, None), (None, S2)))
|
|
354
|
+
|
|
355
|
+
# Initiate a BlockVector
|
|
356
|
+
vb = BlockVector(Vb, (v, v))
|
|
357
|
+
|
|
358
|
+
###
|
|
359
|
+
### 2. Test general basic operations
|
|
360
|
+
### Addition, Substraction, Negation, Multiplication, Composition, Raising to a Power, Transposing
|
|
361
|
+
###
|
|
362
|
+
|
|
363
|
+
## ___Addition and Substraction, also Negation___
|
|
364
|
+
|
|
365
|
+
# Adding and Substracting BlockLOs returns a BlockLO
|
|
366
|
+
assert isinstance(B + B1, BlockLinearOperator)
|
|
367
|
+
assert isinstance(B - B1, BlockLinearOperator)
|
|
368
|
+
|
|
369
|
+
# Adding and Substracting BlockLOs and other LOs returns a SumLinearOperator object
|
|
370
|
+
assert isinstance(B + BI, SumLinearOperator)
|
|
371
|
+
assert isinstance(BI + B, SumLinearOperator)
|
|
372
|
+
assert isinstance(B - BI, SumLinearOperator)
|
|
373
|
+
assert isinstance(BI - B, SumLinearOperator)
|
|
374
|
+
|
|
375
|
+
# Negating a BlockLO works as intended
|
|
376
|
+
assert isinstance(-B, BlockLinearOperator)
|
|
377
|
+
assert array_equal((-B).dot(vb), -B.dot(vb))
|
|
378
|
+
|
|
379
|
+
## ___Multiplication, Composition, Raising to a Power___
|
|
380
|
+
|
|
381
|
+
# Multiplying and Dividing a BlockLO by a scalar returns a BlockLO
|
|
382
|
+
assert isinstance(np.pi * B, BlockLinearOperator)
|
|
383
|
+
assert isinstance(B * np.pi, BlockLinearOperator)
|
|
384
|
+
assert isinstance(B / np.pi, BlockLinearOperator)
|
|
385
|
+
|
|
386
|
+
# Composing BlockLOs works
|
|
387
|
+
assert isinstance(B @ B1, ComposedLinearOperator)
|
|
388
|
+
|
|
389
|
+
# Raising a BlockLO to a power works
|
|
390
|
+
assert isinstance(B**3, PowerLinearOperator)
|
|
391
|
+
|
|
392
|
+
## ___Transposing___
|
|
393
|
+
assert not np.array_equal(B2.toarray(), B2.toarray().T) # using a nonsymmetric matrix throughout
|
|
394
|
+
assert isinstance(B2.T, BlockLinearOperator)
|
|
395
|
+
assert np.array_equal(B2.T.toarray(), B2.toarray().T)
|
|
396
|
+
assert np.array_equal(B2.T.T.toarray(), B2.toarray())
|
|
397
|
+
|
|
398
|
+
###
|
|
399
|
+
### 3. Test special cases
|
|
400
|
+
### Add. und Sub. with ZeroO's, Composition with Zero- and IdentityO's, Raising to the power of 0 and 1
|
|
401
|
+
###
|
|
402
|
+
|
|
403
|
+
## ___Addition and Substraction with ZeroOperators___
|
|
404
|
+
|
|
405
|
+
# Adding a ZeroOperator does not change the BlockLO
|
|
406
|
+
BBZ = B + BZ
|
|
407
|
+
BZB = BZ + B
|
|
408
|
+
assert sparse_equal(BBZ, B)
|
|
409
|
+
assert sparse_equal(BZB, B)
|
|
410
|
+
|
|
411
|
+
# Substracting a ZeroOperator and substracting from a ZeroOperator work as intended
|
|
412
|
+
BmBZ = B - BZ
|
|
413
|
+
BZmB = BZ - B
|
|
414
|
+
assert sparse_equal(BmBZ, B)
|
|
415
|
+
assert sparse_equal(BZmB, -B)
|
|
416
|
+
|
|
417
|
+
## ___Composing with Zero- and IdentityOperators___
|
|
418
|
+
|
|
419
|
+
# Composing a BlockLO with a ZeroOperator returns a ZeroOperator
|
|
420
|
+
# Update 21.12.: ZeroLOs and IdentityLOs from and/or to BlockVectorSpaces are now BlockLOs
|
|
421
|
+
# thus B@BZ is now a ComposedLO.
|
|
422
|
+
assert isinstance(B @ BZ, ComposedLinearOperator)
|
|
423
|
+
assert isinstance(BZ @ B, ComposedLinearOperator)
|
|
424
|
+
|
|
425
|
+
# Composing a BlockLO with the IdentityOperator does not change the object
|
|
426
|
+
assert B @ BI == B
|
|
427
|
+
assert BI @ B == B
|
|
428
|
+
|
|
429
|
+
## ___Raising to the power of 1___
|
|
430
|
+
|
|
431
|
+
# Raising a BlockLO to the power of 1 does not change the object
|
|
432
|
+
assert B**1 is B
|
|
433
|
+
|
|
434
|
+
## ___Raising to the power of 0___
|
|
435
|
+
|
|
436
|
+
# Raising a BlockLO to the power of 0 returns an IdentityOperator
|
|
437
|
+
assert isinstance(B**0, IdentityOperator)
|
|
438
|
+
assert sparse_equal(B**0, BI)
|
|
439
|
+
|
|
440
|
+
#===============================================================================
|
|
441
|
+
@pytest.mark.parametrize('n1', n1array)
|
|
442
|
+
@pytest.mark.parametrize('n2', n2array)
|
|
443
|
+
@pytest.mark.parametrize('p1', p1array)
|
|
444
|
+
@pytest.mark.parametrize('p2', p2array)
|
|
445
|
+
|
|
446
|
+
def test_in_place_operations(n1, n2, p1, p2, P1=False, P2=False):
|
|
447
|
+
|
|
448
|
+
# testing __imul__ although not explicitly implemented (in the LinearOperator class)
|
|
449
|
+
|
|
450
|
+
# Initiate StencilVectorSpace
|
|
451
|
+
V = get_StencilVectorSpace([n1, n2], [p1, p2], [P1, P2])
|
|
452
|
+
Vc = get_StencilVectorSpace([n1, n2], [p1, p2], [P1, P2])
|
|
453
|
+
Vc._dtype = complex
|
|
454
|
+
v = StencilVector(V)
|
|
455
|
+
vc = StencilVector(Vc)
|
|
456
|
+
v_array = np.zeros(n1*n2)
|
|
457
|
+
|
|
458
|
+
for i in range(n1):
|
|
459
|
+
for j in range(n2):
|
|
460
|
+
v[i,j] = i+1
|
|
461
|
+
v_array[i*n2+j] = i+1
|
|
462
|
+
vc[i,j] = i+1
|
|
463
|
+
|
|
464
|
+
I1 = IdentityOperator(V,V)
|
|
465
|
+
I2 = IdentityOperator(V,V)
|
|
466
|
+
I3 = IdentityOperator(V,V)
|
|
467
|
+
I4 = IdentityOperator(Vc,Vc)
|
|
468
|
+
|
|
469
|
+
I1 *= 0
|
|
470
|
+
I2 *= 1
|
|
471
|
+
I3 *= 3
|
|
472
|
+
v3 = I3.dot(v)
|
|
473
|
+
I4 *= 3j
|
|
474
|
+
v4 = I4.dot(vc)
|
|
475
|
+
|
|
476
|
+
assert np.array_equal(v.toarray(), v_array)
|
|
477
|
+
assert isinstance(I1, ZeroOperator)
|
|
478
|
+
assert isinstance(I2, IdentityOperator)
|
|
479
|
+
assert isinstance(I3, ScaledLinearOperator)
|
|
480
|
+
assert np.array_equal(v3.toarray(), np.dot(v_array, 3))
|
|
481
|
+
assert isinstance(I4, ScaledLinearOperator)
|
|
482
|
+
assert np.array_equal(v4.toarray(), np.dot(v_array, 3j))
|
|
483
|
+
|
|
484
|
+
# testing __iadd__ and __isub__ although not explicitly implemented (in the LinearOperator class)
|
|
485
|
+
|
|
486
|
+
nonzero_values1 = dict()
|
|
487
|
+
for k1 in range(-p1,p1+1):
|
|
488
|
+
for k2 in range(-p2,p2+1):
|
|
489
|
+
nonzero_values1[k1,k2] = 1 + k1*n2 + k2
|
|
490
|
+
for k1 in range(-p1,p1+1):
|
|
491
|
+
for k2 in range(-p2,p2+1):
|
|
492
|
+
if k1==0:
|
|
493
|
+
if k2<0:
|
|
494
|
+
nonzero_values1[k1,k2] = nonzero_values1[-k1,-k2]
|
|
495
|
+
elif k1<0:
|
|
496
|
+
nonzero_values1[k1,k2] = nonzero_values1[-k1,-k2]
|
|
497
|
+
|
|
498
|
+
S = StencilMatrix(V,V)
|
|
499
|
+
Z1 = ZeroOperator(V,V)
|
|
500
|
+
Z2 = ZeroOperator(V,V)
|
|
501
|
+
Z3 = Z1.copy()
|
|
502
|
+
|
|
503
|
+
for k1 in range(-p1,p1+1):
|
|
504
|
+
for k2 in range(-p2,p2+1):
|
|
505
|
+
S[:,:,k1,k2] = nonzero_values1[k1,k2]
|
|
506
|
+
S.remove_spurious_entries()
|
|
507
|
+
T = S.copy()
|
|
508
|
+
Sa = S.toarray()
|
|
509
|
+
|
|
510
|
+
Z1 += S
|
|
511
|
+
S += Z2
|
|
512
|
+
|
|
513
|
+
assert isinstance(Z1, StencilMatrix)
|
|
514
|
+
assert isinstance(S, StencilMatrix)
|
|
515
|
+
|
|
516
|
+
S += Z1
|
|
517
|
+
|
|
518
|
+
w = S.dot(v)
|
|
519
|
+
|
|
520
|
+
assert isinstance(S, StencilMatrix)
|
|
521
|
+
assert np.array_equal(w.toarray(), np.dot(np.dot(2, Sa), v_array))
|
|
522
|
+
|
|
523
|
+
Z3 -= T
|
|
524
|
+
T -= Z2
|
|
525
|
+
T -= S+3*Z3
|
|
526
|
+
|
|
527
|
+
w2 = T.dot(v)
|
|
528
|
+
|
|
529
|
+
assert isinstance(Z3, StencilMatrix)
|
|
530
|
+
assert isinstance(T, StencilMatrix)
|
|
531
|
+
assert np.array_equal(w2.toarray(), np.dot(np.dot(2, Sa), v_array))
|
|
532
|
+
|
|
533
|
+
#===============================================================================
|
|
534
|
+
@pytest.mark.parametrize('n1', n1array)
|
|
535
|
+
@pytest.mark.parametrize('n2', n2array)
|
|
536
|
+
@pytest.mark.parametrize('p1', p1array)
|
|
537
|
+
@pytest.mark.parametrize('p2', p2array)
|
|
538
|
+
|
|
539
|
+
def test_inverse_transpose_interaction(n1, n2, p1, p2, P1=False, P2=False):
|
|
540
|
+
|
|
541
|
+
# 1. Initiate square LOs: S (V->V, StencilMatrix), S1 (W->W, StencilMatrix)
|
|
542
|
+
#Initiate BlockLO: B (VxW -> VxW) and a BlockVector u element of VxW
|
|
543
|
+
# 2. For both B and S, check whether all possible combinations of the transpose and the inverse behave as expected
|
|
544
|
+
|
|
545
|
+
# Initiate StencilVectorSpace
|
|
546
|
+
V = get_StencilVectorSpace([n1, n2], [p1, p2], [P1, P2])
|
|
547
|
+
V2 = get_StencilVectorSpace([n1, n2], [p1, p2], [P1, P2])
|
|
548
|
+
W = get_StencilVectorSpace([n1+2, n2], [p1, p2+1], [P1, P2])
|
|
549
|
+
|
|
550
|
+
# Initiate positive definite StencilMatrices for which the cg inverse works (necessary for certain tests)
|
|
551
|
+
S = StencilMatrix(V, V)
|
|
552
|
+
S1 = StencilMatrix(W, W)
|
|
553
|
+
S2 = StencilMatrix(V, V2)
|
|
554
|
+
|
|
555
|
+
# Initiate StencilVectors
|
|
556
|
+
v = StencilVector(V)
|
|
557
|
+
for i in range(n1):
|
|
558
|
+
for j in range(n2):
|
|
559
|
+
v[i,j] = 1
|
|
560
|
+
v2 = StencilVector(V2)
|
|
561
|
+
for i in range(n1):
|
|
562
|
+
for j in range(n2):
|
|
563
|
+
v2[i,j] = 1
|
|
564
|
+
w = StencilVector(W)
|
|
565
|
+
for i in range(n1+2):
|
|
566
|
+
for j in range(n2):
|
|
567
|
+
w[i,j] = 1
|
|
568
|
+
|
|
569
|
+
# Fill the matrices S and S1 (both upper triangular)
|
|
570
|
+
nonzero_values = dict()
|
|
571
|
+
for k1 in range(-p1,p1+1):
|
|
572
|
+
for k2 in range(-p2,p2+1):
|
|
573
|
+
nonzero_values[k1,k2] = 1 + k1*n2 + k2
|
|
574
|
+
for k1 in range(-p1,p1+1):
|
|
575
|
+
for k2 in range(-p2,p2+1):
|
|
576
|
+
if k1==0:
|
|
577
|
+
if k2<0:
|
|
578
|
+
nonzero_values[k1,k2] = nonzero_values[-k1,-k2]
|
|
579
|
+
elif k1<0:
|
|
580
|
+
nonzero_values[k1,k2] = nonzero_values[-k1,-k2]
|
|
581
|
+
|
|
582
|
+
nonzero_values1 = dict()
|
|
583
|
+
for k1 in range(-p1,p1+1):
|
|
584
|
+
for k2 in range(-p2-1,p2+2):
|
|
585
|
+
nonzero_values1[k1,k2] = 1 + k1*n2 + k2
|
|
586
|
+
for k1 in range(-p1,p1+1):
|
|
587
|
+
for k2 in range(-p2-1,p2+2):
|
|
588
|
+
if k1==0:
|
|
589
|
+
if k2<0:
|
|
590
|
+
nonzero_values1[k1,k2] = nonzero_values1[-k1,-k2]
|
|
591
|
+
elif k1<0:
|
|
592
|
+
nonzero_values1[k1,k2] = nonzero_values1[-k1,-k2]
|
|
593
|
+
|
|
594
|
+
for k1 in range(-p1,p1+1):
|
|
595
|
+
for k2 in range(-p2,p2+1):
|
|
596
|
+
S[:,:,k1,k2] = nonzero_values[k1,k2]
|
|
597
|
+
S2[:,:,k1,k2] = nonzero_values[k1,k2]
|
|
598
|
+
S.remove_spurious_entries()
|
|
599
|
+
S2.remove_spurious_entries()
|
|
600
|
+
|
|
601
|
+
for k1 in range(-p1,p1+1):
|
|
602
|
+
for k2 in range(-p2-1,p2+2):
|
|
603
|
+
S1[:,:,k1,k2] = nonzero_values1[k1,k2]
|
|
604
|
+
S1.remove_spurious_entries()
|
|
605
|
+
|
|
606
|
+
# Initiate a BlockVectorSpace, a BlockLO and a BlockVector
|
|
607
|
+
U = BlockVectorSpace(V, W)
|
|
608
|
+
B = BlockLinearOperator(U, U, ((S, None), (None, S1)))
|
|
609
|
+
u = BlockVector(U, (v,w))
|
|
610
|
+
|
|
611
|
+
###
|
|
612
|
+
### Test whether pre-allocated storage in InverseLinearoperator subclasses belong to the right space.
|
|
613
|
+
### Not working so far as algorithms implicitely assume domain == codomain.
|
|
614
|
+
###
|
|
615
|
+
|
|
616
|
+
#S2_inv_pcg = inverse(S2, 'pcg', pc='jacobi', tol=1e-9)
|
|
617
|
+
#S2_inv_lsmr = inverse(S2, 'lsmr', tol=1e-9)
|
|
618
|
+
#x_pcg = S2_inv_pcg @ v2
|
|
619
|
+
#x_lsmr = S2_inv_lsmr @ v2
|
|
620
|
+
#assert isinstance(x_pcg, V)
|
|
621
|
+
#assert isinstance(x_lsmr, V)
|
|
622
|
+
|
|
623
|
+
###
|
|
624
|
+
### BlockLO Transpose - Inverse Tests
|
|
625
|
+
### -1,T & T,-1 --- -1,T,T --- -1,T,-1 --- T,-1,-1 --- T,-1,T (the combinations I test)
|
|
626
|
+
###
|
|
627
|
+
|
|
628
|
+
# Square root test
|
|
629
|
+
scaled_matrix = B * np.random.random() # Ensure the diagonal elements != 1
|
|
630
|
+
diagonal_values = scaled_matrix.diagonal(sqrt=False).toarray()
|
|
631
|
+
sqrt_diagonal_values = scaled_matrix.diagonal(sqrt=True).toarray()
|
|
632
|
+
assert np.array_equal(sqrt_diagonal_values, np.sqrt(diagonal_values))
|
|
633
|
+
|
|
634
|
+
tol = 1e-5
|
|
635
|
+
C = inverse(B, 'cg', tol=tol)
|
|
636
|
+
P = B.diagonal(inverse=True)
|
|
637
|
+
|
|
638
|
+
B_T = B.T
|
|
639
|
+
C_T = C.T
|
|
640
|
+
|
|
641
|
+
# -1,T & T,-1 -> equal
|
|
642
|
+
assert isinstance(C_T, ConjugateGradient)
|
|
643
|
+
assert isinstance(inverse(B_T, 'cg', tol=tol), ConjugateGradient)
|
|
644
|
+
diff = C_T @ u - inverse(B_T, 'cg', tol=tol) @ u
|
|
645
|
+
assert diff.inner(diff) == 0
|
|
646
|
+
|
|
647
|
+
# -1,T,T -> equal -1
|
|
648
|
+
diff = C_T.T @ u - C @ u
|
|
649
|
+
assert diff.inner(diff) == 0
|
|
650
|
+
|
|
651
|
+
# -1,T,-1 -> equal T
|
|
652
|
+
assert isinstance(inverse(C_T, 'bicg'), BlockLinearOperator)
|
|
653
|
+
diff = inverse(C_T, 'bicg') @ u - B_T @ u
|
|
654
|
+
assert diff.inner(diff) == 0
|
|
655
|
+
|
|
656
|
+
# T,-1,-1 -> equal T
|
|
657
|
+
assert isinstance(inverse(inverse(B_T, 'cg', tol=tol), 'pcg', pc=P), BlockLinearOperator)
|
|
658
|
+
diff = inverse(inverse(B_T, 'cg', tol=tol), 'pcg', pc=P) @ u - B_T @ u
|
|
659
|
+
assert diff.inner(diff) == 0
|
|
660
|
+
|
|
661
|
+
# T,-1,T -> equal -1
|
|
662
|
+
assert isinstance(inverse(B_T, 'cg', tol=tol).T, ConjugateGradient)
|
|
663
|
+
diff = inverse(B_T, 'cg', tol=tol) @ u - C @ u
|
|
664
|
+
assert diff.inner(diff) == 0
|
|
665
|
+
|
|
666
|
+
###
|
|
667
|
+
### StencilMatrix Transpose - Inverse Tests
|
|
668
|
+
### -1,T & T,-1 --- -1,T,T --- -1,T,-1 --- T,-1,-1 --- T,-1,T (the combinations I test)
|
|
669
|
+
###
|
|
670
|
+
|
|
671
|
+
tol = 1e-5
|
|
672
|
+
C = inverse(S, 'cg', tol=tol)
|
|
673
|
+
P = S.diagonal(inverse=True)
|
|
674
|
+
|
|
675
|
+
S_T = S.T
|
|
676
|
+
C_T = C.T
|
|
677
|
+
|
|
678
|
+
# -1,T & T,-1 -> equal
|
|
679
|
+
assert isinstance(C_T, ConjugateGradient)
|
|
680
|
+
assert isinstance(inverse(S_T, 'cg', tol=tol), ConjugateGradient)
|
|
681
|
+
diff = C_T @ v - inverse(S_T, 'cg', tol=tol) @ v
|
|
682
|
+
assert diff.inner(diff) == 0
|
|
683
|
+
|
|
684
|
+
# -1,T,T -> equal -1
|
|
685
|
+
diff = C_T.T @ v - C @ v
|
|
686
|
+
assert diff.inner(diff) == 0
|
|
687
|
+
|
|
688
|
+
# -1,T,-1 -> equal T
|
|
689
|
+
assert isinstance(inverse(C_T, 'bicg'), StencilMatrix)
|
|
690
|
+
diff = inverse(C_T, 'bicg') @ v - S_T @ v
|
|
691
|
+
assert diff.inner(diff) == 0
|
|
692
|
+
|
|
693
|
+
# T,-1,-1 -> equal T
|
|
694
|
+
assert isinstance(inverse(inverse(S_T, 'cg', tol=tol), 'pcg', pc=P), StencilMatrix)
|
|
695
|
+
diff = inverse(inverse(S_T, 'cg', tol=tol), 'pcg', pc=P) @ v - S_T @ v
|
|
696
|
+
assert diff.inner(diff) == 0
|
|
697
|
+
|
|
698
|
+
# T,-1,T -> equal -1
|
|
699
|
+
assert isinstance(inverse(S_T, 'cg', tol=tol).T, ConjugateGradient)
|
|
700
|
+
diff = inverse(S_T, 'cg', tol=tol) @ v - C @ v
|
|
701
|
+
assert diff.inner(diff) == 0
|
|
702
|
+
|
|
703
|
+
#===============================================================================
|
|
704
|
+
@pytest.mark.parametrize('n1', [3, 5])
|
|
705
|
+
@pytest.mark.parametrize('n2', [4, 7])
|
|
706
|
+
@pytest.mark.parametrize('p1', [2, 6])
|
|
707
|
+
@pytest.mark.parametrize('p2', [3, 9])
|
|
708
|
+
|
|
709
|
+
def test_positive_definite_matrix(n1, n2, p1, p2):
|
|
710
|
+
P1 = False
|
|
711
|
+
P2 = False
|
|
712
|
+
V = get_StencilVectorSpace([n1, n2], [p1, p2], [P1, P2])
|
|
713
|
+
S = get_positive_definite_StencilMatrix(V)
|
|
714
|
+
|
|
715
|
+
assert_pos_def(S)
|
|
716
|
+
|
|
717
|
+
#===============================================================================
|
|
718
|
+
@pytest.mark.parametrize('n1', [3, 5])
|
|
719
|
+
@pytest.mark.parametrize('n2', [4, 7])
|
|
720
|
+
@pytest.mark.parametrize('p1', [2, 6])
|
|
721
|
+
@pytest.mark.parametrize('p2', [3, 9])
|
|
722
|
+
|
|
723
|
+
def test_operator_evaluation(n1, n2, p1, p2):
|
|
724
|
+
|
|
725
|
+
# 1. Initiate StencilVectorSpace V, pos. def. Stencil Matrix S and StencilVector v = (1,1,1,1)
|
|
726
|
+
# Initiate a BlockVectorSpace U = VxV, a BlockLO B = [[V, None], [None, V]] and a BlockVector u = (v,v)
|
|
727
|
+
# as well as 2 "uncompatible" LOs: Z = ZeroO(U,U), I = IdentityO(V,V)
|
|
728
|
+
# Further create the conjugate gradient InverseLOs of S and B, S_ILO and B_ILO
|
|
729
|
+
# 2.1 PowerLO test
|
|
730
|
+
# Test B**(0,1,2), B_ILO**(0,1,2), Z**(0,1,2)
|
|
731
|
+
# S**(0,1,2), S_ILO**(0,1,2), I**(0,1,2)
|
|
732
|
+
# 2.2 SumLO test
|
|
733
|
+
# Test B + B_ILO + B + B_ILO
|
|
734
|
+
# S + S_ILO + S + S_ILO
|
|
735
|
+
# 2.3 CompositionLO test
|
|
736
|
+
# Test B @ (-B) = -B**2
|
|
737
|
+
# S @ (-S) = -S**2
|
|
738
|
+
# 2.4 Huge Composition
|
|
739
|
+
# H1 = S_ILO . T, testing inverse transpose interaction
|
|
740
|
+
# H2 = (S_ILO) ⁻¹, testing inverse inverse interaction
|
|
741
|
+
# H3 = (2 * S_ILO) @ (S**2), testing composition of container classes
|
|
742
|
+
# H4 = 2 * ( S¹ @ S⁰ ), testing power 1 and 0, composition with Identity, scaling of container class
|
|
743
|
+
# H5 = ZV @ I, ZV a ZeroO(V,V), testing composition with a ZeroO
|
|
744
|
+
# H = H1 @ ( H2 + H3 - H4 + H5 ) . T, testing all together
|
|
745
|
+
# 2.5 InverseLO test (explicit)
|
|
746
|
+
|
|
747
|
+
###
|
|
748
|
+
### 1.
|
|
749
|
+
###
|
|
750
|
+
|
|
751
|
+
P1 = False
|
|
752
|
+
P2 = False
|
|
753
|
+
|
|
754
|
+
# Initiate StencilVectorSpace V
|
|
755
|
+
V = get_StencilVectorSpace([n1, n2], [p1, p2], [P1, P2])
|
|
756
|
+
|
|
757
|
+
# Initiate positive definite StencilMatrices for which the cg inverse works (necessary for certain tests)
|
|
758
|
+
S = get_positive_definite_StencilMatrix(V)
|
|
759
|
+
|
|
760
|
+
# Initiate StencilVectors
|
|
761
|
+
v = StencilVector(V)
|
|
762
|
+
for i in range(n1):
|
|
763
|
+
for j in range(n2):
|
|
764
|
+
v[i,j] = 1
|
|
765
|
+
|
|
766
|
+
# Initiate a BlockVectorSpace, a BlockLO and a BlockVector
|
|
767
|
+
U = BlockVectorSpace(V, V)
|
|
768
|
+
B = BlockLinearOperator(U, U, ((S, None), (None, S)))
|
|
769
|
+
Z = ZeroOperator(U, U)
|
|
770
|
+
I = IdentityOperator(V, V)
|
|
771
|
+
u = BlockVector(U, (v,v))
|
|
772
|
+
tol = 1e-6
|
|
773
|
+
S_ILO = inverse(S, 'cg', tol=tol)
|
|
774
|
+
B_ILO = inverse(B, 'cg', tol=tol)
|
|
775
|
+
|
|
776
|
+
###
|
|
777
|
+
### 2.
|
|
778
|
+
###
|
|
779
|
+
|
|
780
|
+
### 2.1 PowerLO test
|
|
781
|
+
Bmat = B.toarray()
|
|
782
|
+
assert_pos_def(B)
|
|
783
|
+
uarr = u.toarray()
|
|
784
|
+
b0 = ( B**0 @ u ).toarray()
|
|
785
|
+
b1 = ( B**1 @ u ).toarray()
|
|
786
|
+
b2 = ( B**2 @ u ).toarray()
|
|
787
|
+
assert np.array_equal(uarr, b0)
|
|
788
|
+
assert np.linalg.norm( np.dot(Bmat, uarr) - b1 ) < 1e-10
|
|
789
|
+
assert np.linalg.norm( np.dot(Bmat, np.dot(Bmat, uarr)) - b2 ) < 1e-10
|
|
790
|
+
|
|
791
|
+
bi0 = ( B_ILO**0 @ u ).toarray()
|
|
792
|
+
bi1 = ( B_ILO**1 @ u ).toarray()
|
|
793
|
+
bi2 = ( B_ILO**2 @ u ).toarray()
|
|
794
|
+
B_inv_mat = np.linalg.inv(Bmat)
|
|
795
|
+
b_inv_arr = np.matrix.flatten(B_inv_mat)
|
|
796
|
+
error_est = 2 + n1 * n2 * np.max( [ np.abs(b_inv_arr[i]) for i in range(len(b_inv_arr)) ] )
|
|
797
|
+
assert np.array_equal(uarr, bi0)
|
|
798
|
+
bi12 = np.linalg.solve(Bmat, uarr)
|
|
799
|
+
bi22 = np.linalg.solve(Bmat, bi12)
|
|
800
|
+
assert np.linalg.norm( (Bmat @ bi12) - uarr ) < tol
|
|
801
|
+
assert np.linalg.norm( (Bmat @ bi22) - bi12 ) < error_est * tol
|
|
802
|
+
|
|
803
|
+
zeros = U.zeros().toarray()
|
|
804
|
+
z0 = ( Z**0 @ u ).toarray()
|
|
805
|
+
z1 = ( Z**1 @ u ).toarray()
|
|
806
|
+
z2 = ( Z**2 @ u ).toarray()
|
|
807
|
+
assert np.array_equal(uarr, z0)
|
|
808
|
+
assert np.array_equal(zeros, z1)
|
|
809
|
+
assert np.array_equal(zeros, z2)
|
|
810
|
+
|
|
811
|
+
Smat = S.toarray()
|
|
812
|
+
assert_pos_def(S)
|
|
813
|
+
varr = v.toarray()
|
|
814
|
+
s0 = ( S**0 @ v ).toarray()
|
|
815
|
+
s1 = ( S**1 @ v ).toarray()
|
|
816
|
+
s2 = ( S**2 @ v ).toarray()
|
|
817
|
+
assert np.array_equal(varr, s0)
|
|
818
|
+
assert np.linalg.norm( np.dot(Smat, varr) - s1 ) < 1e-10
|
|
819
|
+
assert np.linalg.norm( np.dot(Smat, np.dot(Smat, varr)) - s2 ) < 1e-10
|
|
820
|
+
|
|
821
|
+
si0 = ( S_ILO**0 @ v ).toarray()
|
|
822
|
+
si1 = ( S_ILO**1 @ v ).toarray()
|
|
823
|
+
si2 = ( S_ILO**2 @ v ).toarray()
|
|
824
|
+
S_inv_mat = np.linalg.inv(Smat)
|
|
825
|
+
s_inv_arr = np.matrix.flatten(S_inv_mat)
|
|
826
|
+
error_est = 2 + n1 * n2 * np.max( [ np.abs(s_inv_arr[i]) for i in range(len(s_inv_arr)) ] )
|
|
827
|
+
assert np.array_equal(varr, si0)
|
|
828
|
+
si12 = np.linalg.solve(Smat, varr)
|
|
829
|
+
si22 = np.linalg.solve(Smat, si12)
|
|
830
|
+
assert np.linalg.norm( (Smat @ si12) - varr ) < tol
|
|
831
|
+
assert np.linalg.norm( (Smat @ si22) - si12 ) < error_est * tol
|
|
832
|
+
|
|
833
|
+
i0 = ( I**0 @ v ).toarray()
|
|
834
|
+
i1 = ( I**1 @ v ).toarray()
|
|
835
|
+
i2 = ( I**2 @ v ).toarray()
|
|
836
|
+
assert np.array_equal(varr, i0)
|
|
837
|
+
assert np.array_equal(varr, i1)
|
|
838
|
+
assert np.array_equal(varr, i2)
|
|
839
|
+
|
|
840
|
+
### 2.2 SumLO tests
|
|
841
|
+
Sum1 = B + B_ILO + B + B_ILO
|
|
842
|
+
Sum2 = S + S_ILO + S + S_ILO
|
|
843
|
+
sum1 = Sum1 @ u
|
|
844
|
+
sum2 = Sum2 @ v
|
|
845
|
+
u_approx = B @ (0.5*(sum1 - 2*B@u))
|
|
846
|
+
v_approx = S @ (0.5*(sum2 - 2*S@v))
|
|
847
|
+
assert np.linalg.norm( (u_approx - u).toarray() ) < tol
|
|
848
|
+
assert np.linalg.norm( (v_approx - v).toarray() ) < tol
|
|
849
|
+
|
|
850
|
+
### 2.3 CompLO tests
|
|
851
|
+
C1 = B @ (-B)
|
|
852
|
+
C2 = S @ (-S)
|
|
853
|
+
c1 = ( C1 @ u ).toarray()
|
|
854
|
+
c2 = ( C2 @ v ).toarray()
|
|
855
|
+
assert np.array_equal(-c1, b2)
|
|
856
|
+
assert np.array_equal(-c2, s2)
|
|
857
|
+
|
|
858
|
+
### 2.4 Huge composition
|
|
859
|
+
ZV = ZeroOperator(V, V)
|
|
860
|
+
H1 = S_ILO.T
|
|
861
|
+
H2 = inverse(S_ILO, 'bicg', tol=tol)
|
|
862
|
+
H3 = (2 * S_ILO) @ S**2
|
|
863
|
+
H4 = 2 * (S**1 @ S**0)
|
|
864
|
+
H5 = ZV @ I
|
|
865
|
+
H = H1 @ ( H2 + H3 - H4 + H5 ).T
|
|
866
|
+
assert np.linalg.norm( (H @ v).toarray() - v.toarray() ) < 10 * tol
|
|
867
|
+
|
|
868
|
+
### 2.5 InverseLO test
|
|
869
|
+
|
|
870
|
+
S_cg = inverse(S, 'cg', tol=tol)
|
|
871
|
+
B_cg = inverse(B, 'cg', tol=tol)
|
|
872
|
+
S_pcg = inverse(S, 'pcg', pc=S.diagonal(inverse=True), tol=tol)
|
|
873
|
+
B_pcg = inverse(B, 'pcg', pc=B.diagonal(inverse=True), tol=tol)
|
|
874
|
+
S_bicg = inverse(S, 'bicg', tol=tol)
|
|
875
|
+
B_bicg = inverse(B, 'bicg', tol=tol)
|
|
876
|
+
S_lsmr = inverse(S, 'lsmr', tol=tol)
|
|
877
|
+
B_lsmr = inverse(B, 'lsmr', tol=tol)
|
|
878
|
+
S_mr = inverse(S, 'minres', tol=tol)
|
|
879
|
+
B_mr = inverse(B, 'minres', tol=tol)
|
|
880
|
+
|
|
881
|
+
xs_cg = S_cg @ v
|
|
882
|
+
xs_pcg = S_pcg @ v
|
|
883
|
+
xs_bicg = S_bicg @ v
|
|
884
|
+
xs_lsmr = S_lsmr @ v
|
|
885
|
+
xs_mr = S_mr @ v
|
|
886
|
+
|
|
887
|
+
xb_cg = B_cg @ u
|
|
888
|
+
xb_pcg = B_pcg @ u
|
|
889
|
+
xb_bicg = B_bicg @ u
|
|
890
|
+
xb_lsmr = B_lsmr @ u
|
|
891
|
+
xb_mr = B_mr @ u
|
|
892
|
+
|
|
893
|
+
# Several break-criteria in the LSMR algorithm require different way to determine success
|
|
894
|
+
# than asserting rnorm < tol, as that is not required. Even though it should?
|
|
895
|
+
|
|
896
|
+
assert np.linalg.norm( (S @ xs_cg - v).toarray() ) < tol
|
|
897
|
+
assert np.linalg.norm( (S @ xs_pcg - v).toarray() ) < tol
|
|
898
|
+
assert np.linalg.norm( (S @ xs_bicg - v).toarray() ) < tol
|
|
899
|
+
assert S_lsmr.get_success() == True
|
|
900
|
+
assert np.linalg.norm( (S @ xs_mr - v).toarray() ) < tol
|
|
901
|
+
|
|
902
|
+
assert np.linalg.norm( (B @ xb_cg - u).toarray() ) < tol
|
|
903
|
+
assert np.linalg.norm( (B @ xb_pcg - u).toarray() ) < tol
|
|
904
|
+
assert np.linalg.norm( (B @ xb_bicg - u).toarray() ) < tol
|
|
905
|
+
assert B_lsmr.get_success() == True
|
|
906
|
+
assert np.linalg.norm( (B @ xb_mr - u).toarray() ) < tol
|
|
907
|
+
|
|
908
|
+
#===============================================================================
|
|
909
|
+
|
|
910
|
+
def test_internal_storage():
|
|
911
|
+
|
|
912
|
+
# Create LinearOperator Z = A @ A.T @ A @ A.T @ A, where the domain and codomain of A are of different dimension.
|
|
913
|
+
# Prior to a fix, operator would not have enough preallocated storage defined.
|
|
914
|
+
|
|
915
|
+
n1=2
|
|
916
|
+
n2=1
|
|
917
|
+
p1=1
|
|
918
|
+
p2=1
|
|
919
|
+
P1=False
|
|
920
|
+
P2=False
|
|
921
|
+
V = get_StencilVectorSpace([n1, n2], [p1, p2], [P1, P2])
|
|
922
|
+
U1 = BlockVectorSpace(V, V)
|
|
923
|
+
U2 = BlockVectorSpace(V, V, V)
|
|
924
|
+
|
|
925
|
+
x1 = StencilVector(V)
|
|
926
|
+
x1[0] = 1
|
|
927
|
+
x1[1] = 1
|
|
928
|
+
x = BlockVector(U1, (x1, x1))
|
|
929
|
+
xx = BlockVector(U2, (x1, x1, x1))
|
|
930
|
+
|
|
931
|
+
A1 = StencilMatrix(V, V)
|
|
932
|
+
A1[0, 0, 0, 0] = 1
|
|
933
|
+
A1[1, 0, 0, 0] = 1
|
|
934
|
+
A = BlockLinearOperator(U1, U2, ((A1, A1), (A1, A1), (A1, A1)))
|
|
935
|
+
B = A.T
|
|
936
|
+
C = A
|
|
937
|
+
D = A.T
|
|
938
|
+
|
|
939
|
+
Z1_1 = A @ (B @ C)
|
|
940
|
+
Z1_2 = (A @ B) @ C
|
|
941
|
+
Z1_3 = A @ B @ C
|
|
942
|
+
y1_1 = Z1_1 @ x
|
|
943
|
+
y1_2 = Z1_2 @ x
|
|
944
|
+
y1_3 = Z1_3 @ x
|
|
945
|
+
|
|
946
|
+
Z2_1 = (A @ B) @ (C @ D)
|
|
947
|
+
Z2_2 = (A @ B @ C) @ D
|
|
948
|
+
Z2_3 = A @ (B @ C @ D)
|
|
949
|
+
y2_1 = Z2_1 @ xx
|
|
950
|
+
y2_2 = Z2_2 @ xx
|
|
951
|
+
y2_3 = Z2_3 @ xx
|
|
952
|
+
|
|
953
|
+
assert len(Z1_1.tmp_vectors) == 2
|
|
954
|
+
assert len(Z1_2.tmp_vectors) == 2
|
|
955
|
+
assert len(Z1_3.tmp_vectors) == 2
|
|
956
|
+
assert len(Z2_1.tmp_vectors) == 3
|
|
957
|
+
assert len(Z2_2.tmp_vectors) == 3
|
|
958
|
+
assert len(Z2_3.tmp_vectors) == 3
|
|
959
|
+
assert np.array_equal( y1_1.toarray(), y1_2.toarray() ) & np.array_equal( y1_2.toarray(), y1_3.toarray() )
|
|
960
|
+
assert np.array_equal( y2_1.toarray(), y2_2.toarray() ) & np.array_equal( y2_2.toarray(), y2_3.toarray() )
|
|
961
|
+
|
|
962
|
+
#===============================================================================
|
|
963
|
+
@pytest.mark.parametrize('solver', ['cg', 'pcg', 'bicg', 'minres', 'lsmr'])
|
|
964
|
+
|
|
965
|
+
def test_x0update(solver):
|
|
966
|
+
n1 = 4
|
|
967
|
+
n2 = 3
|
|
968
|
+
p1 = 5
|
|
969
|
+
p2 = 2
|
|
970
|
+
P1 = False
|
|
971
|
+
P2 = False
|
|
972
|
+
V = get_StencilVectorSpace([n1, n2], [p1, p2], [P1, P2])
|
|
973
|
+
A = get_positive_definite_StencilMatrix(V)
|
|
974
|
+
assert_pos_def(A)
|
|
975
|
+
b = StencilVector(V)
|
|
976
|
+
for n in range(n1):
|
|
977
|
+
b[n, :] = 1.
|
|
978
|
+
assert np.array_equal(b.toarray(), np.ones(n1*n2, dtype=float))
|
|
979
|
+
|
|
980
|
+
# Create Inverse
|
|
981
|
+
tol = 1e-6
|
|
982
|
+
if solver == 'pcg':
|
|
983
|
+
A_inv = inverse(A, solver, pc=A.diagonal(inverse=True), tol=tol)
|
|
984
|
+
else:
|
|
985
|
+
A_inv = inverse(A, solver, tol=tol)
|
|
986
|
+
|
|
987
|
+
# Check whether x0 is not None
|
|
988
|
+
x0_init = A_inv.get_options("x0")
|
|
989
|
+
assert x0_init is not None
|
|
990
|
+
|
|
991
|
+
# Apply inverse and check x0
|
|
992
|
+
x = A_inv @ b
|
|
993
|
+
x0_new1 = A_inv.get_options("x0")
|
|
994
|
+
assert x0_new1 is x0_init
|
|
995
|
+
|
|
996
|
+
# Change x0, apply A_inv and check for x0
|
|
997
|
+
A_inv.set_options(x0 = b)
|
|
998
|
+
assert A_inv.get_options("x0") is b
|
|
999
|
+
|
|
1000
|
+
x = A_inv @ b
|
|
1001
|
+
assert A_inv.get_options("x0") is b
|
|
1002
|
+
|
|
1003
|
+
# Apply inverse using out=x0 and check for updated x0
|
|
1004
|
+
x = A_inv.dot(b, out=b)
|
|
1005
|
+
assert A_inv.get_options('x0') is x
|
|
1006
|
+
|
|
1007
|
+
#===============================================================================
|
|
1008
|
+
def test_dot_inner():
|
|
1009
|
+
|
|
1010
|
+
n1, n2 = 4, 7
|
|
1011
|
+
p1, p2 = 2, 3
|
|
1012
|
+
P1, P2 = False, False
|
|
1013
|
+
|
|
1014
|
+
V = get_StencilVectorSpace([n1, n2], [p1, p2], [P1, P2])
|
|
1015
|
+
M = get_positive_definite_StencilMatrix(V)
|
|
1016
|
+
N = get_positive_definite_StencilMatrix(V)
|
|
1017
|
+
|
|
1018
|
+
U1 = BlockVectorSpace(V, V)
|
|
1019
|
+
U2 = BlockVectorSpace(V, V, V)
|
|
1020
|
+
A = BlockLinearOperator(U1, U2, ((M, None),
|
|
1021
|
+
(M, N),
|
|
1022
|
+
(None, N)))
|
|
1023
|
+
|
|
1024
|
+
b = A.domain.zeros()
|
|
1025
|
+
c = A.codomain.zeros()
|
|
1026
|
+
|
|
1027
|
+
# Set the values of b and c randomly from a uniform distribution over the
|
|
1028
|
+
# interval [0, 1)
|
|
1029
|
+
rng = np.random.default_rng(seed=42)
|
|
1030
|
+
for bj in b:
|
|
1031
|
+
Vj = bj.space
|
|
1032
|
+
rng.random(size=Vj.shape, dtype=Vj.dtype, out=bj._data)
|
|
1033
|
+
for ci in c:
|
|
1034
|
+
Vi = ci.space
|
|
1035
|
+
rng.random(size=Vi.shape, dtype=Vi.dtype, out=ci._data)
|
|
1036
|
+
|
|
1037
|
+
# Create a work vector for the dot product, needed to compare results
|
|
1038
|
+
work_vec = A.codomain.zeros()
|
|
1039
|
+
|
|
1040
|
+
# Result of dot product is a temporary vector, which is allocated and then
|
|
1041
|
+
# discarded. This is the default behavior of the dot method.
|
|
1042
|
+
r0 = A.dot(b).inner(c)
|
|
1043
|
+
|
|
1044
|
+
# Result of dot product is stored in work_vec and used in the next line
|
|
1045
|
+
A.dot(b, out=work_vec)
|
|
1046
|
+
r1 = work_vec.inner(c)
|
|
1047
|
+
|
|
1048
|
+
# Result of dot product is stored in work_vec and used in the same line
|
|
1049
|
+
r2 = A.dot(b, out=work_vec).inner(c)
|
|
1050
|
+
|
|
1051
|
+
# Calling the dot_inner method, which uses an internal work vector to store
|
|
1052
|
+
# the result of the dot product, and then uses it for the inner product.
|
|
1053
|
+
r3 = A.dot_inner(b, c)
|
|
1054
|
+
|
|
1055
|
+
# Check if the results are equal
|
|
1056
|
+
assert r0 == r1
|
|
1057
|
+
assert r0 == r2
|
|
1058
|
+
assert r0 == r3
|
|
1059
|
+
|
|
1060
|
+
#===============================================================================
|
|
1061
|
+
# SCRIPT FUNCTIONALITY
|
|
1062
|
+
#===============================================================================
|
|
1063
|
+
if __name__ == "__main__":
|
|
1064
|
+
import sys
|
|
1065
|
+
pytest.main( sys.argv )
|