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,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)
|