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.
Files changed (98) hide show
  1. feectools/__init__.py +0 -0
  2. feectools/accelerate/__init__.py +0 -0
  3. feectools/accelerate/accelerate.py +220 -0
  4. feectools/accelerate/compile_psydac.mk +52 -0
  5. feectools/api/__init__.py +0 -0
  6. feectools/api/essential_bc.py +122 -0
  7. feectools/api/fem_bilinear_form.py +2226 -0
  8. feectools/api/fem_common.py +286 -0
  9. feectools/api/fem_sum_form.py +123 -0
  10. feectools/api/settings.py +82 -0
  11. feectools/core/__init__.py +11 -0
  12. feectools/core/bsplines.py +1107 -0
  13. feectools/core/bsplines_kernels.py +1349 -0
  14. feectools/core/field_evaluation_kernels.py +5015 -0
  15. feectools/core/tests/__init__.py +0 -0
  16. feectools/core/tests/test_bsplines.py +263 -0
  17. feectools/core/tests/test_bsplines_kernel.py +40 -0
  18. feectools/core/tests/test_bsplines_pyccel.py +752 -0
  19. feectools/ddm/__init__.py +3 -0
  20. feectools/ddm/basic.py +78 -0
  21. feectools/ddm/blocking_data_exchanger.py +348 -0
  22. feectools/ddm/cart.py +1835 -0
  23. feectools/ddm/interface_data_exchanger.py +122 -0
  24. feectools/ddm/mpi.py +109 -0
  25. feectools/ddm/nonblocking_data_exchanger.py +331 -0
  26. feectools/ddm/partition.py +207 -0
  27. feectools/ddm/petsc.py +112 -0
  28. feectools/ddm/tests/__init__.py +0 -0
  29. feectools/ddm/tests/test_cart_1d.py +138 -0
  30. feectools/ddm/tests/test_cart_2d.py +164 -0
  31. feectools/ddm/tests/test_cart_3d.py +158 -0
  32. feectools/ddm/tests/test_multicart_2d.py +173 -0
  33. feectools/ddm/tests/test_partition.py +124 -0
  34. feectools/ddm/utilities.py +24 -0
  35. feectools/feec/__init__.py +0 -0
  36. feectools/feec/derivatives.py +780 -0
  37. feectools/feec/dof_kernels.py +210 -0
  38. feectools/feec/global_geometric_projectors.py +1073 -0
  39. feectools/feec/hodge.py +148 -0
  40. feectools/fem/__init__.py +0 -0
  41. feectools/fem/basic.py +465 -0
  42. feectools/fem/grid.py +181 -0
  43. feectools/fem/partitioning.py +344 -0
  44. feectools/fem/projectors.py +160 -0
  45. feectools/fem/splines.py +559 -0
  46. feectools/fem/tensor.py +1393 -0
  47. feectools/fem/tests/__init__.py +0 -0
  48. feectools/fem/tests/analytical_profiles_1d.py +100 -0
  49. feectools/fem/tests/analytical_profiles_base.py +34 -0
  50. feectools/fem/tests/splines_error_bounds.py +155 -0
  51. feectools/fem/tests/test_spline_histopolation.py +120 -0
  52. feectools/fem/tests/test_spline_interpolation.py +182 -0
  53. feectools/fem/tests/test_splines.py +184 -0
  54. feectools/fem/tests/test_splines_par.py +46 -0
  55. feectools/fem/tests/test_vector_spaces.py +150 -0
  56. feectools/fem/tests/utilities.py +47 -0
  57. feectools/fem/vector.py +729 -0
  58. feectools/linalg/__init__.py +0 -0
  59. feectools/linalg/basic.py +1386 -0
  60. feectools/linalg/block.py +1451 -0
  61. feectools/linalg/direct_solvers.py +201 -0
  62. feectools/linalg/fft.py +258 -0
  63. feectools/linalg/kernels/__init__.py +0 -0
  64. feectools/linalg/kernels/axpy_kernels.py +57 -0
  65. feectools/linalg/kernels/inner_kernels.py +100 -0
  66. feectools/linalg/kernels/matvec_kernels.py +206 -0
  67. feectools/linalg/kernels/stencil2IJV_kernels.py +227 -0
  68. feectools/linalg/kernels/stencil2coo_kernels.py +179 -0
  69. feectools/linalg/kernels/transpose_kernels.py +263 -0
  70. feectools/linalg/kron.py +911 -0
  71. feectools/linalg/solvers.py +1914 -0
  72. feectools/linalg/sparse.py +114 -0
  73. feectools/linalg/stencil.py +2923 -0
  74. feectools/linalg/stencil_dot_kernels.py +317 -0
  75. feectools/linalg/stencil_transpose_kernels.py +372 -0
  76. feectools/linalg/tests/__init__.py +0 -0
  77. feectools/linalg/tests/test_block.py +1588 -0
  78. feectools/linalg/tests/test_fft.py +106 -0
  79. feectools/linalg/tests/test_kron_stencil_matrix.py +114 -0
  80. feectools/linalg/tests/test_linalg.py +1065 -0
  81. feectools/linalg/tests/test_matrix_free.py +128 -0
  82. feectools/linalg/tests/test_solvers.py +213 -0
  83. feectools/linalg/tests/test_stencil_interface_matrix.py +379 -0
  84. feectools/linalg/tests/test_stencil_vector.py +1036 -0
  85. feectools/linalg/tests/test_stencil_vector_space.py +440 -0
  86. feectools/linalg/topetsc.py +522 -0
  87. feectools/linalg/utilities.py +200 -0
  88. feectools/utilities/__init__.py +0 -0
  89. feectools/utilities/quadratures.py +113 -0
  90. feectools/utilities/utils.py +166 -0
  91. feectools/version.py +1 -0
  92. feectools-0.1.0.dist-info/METADATA +66 -0
  93. feectools-0.1.0.dist-info/RECORD +98 -0
  94. feectools-0.1.0.dist-info/WHEEL +5 -0
  95. feectools-0.1.0.dist-info/entry_points.txt +3 -0
  96. feectools-0.1.0.dist-info/licenses/AUTHORS +22 -0
  97. feectools-0.1.0.dist-info/licenses/LICENSE +21 -0
  98. 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 )