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,780 @@
1
+ # -*- coding: UTF-8 -*-
2
+
3
+ import numpy as np
4
+ import scipy.sparse as spa
5
+
6
+ from feectools.linalg.stencil import StencilVector, StencilMatrix, StencilVectorSpace
7
+ from feectools.linalg.kron import KroneckerStencilMatrix
8
+ from feectools.linalg.block import BlockVector, BlockLinearOperator
9
+ from feectools.fem.vector import VectorFemSpace
10
+ from feectools.fem.tensor import TensorFemSpace
11
+ from feectools.linalg.basic import IdentityOperator
12
+ from feectools.fem.basic import FemField, FemSpace, FemLinearOperator
13
+ from feectools.linalg.basic import LinearOperator
14
+ from feectools.ddm.cart import DomainDecomposition, CartDecomposition
15
+
16
+ __all__ = (
17
+ 'DirectionalDerivativeOperator',
18
+ 'Derivative1D',
19
+ 'Gradient2D',
20
+ 'Gradient3D',
21
+ 'ScalarCurl2D',
22
+ 'VectorCurl2D',
23
+ 'Curl3D',
24
+ 'Divergence2D',
25
+ 'Divergence3D',
26
+ 'BrokenGradient2D',
27
+ 'BrokenTransposedGradient2D',
28
+ 'BrokenScalarCurl2D',
29
+ 'BrokenTransposedScalarCurl2D',
30
+ )
31
+
32
+ #====================================================================================================
33
+ # Singlepatch derivative operators
34
+ #====================================================================================================
35
+ class DirectionalDerivativeOperator(LinearOperator):
36
+ """
37
+ Represents a matrix-free derivative operator in a specific cardinal direction.
38
+ Can be negated and transposed.
39
+
40
+ Parameters
41
+ ----------
42
+ V : StencilVectorSpace
43
+ The domain of the operator. (or codomain if transposed)
44
+
45
+ W : StencilVectorSpace
46
+ The codomain of the operator. (or domain, if transposed)
47
+ Has to be compatible with the domain, i.e. it has to
48
+ be equal to it, except for the differentiation direction.
49
+
50
+ diffdir : int
51
+ The differentiation direction.
52
+
53
+ negative : bool
54
+ If True, this operator is multiplied by -1 after execution.
55
+ (if False, nothing happens)
56
+
57
+ transposed : bool
58
+ If True, this operator represents the transposed derivative operator.
59
+ Note that then V is the codomain and W is the domain.
60
+ """
61
+
62
+ def __init__(self, V, W, diffdir, *, negative=False, transposed=False):
63
+ assert isinstance(V, StencilVectorSpace)
64
+ assert isinstance(W, StencilVectorSpace)
65
+ assert V.ndim == W.ndim
66
+ assert all([vp==wp for vp, wp in zip(V.periods, W.periods)])
67
+ assert V.parallel == W.parallel
68
+ assert V.dtype is W.dtype
69
+ assert diffdir >= 0 and diffdir < V.ndim
70
+
71
+ # no need for the pads to conform, but we want them to be there at least
72
+ # (we need them to perform the diff operation in-place)
73
+ assert all([pad > 0 for pad in V.pads])
74
+ assert all([pad > 0 for pad in W.pads])
75
+
76
+ # check that the number of points conforms
77
+ assert all([vn==wn+1 if not vwp and diffdir==i else vn==wn
78
+ for i, (vn, wn, vwp) in enumerate(zip(V.npts, W.npts, V.periods))])
79
+
80
+ self._spaceV = V
81
+ self._spaceW = W
82
+ self._diffdir = diffdir
83
+ self._negative = negative
84
+ self._transposed = transposed
85
+
86
+ if self._transposed:
87
+ self._domain = W
88
+ self._codomain = V
89
+ else:
90
+ self._domain = V
91
+ self._codomain = W
92
+
93
+ # the local area in the codomain without padding
94
+ self._idslice = tuple([slice(pad*m, e-s+1+pad*m) for pad, s, e, m
95
+ in zip(self._codomain.pads, self._codomain.starts, self._codomain.ends, self._codomain.shifts)])
96
+
97
+ # prepare the slices (they are of the right size then, we checked this already)
98
+ # identity slice
99
+ idslice = self._idslice
100
+
101
+ # differentiation slice (moved by one in the direction of differentiation)
102
+ diff_pad = self._codomain.pads[self._diffdir]
103
+ diff_s = self._codomain.starts[self._diffdir]
104
+ diff_e = self._codomain.ends[self._diffdir]
105
+ diff_m = self._codomain.shifts[self._diffdir]
106
+
107
+ # the diffslice depends on the transposition
108
+ if self._transposed:
109
+ diff_partslice = slice(diff_m*diff_pad-1, diff_e-diff_s+1+diff_pad*diff_m-1)
110
+ else:
111
+ diff_partslice = slice(diff_m*diff_pad+1, diff_e-diff_s+1+diff_m*diff_pad+1)
112
+
113
+ diffslice = tuple([diff_partslice if i==self._diffdir else idslice[i]
114
+ for i in range(self._domain.ndim)])
115
+
116
+
117
+ # define differentiation lambda based on the parameter negative (or sign)
118
+ if self._negative:
119
+ self._do_diff = lambda v,out: np.subtract(v._data[idslice],
120
+ v._data[diffslice], out=out._data[idslice])
121
+ else:
122
+ self._do_diff = lambda v,out: np.subtract(v._data[diffslice],
123
+ v._data[idslice], out=out._data[idslice])
124
+
125
+ @property
126
+ def domain(self):
127
+ return self._domain
128
+
129
+ # ...
130
+ @property
131
+ def codomain(self):
132
+ return self._codomain
133
+
134
+ # ...
135
+ @property
136
+ def dtype( self ):
137
+ return self.domain.dtype
138
+
139
+ def __truediv__(self, a):
140
+ """ Divide by scalar. """
141
+ return self * (1.0 / a)
142
+
143
+ def __itruediv__(self, a):
144
+ """ Divide by scalar, in place. """
145
+ self *= 1.0 / a
146
+ return self
147
+
148
+ # ...
149
+ def dot(self, v, out=None):
150
+ """
151
+ Applies the derivative operator on the given StencilVector.
152
+
153
+ This operation will not allocate any temporary memory
154
+ unless used in-place. (i.e. only if `v is out`)
155
+
156
+ Parameters
157
+ ----------
158
+ v : StencilVector
159
+ The input StencilVector. Has to be in the domain space.
160
+
161
+ out : StencilVector | NoneType
162
+ The output StencilVector, or None. If given, it has to be in the codomain space.
163
+
164
+ Returns
165
+ -------
166
+ out : StencilVector
167
+ Either a new allocation (if `out is None`), or a reference to the parameter `out`.
168
+ """
169
+ assert isinstance(v, StencilVector)
170
+
171
+ # setup, space checks
172
+ assert v.space is self._domain
173
+
174
+ # Check if the ghost regions are up to date
175
+ if not v.ghost_regions_in_sync:
176
+ v.update_ghost_regions()
177
+
178
+ if out is None:
179
+ out = self._codomain.zeros()
180
+
181
+ assert isinstance(out, StencilVector)
182
+ assert out.space is self._codomain
183
+
184
+ # apply the differentiation and return the result
185
+ self._do_diff(v, out)
186
+
187
+ return out
188
+
189
+ def tokronstencil(self):
190
+ """
191
+ Converts this KroneckerDerivativeOperator into a KroneckerStencilMatrix.
192
+
193
+ Returns
194
+ -------
195
+ out : KroneckerStencilMatrix
196
+ The resulting KroneckerStencilMatrix.
197
+ """
198
+ # build derivative stencil matrix (don't care for transposition here)
199
+ # hence, use spaceV and spaceW instead of domain, codomain
200
+ periodic_d = self._spaceV.periods[self._diffdir]
201
+ nc = self._spaceV.cart.domain_decomposition.ncells[self._diffdir]
202
+ p_d = self._spaceV.pads[self._diffdir]
203
+ n_d = self._spaceV.npts[self._diffdir]
204
+ m_d = self._spaceW.npts[self._diffdir]
205
+
206
+ domain_1d = DomainDecomposition([nc], [periodic_d])
207
+ cart1_1d = CartDecomposition( domain_1d, [n_d], [[0]], [[n_d-1]], [p_d], [1] )
208
+ cart2_1d = CartDecomposition( domain_1d, [m_d], [[0]], [[m_d-1]], [p_d], [1] )
209
+ V1_d = StencilVectorSpace(cart1_1d)
210
+ V2_d = StencilVectorSpace(cart2_1d)
211
+ M = StencilMatrix(V1_d, V2_d)
212
+
213
+ # handle sign already here for now...
214
+ sign = -1. if self._negative else 1.
215
+ M._data[p_d:p_d+m_d, p_d] = -1. * sign
216
+ M._data[p_d:p_d+m_d, p_d+1] = 1. * sign
217
+
218
+ # now transpose, if needed
219
+ if self._transposed:
220
+ M = M.T
221
+
222
+ # identity matrices
223
+ def make_id(i):
224
+ nc = self._spaceV.cart.domain_decomposition.ncells[i]
225
+ n_i = self._domain.npts[i]
226
+ p_i = self._domain.pads[i]
227
+ periodic_i = self._domain.periods[i]
228
+ domain_1d = DomainDecomposition([nc], [periodic_i])
229
+ cart = CartDecomposition( domain_1d, [n_i], [[0]], [[n_i-1]], [p_i], [1] )
230
+
231
+ V=StencilVectorSpace(cart)
232
+ A=StencilMatrix(V, V)
233
+ idslice = (*((slice(None),) * V.ndim), *V.pads)
234
+ A._data[idslice] = 1.
235
+
236
+ return A
237
+
238
+ # combine to Kronecker matrix
239
+ mats = [M if i == self._diffdir else make_id(i) for i in range(self._domain.ndim)]
240
+ return KroneckerStencilMatrix(self._domain, self._codomain, *mats)
241
+
242
+ def transpose(self, conjugate=False):
243
+ """
244
+ Transposes this operator. Creates and returns a new object.
245
+
246
+ Returns
247
+ -------
248
+ out : DirectionalDerivativeOperator
249
+ The transposed operator.
250
+ """
251
+ return DirectionalDerivativeOperator(self._spaceV, self._spaceW,
252
+ self._diffdir, negative=self._negative, transposed=not self._transposed)
253
+
254
+ def __neg__(self):
255
+ """
256
+ Negates this operator. Creates and returns a new object.
257
+ """
258
+ return DirectionalDerivativeOperator(self._spaceV, self._spaceW,
259
+ self._diffdir, negative=not self._negative, transposed=self._transposed)
260
+
261
+ def toarray(self, **kwargs):
262
+ """
263
+ Transforms this operator into a dense matrix.
264
+
265
+ Returns
266
+ -------
267
+ out : ndarray
268
+ The resulting matrix.
269
+ """
270
+ return self.tosparse(**kwargs).toarray()
271
+
272
+ def tosparse(self, **kwargs):
273
+
274
+ """
275
+ Transforms this operator into a sparse matrix in COO format.
276
+ Includes padding in both domain and codomain which is optional, if the domain is serial,
277
+ but mandatory if the domain is parallel.
278
+
279
+ Parameters
280
+ ----------
281
+ with_pads : Bool,optional
282
+ If true, then padding in domain and codomain direction is included. Enabled by default.
283
+
284
+ Returns
285
+ -------
286
+ out : COOMatrix
287
+ The resulting matrix.
288
+ """
289
+ # again, we do the transposition later
290
+
291
+ with_pads = kwargs.pop('with_pads', False)
292
+
293
+ # avoid this case (no pads, but parallel)
294
+ assert not (self.domain.parallel and not with_pads)
295
+
296
+ # begin with a 1×1 matrix
297
+ matrix = spa.identity(1, format='coo')
298
+ sign = -1 if self._negative else 1
299
+
300
+ # then, iterate over all dimensions
301
+ for d in range(self._spaceV.ndim):
302
+ # domain and codomain sizes...
303
+ domain_local = self._spaceV.ends[d] - self._spaceV.starts[d] + 1
304
+ codomain_local = self._spaceW.ends[d] - self._spaceW.starts[d] + 1
305
+
306
+ if with_pads:
307
+ # ... potentially with pads
308
+ domain_local += 2 * self._spaceV.pads[d]
309
+ codomain_local += 2 * self._spaceW.pads[d]
310
+
311
+ if self._diffdir == d:
312
+ # if we are at the differentiation direction, construct differentiation matrix
313
+ if codomain_local == 1 and domain_local == 1 and self.domain.periods[d]:
314
+ # case of one cell and periodic BC should be treated as a constant, thus zero matrix
315
+ directional_matrix = spa.coo_array((codomain_local, domain_local))
316
+
317
+ else:
318
+ maindiag = np.ones(domain_local) * (-sign)
319
+ adddiag = np.ones(domain_local) * sign
320
+
321
+ # handle special case with not self.domain.parallel and not with_pads and periodic
322
+ if self.domain.periods[d] and not self.domain.parallel and not with_pads:
323
+ # then: add element to other side of the array
324
+ adddiagcirc = np.array([sign])
325
+ offsets = (-codomain_local+1, 0, 1)
326
+ diags = (adddiagcirc, maindiag, adddiag)
327
+ else:
328
+ # else, just take main and off diagonal
329
+ offsets = (0,1)
330
+ diags = (maindiag, adddiag)
331
+
332
+ directional_matrix = spa.diags(diags, offsets=offsets, shape=(codomain_local, domain_local), format='coo')
333
+ else:
334
+ # avoid using padding, if possible
335
+ directional_matrix = spa.identity(domain_local)
336
+
337
+ # finally, take the Kronecker product
338
+ matrix = spa.kron(matrix, directional_matrix)
339
+
340
+ # now, we may transpose (this should be very cheap)
341
+ if self._transposed:
342
+ matrix = matrix.T
343
+
344
+ return matrix
345
+
346
+ def copy(self):
347
+ """
348
+ Create an identical copy of this operator. Creates and returns a new object.
349
+ """
350
+ return DirectionalDerivativeOperator(self._spaceV, self._spaceW,
351
+ self._diffdir, negative=self._negative, transposed=self._transposed)
352
+
353
+ #====================================================================================================
354
+ class Derivative1D(FemLinearOperator):
355
+ """
356
+ 1D derivative.
357
+
358
+ Parameters
359
+ ----------
360
+ H1 : 1D TensorFemSpace
361
+ Domain of derivative operator.
362
+
363
+ L2 : 1D TensorFemSpace
364
+ Codomain of derivative operator.
365
+
366
+ """
367
+ def __init__(self, H1, L2):
368
+
369
+ assert isinstance(H1, TensorFemSpace); assert H1.ldim == 1
370
+ assert isinstance(L2, TensorFemSpace); assert L2.ldim == 1
371
+ assert H1.periodic[0] == L2.periodic[0]
372
+ assert H1.degree[0] == L2.degree[0] + 1
373
+
374
+ # Store data in object
375
+ super().__init__(fem_domain = H1, fem_codomain = L2, linop = DirectionalDerivativeOperator(H1.coeff_space, L2.coeff_space, 0))
376
+
377
+ #====================================================================================================
378
+ class Gradient2D(FemLinearOperator):
379
+ """
380
+ Gradient operator in 2D.
381
+
382
+ Parameters
383
+ ----------
384
+ H1 : 2D TensorFemSpace
385
+ Domain of gradient operator.
386
+
387
+ Hcurl : 2D VectorFemSpace
388
+ Codomain of gradient operator.
389
+
390
+ """
391
+ def __init__(self, H1, Hcurl):
392
+
393
+ assert isinstance( H1, TensorFemSpace); assert H1.ldim == 2
394
+ assert isinstance(Hcurl, VectorFemSpace); assert Hcurl.ldim == 2
395
+
396
+ assert Hcurl.spaces[0].periodic == H1.periodic
397
+ assert Hcurl.spaces[1].periodic == H1.periodic
398
+
399
+ assert tuple(Hcurl.spaces[0].degree) == (H1.degree[0]-1, H1.degree[1] )
400
+ assert tuple(Hcurl.spaces[1].degree) == (H1.degree[0] , H1.degree[1]-1)
401
+
402
+ # Tensor-product spaces of coefficients - domain
403
+ B_B = H1.coeff_space
404
+
405
+ # Tensor-product spaces of coefficients - codomain
406
+ (M_B, B_M) = Hcurl.coeff_space.spaces
407
+
408
+ # Build Gradient matrix block by block
409
+ blocks = [[DirectionalDerivativeOperator(B_B, M_B, 0)],
410
+ [DirectionalDerivativeOperator(B_B, B_M, 1)]]
411
+ matrix = BlockLinearOperator(H1.coeff_space, Hcurl.coeff_space, blocks=blocks)
412
+
413
+ # Store data in object
414
+ super().__init__(fem_domain = H1, fem_codomain = Hcurl, linop = matrix)
415
+
416
+ #====================================================================================================
417
+ class Gradient3D(FemLinearOperator):
418
+ """
419
+ Gradient operator in 3D.
420
+
421
+ Parameters
422
+ ----------
423
+ H1 : 3D TensorFemSpace
424
+ Domain of gradient operator.
425
+
426
+ Hcurl : 3D VectorFemSpace
427
+ Codomain of gradient operator.
428
+
429
+ """
430
+ def __init__(self, H1, Hcurl):
431
+
432
+ assert isinstance( H1, TensorFemSpace); assert H1.ldim == 3
433
+ assert isinstance(Hcurl, VectorFemSpace); assert Hcurl.ldim == 3
434
+
435
+ assert Hcurl.spaces[0].periodic == H1.periodic
436
+ assert Hcurl.spaces[1].periodic == H1.periodic
437
+ assert Hcurl.spaces[2].periodic == H1.periodic
438
+
439
+ assert tuple(Hcurl.spaces[0].degree) == (H1.degree[0]-1, H1.degree[1] , H1.degree[2] )
440
+ assert tuple(Hcurl.spaces[1].degree) == (H1.degree[0] , H1.degree[1]-1, H1.degree[2] )
441
+ assert tuple(Hcurl.spaces[2].degree) == (H1.degree[0] , H1.degree[1] , H1.degree[2]-1)
442
+
443
+ # Tensor-product spaces of coefficients - domain
444
+ B_B_B = H1.coeff_space
445
+
446
+ # Tensor-product spaces of coefficients - codomain
447
+ (M_B_B, B_M_B, B_B_M) = Hcurl.coeff_space.spaces
448
+
449
+ # Build Gradient matrix block by block
450
+ blocks = [[DirectionalDerivativeOperator(B_B_B, M_B_B, 0)],
451
+ [DirectionalDerivativeOperator(B_B_B, B_M_B, 1)],
452
+ [DirectionalDerivativeOperator(B_B_B, B_B_M, 2)]]
453
+ matrix = BlockLinearOperator(H1.coeff_space, Hcurl.coeff_space, blocks=blocks)
454
+
455
+ # Store data in object
456
+ super().__init__(fem_domain = H1, fem_codomain = Hcurl, linop = matrix)
457
+
458
+ #====================================================================================================
459
+ class ScalarCurl2D(FemLinearOperator):
460
+ """
461
+ Scalar curl operator in 2D: computes a scalar field from a vector field.
462
+
463
+ Parameters
464
+ ----------
465
+ Hcurl : 2D VectorFemSpace
466
+ Domain of 2D scalar curl operator.
467
+
468
+ L2 : 2D TensorFemSpace
469
+ Codomain of 2D scalar curl operator.
470
+
471
+ """
472
+ def __init__(self, Hcurl, L2):
473
+
474
+ assert isinstance(Hcurl, VectorFemSpace); assert Hcurl.ldim == 2
475
+ assert isinstance( L2, TensorFemSpace); assert L2.ldim == 2
476
+
477
+ assert Hcurl.spaces[0].periodic == L2.periodic
478
+ assert Hcurl.spaces[1].periodic == L2.periodic
479
+
480
+ assert tuple(Hcurl.spaces[0].degree) == (L2.degree[0] , L2.degree[1]+1)
481
+ assert tuple(Hcurl.spaces[1].degree) == (L2.degree[0]+1, L2.degree[1] )
482
+
483
+ # Tensor-product spaces of coefficients - domain
484
+ (M_B, B_M) = Hcurl.coeff_space.spaces
485
+
486
+ # Tensor-product spaces of coefficients - codomain
487
+ M_M = L2.coeff_space
488
+
489
+ # Build Curl matrix block by block
490
+ blocks = [[-DirectionalDerivativeOperator(M_B, M_M, 1),
491
+ DirectionalDerivativeOperator(B_M, M_M, 0)]]
492
+ matrix = BlockLinearOperator(Hcurl.coeff_space, L2.coeff_space, blocks=blocks)
493
+
494
+ # Store data in object
495
+ super().__init__(fem_domain = Hcurl, fem_codomain = L2, linop = matrix)
496
+
497
+ #====================================================================================================
498
+ class VectorCurl2D(FemLinearOperator):
499
+ """
500
+ Vector curl operator in 2D: computes a vector field from a scalar field.
501
+ This is sometimes called the 'rot' operator.
502
+
503
+ Parameters
504
+ ----------
505
+ H1 : 2D TensorFemSpace
506
+ Domain of 2D vector curl operator.
507
+
508
+ Hdiv : 2D VectorFemSpace
509
+ Codomain of 2D vector curl operator.
510
+
511
+ """
512
+ def __init__(self, H1, Hdiv):
513
+
514
+ assert isinstance( H1, TensorFemSpace); assert H1.ldim == 2
515
+ assert isinstance(Hdiv, VectorFemSpace); assert Hdiv.ldim == 2
516
+
517
+ assert Hdiv.spaces[0].periodic == H1.periodic
518
+ assert Hdiv.spaces[1].periodic == H1.periodic
519
+
520
+ assert tuple(Hdiv.spaces[0].degree) == (H1.degree[0] , H1.degree[1]-1)
521
+ assert tuple(Hdiv.spaces[1].degree) == (H1.degree[0]-1, H1.degree[1] )
522
+
523
+ # Tensor-product spaces of coefficients - domain
524
+ B_B = H1.coeff_space
525
+
526
+ # Tensor-product spaces of coefficients - codomain
527
+ (B_M, M_B) = Hdiv.coeff_space.spaces
528
+
529
+ # Build Curl matrix block by block
530
+ blocks = [[DirectionalDerivativeOperator(B_B, B_M, 1)],
531
+ [-DirectionalDerivativeOperator(B_B, M_B, 0)]]
532
+ matrix = BlockLinearOperator(H1.coeff_space, Hdiv.coeff_space, blocks=blocks)
533
+
534
+ # Store data in object
535
+ super().__init__(fem_domain = H1, fem_codomain = Hdiv, linop = matrix)
536
+
537
+ #====================================================================================================
538
+ class Curl3D(FemLinearOperator):
539
+ """
540
+ Curl operator in 3D.
541
+
542
+ Parameters
543
+ ----------
544
+ Hcurl : 3D VectorFemSpace
545
+ Domain of 3D curl operator.
546
+
547
+ Hdiv : 3D VectorFemSpace
548
+ Codomain of 3D curl operator.
549
+
550
+ """
551
+ def __init__(self, Hcurl, Hdiv):
552
+
553
+ assert isinstance(Hcurl, VectorFemSpace); assert Hcurl.ldim == 3
554
+ assert isinstance( Hdiv, VectorFemSpace); assert Hdiv.ldim == 3
555
+
556
+ assert Hcurl.spaces[0].periodic == Hdiv.spaces[0].periodic
557
+ assert Hcurl.spaces[1].periodic == Hdiv.spaces[1].periodic
558
+ assert Hcurl.spaces[2].periodic == Hdiv.spaces[2].periodic
559
+
560
+ Hdiv0, Hdiv1, Hdiv2 = Hdiv.spaces
561
+ assert tuple(Hcurl.spaces[1].degree) == (Hdiv0.degree[0] , Hdiv0.degree[1] , Hdiv0.degree[2]+1)
562
+ assert tuple(Hcurl.spaces[2].degree) == (Hdiv0.degree[0] , Hdiv0.degree[1]+1, Hdiv0.degree[2] )
563
+ assert tuple(Hcurl.spaces[0].degree) == (Hdiv1.degree[0] , Hdiv1.degree[1] , Hdiv1.degree[2]+1)
564
+ assert tuple(Hcurl.spaces[2].degree) == (Hdiv1.degree[0]+1, Hdiv1.degree[1] , Hdiv1.degree[2] )
565
+ assert tuple(Hcurl.spaces[0].degree) == (Hdiv2.degree[0] , Hdiv2.degree[1]+1, Hdiv2.degree[2] )
566
+ assert tuple(Hcurl.spaces[1].degree) == (Hdiv2.degree[0]+1, Hdiv2.degree[1] , Hdiv2.degree[2] )
567
+
568
+ # Tensor-product spaces of coefficients - domain
569
+ (M_B_B, B_M_B, B_B_M) = Hcurl.coeff_space.spaces
570
+
571
+ # Tensor-product spaces of coefficients - codomain
572
+ (B_M_M, M_B_M, M_M_B) = Hdiv.coeff_space.spaces
573
+
574
+ # ...
575
+ # Build Curl matrix block by block
576
+ D = DirectionalDerivativeOperator
577
+ blocks = [[ None , -D(B_M_B, B_M_M, 2) , D(B_B_M, B_M_M, 1)],
578
+ [ D(M_B_B, M_B_M, 2) , None, -D(B_B_M, M_B_M, 0)],
579
+ [-D(M_B_B, M_M_B, 1) , D(B_M_B, M_M_B, 0) , None ]]
580
+
581
+ matrix = BlockLinearOperator(Hcurl.coeff_space, Hdiv.coeff_space, blocks=blocks)
582
+ # ...
583
+
584
+ # Store data in object
585
+ super().__init__(fem_domain = Hcurl, fem_codomain = Hdiv, linop = matrix)
586
+
587
+ #====================================================================================================
588
+ class Divergence2D(FemLinearOperator):
589
+ """
590
+ Divergence operator in 2D.
591
+
592
+ Parameters
593
+ ----------
594
+ Hdiv : 2D VectorFemSpace
595
+ Domain of divergence operator.
596
+
597
+ L2 : 2D TensorFemSpace
598
+ Codomain of divergence operator.
599
+
600
+ """
601
+ def __init__(self, Hdiv, L2):
602
+
603
+ assert isinstance(Hdiv, VectorFemSpace); assert Hdiv.ldim == 2
604
+ assert isinstance( L2, TensorFemSpace); assert L2.ldim == 2
605
+
606
+ assert Hdiv.spaces[0].periodic == L2.periodic
607
+ assert Hdiv.spaces[1].periodic == L2.periodic
608
+
609
+ assert tuple(Hdiv.spaces[0].degree) == (L2.degree[0]+1, L2.degree[1] )
610
+ assert tuple(Hdiv.spaces[1].degree) == (L2.degree[0] , L2.degree[1]+1)
611
+
612
+ # Tensor-product spaces of coefficients - domain
613
+ (B_M, M_B) = Hdiv.coeff_space.spaces
614
+
615
+ # Tensor-product spaces of coefficients - codomain
616
+ M_M = L2.coeff_space
617
+
618
+ # Build Divergence matrix block by block
619
+ f = KroneckerStencilMatrix
620
+ blocks = [[DirectionalDerivativeOperator(B_M, M_M, 0), DirectionalDerivativeOperator(M_B, M_M, 1)]]
621
+ matrix = BlockLinearOperator(Hdiv.coeff_space, L2.coeff_space, blocks=blocks)
622
+
623
+ # Store data in object
624
+ super().__init__(fem_domain = Hdiv, fem_codomain = L2, linop = matrix)
625
+
626
+ #====================================================================================================
627
+ class Divergence3D(FemLinearOperator):
628
+ """
629
+ Divergence operator in 3D.
630
+
631
+ Parameters
632
+ ----------
633
+ Hdiv : 3D VectorFemSpace
634
+ Domain of divergence operator.
635
+
636
+ L2 : 3D TensorFemSpace
637
+ Codomain of divergence operator.
638
+
639
+ """
640
+ def __init__(self, Hdiv, L2):
641
+
642
+ assert isinstance(Hdiv, VectorFemSpace); assert Hdiv.ldim == 3
643
+ assert isinstance( L2, TensorFemSpace); assert L2.ldim == 3
644
+
645
+ assert Hdiv.spaces[0].periodic == L2.periodic
646
+ assert Hdiv.spaces[1].periodic == L2.periodic
647
+ assert Hdiv.spaces[2].periodic == L2.periodic
648
+
649
+ assert tuple(Hdiv.spaces[0].degree) == (L2.degree[0]+1, L2.degree[1] , L2.degree[2] )
650
+ assert tuple(Hdiv.spaces[1].degree) == (L2.degree[0] , L2.degree[1]+1, L2.degree[2] )
651
+ assert tuple(Hdiv.spaces[2].degree) == (L2.degree[0] , L2.degree[1] , L2.degree[2]+1)
652
+
653
+ # Tensor-product spaces of coefficients - domain
654
+ (B_M_M, M_B_M, M_M_B) = Hdiv.coeff_space.spaces
655
+
656
+ # Tensor-product spaces of coefficients - codomain
657
+ M_M_M = L2.coeff_space
658
+
659
+ # Build Divergence matrix block by block
660
+ blocks = [[DirectionalDerivativeOperator(B_M_M, M_M_M, 0),
661
+ DirectionalDerivativeOperator(M_B_M, M_M_M, 1),
662
+ DirectionalDerivativeOperator(M_M_B, M_M_M, 2)]]
663
+ matrix = BlockLinearOperator(Hdiv.coeff_space, L2.coeff_space, blocks=blocks)
664
+
665
+ # Store data in object
666
+ super().__init__(fem_domain = Hdiv, fem_codomain = L2, linop = matrix)
667
+
668
+ #====================================================================================================
669
+ # 2D Multipatch derivative operators
670
+ #====================================================================================================
671
+ class BrokenGradient2D(FemLinearOperator):
672
+ """
673
+ Gradient operator in a 2D multipatch domain,
674
+ acting independently on each patch.
675
+ In general, the resulting field is therefore discontinuous, or "broken".
676
+
677
+ Parameters
678
+ ----------
679
+ V0h : MultipatchFemSpace
680
+ Domain of the gradient operator.
681
+
682
+ V1h : MultipatchFemSpace
683
+ Codomain of the gradient operator.
684
+ """
685
+ def __init__(self, V0h, V1h):
686
+
687
+ FemLinearOperator.__init__(self, fem_domain=V0h, fem_codomain=V1h)
688
+
689
+ D0s = [Gradient2D(V0, V1) for V0, V1 in zip(V0h.spaces, V1h.spaces)]
690
+
691
+ self._linop = BlockLinearOperator(self.linop_domain, self.linop_codomain, blocks={
692
+ (i, i): D0i.linop for i, D0i in enumerate(D0s)})
693
+
694
+ def transpose(self, conjugate=False):
695
+ # todo (MCP): define as the dual differential operator
696
+ return BrokenTransposedGradient2D(self.fem_domain, self.fem_codomain)
697
+
698
+ # ==============================================================================
699
+ class BrokenTransposedGradient2D(FemLinearOperator):
700
+ """
701
+ Transposed gradient operator in a 2D multipatch domain,
702
+ acting independently on each patch.
703
+ In general, the resulting field is therefore discontinuous, or "broken".
704
+
705
+ Parameters
706
+ ----------
707
+ V0h : MultipatchFemSpace
708
+ Codomain of the transposed gradient operator.
709
+
710
+ V1h : MultipatchFemSpace
711
+ Domain of the transposed gradient operator.
712
+ """
713
+ def __init__(self, V0h, V1h):
714
+
715
+ FemLinearOperator.__init__(self, fem_domain=V1h, fem_codomain=V0h)
716
+
717
+ D0s = [Gradient2D(V0, V1) for V0, V1 in zip(V0h.spaces, V1h.spaces)]
718
+
719
+ self._linop = BlockLinearOperator(self.linop_domain, self.linop_codomain, blocks={
720
+ (i, i): D0i.linop.T for i, D0i in enumerate(D0s)})
721
+
722
+ def transpose(self, conjugate=False):
723
+ # todo (MCP): discard
724
+ return BrokenGradient2D(self.fem_codomain, self.fem_domain)
725
+
726
+ # ==============================================================================
727
+ class BrokenScalarCurl2D(FemLinearOperator):
728
+ """
729
+ Scalar curl operator in a 2D multipatch domain,
730
+ acting independently on each patch.
731
+ In general, the resulting field is therefore discontinuous, or "broken".
732
+
733
+ Parameters
734
+ ----------
735
+ V1h : MultipatchFemSpace
736
+ Domain of the scalar curl operator.
737
+
738
+ V2h : MultipatchFemSpace
739
+ Codomain of the scalar curl operator.
740
+ """
741
+ def __init__(self, V1h, V2h):
742
+
743
+ FemLinearOperator.__init__(self, fem_domain=V1h, fem_codomain=V2h)
744
+
745
+ D1s = [ScalarCurl2D(V1, V2) for V1, V2 in zip(V1h.spaces, V2h.spaces)]
746
+
747
+ self._linop = BlockLinearOperator(self.linop_domain, self.linop_codomain, blocks={
748
+ (i, i): D1i.linop for i, D1i in enumerate(D1s)})
749
+
750
+ def transpose(self, conjugate=False):
751
+ return BrokenTransposedScalarCurl2D(
752
+ V1h=self.fem_domain, V2h=self.fem_codomain)
753
+
754
+
755
+ # ==============================================================================
756
+ class BrokenTransposedScalarCurl2D(FemLinearOperator):
757
+ """
758
+ Transposed scalar curl operator in a 2D multipatch domain,
759
+ acting independently on each patch.
760
+ In general, the resulting field is therefore discontinuous, or "broken".
761
+
762
+ Parameters
763
+ ----------
764
+ V1h : MultipatchFemSpace
765
+ Codomain of the transposed scalar curl operator.
766
+
767
+ V2h : MultipatchFemSpace
768
+ Domain of the transposed scalar curl operator.
769
+ """
770
+ def __init__(self, V1h, V2h):
771
+
772
+ FemLinearOperator.__init__(self, fem_domain=V2h, fem_codomain=V1h)
773
+
774
+ D1s = [ScalarCurl2D(V1, V2) for V1, V2 in zip(V1h.spaces, V2h.spaces)]
775
+
776
+ self._linop = BlockLinearOperator(self.linop_domain, self.linop_codomain, blocks={
777
+ (i, i): D1i.linop.T for i, D1i in enumerate(D1s)})
778
+
779
+ def transpose(self, conjugate=False):
780
+ return BrokenScalarCurl2D(V1h=self.fem_codomain, V2h=self.fem_domain)