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,1073 @@
1
+ # -*- coding: UTF-8 -*-
2
+
3
+ import numpy as np
4
+
5
+ from feectools.linalg.kron import KroneckerLinearSolver, KroneckerStencilMatrix
6
+ from feectools.linalg.stencil import StencilMatrix, StencilVectorSpace
7
+ from feectools.linalg.block import BlockLinearOperator, BlockVector
8
+ from feectools.core.bsplines import quadrature_grid
9
+ from feectools.utilities.quadratures import gauss_legendre
10
+ from feectools.fem.basic import FemField
11
+ from feectools.feec import dof_kernels
12
+
13
+ from feectools.fem.tensor import TensorFemSpace
14
+ from feectools.fem.vector import VectorFemSpace, MultipatchFemSpace
15
+
16
+ from feectools.ddm.cart import DomainDecomposition, CartDecomposition
17
+ from feectools.utilities.utils import roll_edges
18
+
19
+ from abc import ABCMeta, abstractmethod
20
+
21
+ __all__ = ('GlobalGeometricProjector', 'GlobalGeometricProjectorH1', 'GlobalGeometricProjectorHcurl', 'GlobalGeometricProjectorHdiv', 'GlobalGeometricProjectorL2',
22
+ 'MultipatchGeometricProjector',
23
+ 'evaluate_dofs_1d_0form', 'evaluate_dofs_1d_1form',
24
+ 'evaluate_dofs_2d_0form', 'evaluate_dofs_2d_1form_hcurl', 'evaluate_dofs_2d_1form_hdiv', 'evaluate_dofs_2d_2form',
25
+ 'evaluate_dofs_3d_0form', 'evaluate_dofs_3d_1form', 'evaluate_dofs_3d_2form', 'evaluate_dofs_3d_3form')
26
+
27
+ #==============================================================================
28
+ class GlobalGeometricProjector(metaclass=ABCMeta):
29
+ """
30
+ Projects callable functions to some scalar or vector FEM space.
31
+
32
+ A global projector is constructed over a tensor-product grid in the logical
33
+ domain. The vertices of this grid are obtained as the tensor product of the
34
+ 1D splines' Greville points along each direction.
35
+
36
+ This projector matches the "geometric" degrees of freedom of discrete
37
+ n-forms (where n depends on the underlying space).
38
+ This is done by projecting each component of the vector field independently,
39
+ by combining 1D histopolation with 1D interpolation.
40
+
41
+ This class cannot be instantiated directly (use a subclass instead).
42
+
43
+ Parameters
44
+ ----------
45
+ space : VectorFemSpace | TensorFemSpace
46
+ Some finite element space, codomain of the projection
47
+ operator. The exact structure where to use histopolation and where interpolation
48
+ has to be given by a subclass of the GlobalGeometricProjector class.
49
+ As of now, it is implicitly assumed for a VectorFemSpace, that for each direction
50
+ that all spaces with interpolation are the same, and all spaces with histopolation are the same
51
+ (i.e. yield the same quadrature/interpolation points etc.); so use with care on an arbitrary VectorFemSpace.
52
+ It is right now only intended to be used with VectorFemSpaces or TensorFemSpaces from DeRham complex objects.
53
+
54
+ nquads : list(int) | tuple(int)
55
+ Number of quadrature points along each direction, to be used in Gauss
56
+ quadrature rule for computing the (approximated) degrees of freedom.
57
+ This parameter is ignored, if the projector only uses interpolation (and no histopolation).
58
+ """
59
+
60
+ def __init__(self, space, nquads = None):
61
+ self._space = space
62
+ self._rhs = space.coeff_space.zeros()
63
+
64
+ if isinstance(space, TensorFemSpace):
65
+ tensorspaces = [space]
66
+ rhsblocks = [self._rhs]
67
+ elif isinstance(space, VectorFemSpace):
68
+ tensorspaces = space.spaces
69
+ rhsblocks = self._rhs.blocks
70
+ else:
71
+ # no SplineSpace support for now
72
+ raise NotImplementedError()
73
+
74
+ # from now on, we only deal with tensorspaces, i.e. a list of TensorFemSpace instances
75
+
76
+ self._dim = tensorspaces[0].ldim
77
+ assert all([self._dim == tspace.ldim for tspace in tensorspaces])
78
+
79
+ self._blockcount = len(tensorspaces)
80
+
81
+ # set up quadrature weights
82
+ if nquads:
83
+ assert len(nquads) == self._dim
84
+ uw = [gauss_legendre(k) for k in nquads]
85
+
86
+ # retrieve projection space structure
87
+ # this is a 2D Python array (first level: block, second level: tensor direction)
88
+ # which indicates where to use histopolation and where interpolation
89
+ structure = self._structure(self._dim)
90
+
91
+ # check the layout of the array, and for the existence of interpolation/histopolation...
92
+ assert len(structure) == self._blockcount
93
+
94
+ has_i = False
95
+ has_h = False
96
+ for block in structure:
97
+ assert len(block) == self._dim
98
+ for cell in block:
99
+ if cell == 'I': has_i = True
100
+ if cell == 'H': has_h = True
101
+
102
+ if has_h and nquads is None:
103
+ raise ValueError('The number of quadrature points `nquads` must be provided for performing histopolation')
104
+
105
+ # ... and activate them.
106
+ for space in tensorspaces:
107
+ if has_i: space.init_interpolation()
108
+ if has_h: space.init_histopolation()
109
+
110
+ # retrieve the restriction function
111
+ func = self._function(self._dim)
112
+
113
+ # construct arguments for the function
114
+ # TODO: verify that histopolation and interpolation behave EXACTLY the same for all blocks, including their distribution in MPI
115
+ # (this is currently not checked anywhere)
116
+ intp_x = [None] * self._dim if has_i else []
117
+ quad_x = [None] * self._dim if has_h else []
118
+ quad_w = [None] * self._dim if has_h else []
119
+ dofs = [None] * self._blockcount
120
+
121
+ # in the meanwhile, also store all grids in a canonical format
122
+ # (and fetch the interpolation/histopolation solvers)
123
+ self._grid_x = []
124
+ self._grid_w = []
125
+ solverblocks = []
126
+ matrixblocks = []
127
+ blocks = [] # for BlockLinearOperator
128
+ for i,block in enumerate(structure):
129
+ # do for each block (i.e. each TensorFemSpace):
130
+
131
+ block_x = []
132
+ block_w = []
133
+ solvercells = []
134
+ matrixcells = []
135
+ blocks += [[]]
136
+ for j, cell in enumerate(block):
137
+ # for each direction in the tensor space (i.e. each SplineSpace):
138
+
139
+ V = tensorspaces[i].spaces[j]
140
+ s = tensorspaces[i].coeff_space.starts[j]
141
+ e = tensorspaces[i].coeff_space.ends[j]
142
+ p = tensorspaces[i].coeff_space.pads[j]
143
+ n = tensorspaces[i].coeff_space.npts[j]
144
+ m = tensorspaces[i].multiplicity[j]
145
+ periodic = tensorspaces[i].coeff_space.periods[j]
146
+ ncells = tensorspaces[i].ncells[j]
147
+ blocks[-1] += [None] # fill blocks with None, fill the diagonals later
148
+
149
+ # create a distributed matrix for the current 1d SplineSpace
150
+ domain_decomp = DomainDecomposition([ncells], [periodic])
151
+ cart_decomp = CartDecomposition(domain_decomp, [n], [[s]], [[e]], [p], [m])
152
+ V_cart = StencilVectorSpace(cart_decomp)
153
+ M = StencilMatrix(V_cart, V_cart)
154
+
155
+ if cell == 'I':
156
+ # interpolation case
157
+ if intp_x[j] is None:
158
+ intp_x[j] = V.greville[s:e+1]
159
+ local_intp_x = intp_x[j]
160
+
161
+ # for the grids, make interpolation appear like quadrature
162
+ local_x = local_intp_x[:, np.newaxis]
163
+ local_w = np.ones_like(local_x)
164
+ solvercells += [V._interpolator]
165
+
166
+ # make 1D collocation matrix in stencil format
167
+ row_indices, col_indices = np.nonzero(V.imat)
168
+
169
+ for row_i, col_i in zip(row_indices, col_indices):
170
+
171
+ # only consider row indices on process
172
+ if row_i in range(V_cart.starts[0], V_cart.ends[0] + 1):
173
+ row_i_loc = row_i - s
174
+
175
+ M._data[row_i_loc + m*p, (col_i + p - row_i)%V.imat.shape[1]] = V.imat[row_i, col_i]
176
+
177
+ # check if stencil matrix was built correctly
178
+ assert np.allclose(M.toarray()[s:e + 1], V.imat[s:e + 1])
179
+ # TODO Fix toarray() for multiplicity m > 1
180
+ matrixcells += [M.copy()]
181
+
182
+ elif cell == 'H':
183
+ # histopolation case
184
+ if quad_x[j] is None:
185
+ u, w = uw[j]
186
+ global_quad_x, global_quad_w = quadrature_grid(V.histopolation_grid, u, w)
187
+
188
+ # "roll" back points to the interval to ensure that the quadrature points are
189
+ # in the domain. Only useful in the periodic case (else do nothing)
190
+ # if not used then you will have quadrature points outside of the domain which
191
+ # might cause problem when your function is only defined inside the domain.
192
+ roll_edges(V.domain, global_quad_x)
193
+ quad_x[j] = global_quad_x[s:e+1]
194
+ quad_w[j] = global_quad_w[s:e+1]
195
+
196
+ local_x, local_w = quad_x[j], quad_w[j]
197
+ solvercells += [V._histopolator]
198
+
199
+ # make 1D collocation matrix in stencil format
200
+ row_indices, col_indices = np.nonzero(V.hmat)
201
+
202
+ for row_i, col_i in zip(row_indices, col_indices):
203
+
204
+ # only consider row indices on process
205
+ if row_i in range(V_cart.starts[0], V_cart.ends[0] + 1):
206
+ row_i_loc = row_i - s
207
+
208
+ M._data[row_i_loc + m*p, (col_i + p - row_i)%V.hmat.shape[1]] = V.hmat[row_i, col_i]
209
+
210
+ # check if stencil matrix was built correctly
211
+ assert np.allclose(M.toarray()[s:e + 1], V.hmat[s:e + 1])
212
+
213
+ matrixcells += [M.copy()]
214
+
215
+ else:
216
+ raise NotImplementedError('Invalid entry in structure array.')
217
+
218
+ block_x += [local_x]
219
+ block_w += [local_w]
220
+
221
+ # build Kronecker out of single directions
222
+ if isinstance(self.space, TensorFemSpace):
223
+ matrixblocks += [KroneckerStencilMatrix(self.space.coeff_space, self.space.coeff_space, *matrixcells)]
224
+ else:
225
+ matrixblocks += [KroneckerStencilMatrix(self.space.coeff_space[i], self.space.coeff_space[i], *matrixcells)]
226
+
227
+ # fill the diagonals for BlockLinearOperator
228
+ blocks[i][i] = matrixblocks[-1]
229
+
230
+ # finish block, build solvers, get dataslice to project to
231
+ self._grid_x += [block_x]
232
+ self._grid_w += [block_w]
233
+
234
+ solverblocks += [KroneckerLinearSolver(tensorspaces[i].coeff_space, tensorspaces[i].coeff_space, solvercells)]
235
+
236
+ dataslice = tuple(slice(p*m, -p*m) for p, m in zip(tensorspaces[i].coeff_space.pads,tensorspaces[i].coeff_space.shifts))
237
+ dofs[i] = rhsblocks[i]._data[dataslice]
238
+
239
+ def create_block_diagonal(matrixblocks):
240
+ n = len(matrixblocks)
241
+ blocks = [[None if i != j else matrixblocks[i] for j in range(n)] for i in range(n)]
242
+ return blocks
243
+ # build final Inter-/Histopolation matrix (distributed)
244
+ if isinstance(self.space, TensorFemSpace):
245
+ self._imat_kronecker = matrixblocks[0]
246
+ else:
247
+ # self._imat_kronecker = BlockLinearOperator(self.space.coeff_space, self.space.coeff_space,
248
+ # blocks=blocks)
249
+ self._imat_kronecker = BlockLinearOperator(self.space.coeff_space, self.space.coeff_space,
250
+ # blocks=[[matrixblocks[0], None, None],
251
+ # [None, matrixblocks[1], None],
252
+ # [None, None, matrixblocks[2]]],
253
+ blocks=create_block_diagonal(matrixblocks),
254
+ )
255
+
256
+ # finish arguments and create a lambda
257
+ args = (*intp_x, *quad_x, *quad_w, *dofs)
258
+ self._func = lambda *fun: func(*args, *fun)
259
+
260
+ # build a BlockLinearOperator, if necessary
261
+ if len(solverblocks) == 1:
262
+ self._solver = solverblocks[0]
263
+ else:
264
+ domain = codomain = self._space.coeff_space
265
+ blocks = {(i, i): B_i for i, B_i in enumerate(solverblocks)}
266
+ self._solver = BlockLinearOperator(domain, codomain, blocks)
267
+
268
+ @property
269
+ def space(self):
270
+ """
271
+ The space to which this Projector projects.
272
+ """
273
+ return self._space
274
+
275
+ @property
276
+ def dim(self):
277
+ """
278
+ The dimension of the underlying TensorFemSpaces.
279
+ """
280
+ return self._dim
281
+
282
+ @property
283
+ def blockcount(self):
284
+ """
285
+ The number of blocks. In case that self.space is a TensorFemSpace, this is 1,
286
+ otherwise it denotes the number of blocks in the VectorFemSpace.
287
+ """
288
+ return self._blockcount
289
+
290
+ @property
291
+ def grid_x(self):
292
+ """
293
+ The local interpolation/histopolation grids which are used; it denotes the position of the interpolation/quadrature points.
294
+ All the grids are stored inside a two-dimensional array; the outer dimension denotes the block, the inner the tensor space direction.
295
+ """
296
+ return self._grid_x
297
+
298
+ @property
299
+ def grid_w(self):
300
+ """
301
+ The local interpolation/histopolation grids which are used; it denotes the weights of the quadrature points (in the case of interpolation, this will return the weight 1 for the given positions).
302
+ All the grids are stored inside a two-dimensional array; the outer dimension denotes the block, the inner the tensor space direction.
303
+ """
304
+ return self._grid_w
305
+
306
+ @property
307
+ def func(self):
308
+ """
309
+ The function which is used for projecting a given callable (or list thereof) to the DOFs in the target space.
310
+ """
311
+ return self._func
312
+
313
+ @property
314
+ def solver(self):
315
+ """
316
+ The solver used for transforming the DOFs in the target space into spline coefficients.
317
+ """
318
+ return self._solver
319
+
320
+ @property
321
+ def imat_kronecker(self):
322
+ """
323
+ Inter-/Histopolation matrix in distributed format.
324
+ """
325
+ return self._imat_kronecker
326
+
327
+ @abstractmethod
328
+ def _structure(self, dim):
329
+ """
330
+ Has to be implemented by a subclass. Returns a 2-dimensional array
331
+ which contains strings which either say 'I' or 'H', e.g.
332
+ [['H', 'I', 'I'], ['I', 'H', 'I'], ['I', 'I', 'H']] for the 3-dimensional Hcurl space.
333
+
334
+ The inner array dimension has to conform to the dim parameter,
335
+ the outer with the number of blocks of the target space.
336
+
337
+ Parameters
338
+ ----------
339
+ dim : int
340
+ The dimension of the underlying TensorFemSpaces.
341
+
342
+ Returns
343
+ -------
344
+ structure : array
345
+ The described structure matrix.
346
+ """
347
+ pass
348
+
349
+ @abstractmethod
350
+ def _function(self, dim):
351
+ """
352
+ Has to be implemented by a subclass. Returns a function which accepts the arguments
353
+ in the order (*intp_x, *quad_x, *quad_w, *dofs, *f) (see __init__ function) and then
354
+ evaluates a given callable using these arguments. Note that the dofs array is modified by the function.
355
+
356
+ (this can and most likely will be replaced, if we construct the functions somewhere else, e.g. with code generation)
357
+
358
+ Parameters
359
+ ----------
360
+ dim : int
361
+ The dimension of the underlying TensorFemSpaces.
362
+
363
+ Returns
364
+ -------
365
+ func : callable
366
+ The described function.
367
+ """
368
+ pass
369
+
370
+ def __call__(self, fun, dofs_only = False):
371
+ r"""
372
+ Project vector function onto the given finite element
373
+ space by the instance of this class. This happens in the logical domain $\hat{\Omega}$.
374
+
375
+ Parameters
376
+ ----------
377
+ fun : callable or list/tuple of callables
378
+ Scalar components of the real- or complex-valued vector function to be
379
+ projected, with arguments the coordinates (x_1, ..., x_N) of a
380
+ point in the logical domain.
381
+
382
+ $fun_i : \hat{\Omega} \mapsto \mathbb{R}$ with i = 1, ..., N.
383
+
384
+ dofs_only : bool
385
+ Whether to just compute and return the DOFs
386
+ (i.e. no inversion of the inter-/histopolation matrix needed to get the FEM coefficiens)
387
+
388
+ Returns
389
+ -------
390
+ field : FemField
391
+ Field obtained by projection (element of the target space-conforming
392
+ finite element space). This is also a real- or complex-valued scalar/vector function
393
+ in the logical domain.
394
+ """
395
+ # build the rhs (degrees of freedom - DOFs)
396
+ if self._blockcount > 1 or isinstance(fun, list) or isinstance(fun, tuple):
397
+ # (we also support 1-tuples as argument for scalar spaces)
398
+ assert self._blockcount == len(fun)
399
+ self._func(*fun)
400
+ else:
401
+ self._func(fun)
402
+ if dofs_only:
403
+ return self._rhs.copy()
404
+ else:
405
+ # solver for FEM coefficients
406
+ coeffs = self._solver.dot(self._rhs)
407
+
408
+ return FemField(self._space, coeffs=coeffs)
409
+
410
+ #==============================================================================
411
+ # SINGLEPATCH PROJECTORS
412
+ #==============================================================================
413
+ class GlobalGeometricProjectorH1(GlobalGeometricProjector):
414
+ """
415
+ Projector from H1 to an H1-conforming finite element space (i.e. a finite
416
+ dimensional subspace of H1) constructed with tensor-product B-splines in 1,
417
+ 2 or 3 dimensions.
418
+
419
+ This is a global projector based on interpolation over a tensor-product
420
+ grid in the logical domain. The interpolation grid is the tensor product of
421
+ the 1D splines' Greville points along each direction.
422
+
423
+ Parameters
424
+ ----------
425
+ H1 : SplineSpace or TensorFemSpace
426
+ H1-conforming finite element space, codomain of the projection operator
427
+ """
428
+ def _structure(self, dim):
429
+ return [['I'] * dim]
430
+
431
+ def _function(self, dim):
432
+ if dim == 1: return evaluate_dofs_1d_0form
433
+ elif dim == 2: return evaluate_dofs_2d_0form
434
+ elif dim == 3: return evaluate_dofs_3d_0form
435
+ else:
436
+ raise ValueError('H1 projector of dimension {} not available'.format(dim))
437
+
438
+ #--------------------------------------------------------------------------
439
+ def __call__(self, fun, dofs_only = False):
440
+ r"""
441
+ Project scalar function onto the H1-conforming finite element space.
442
+ This happens in the logical domain $\hat{\Omega}$.
443
+
444
+ Parameters
445
+ ----------
446
+ fun : callable
447
+ Real- or complex-valued scalar function to be projected, with arguments the
448
+ coordinates (x_1, ..., x_N) of a point in the logical domain. This
449
+ corresponds to the coefficient of a 0-form.
450
+
451
+ $fun : \hat{\Omega} \mapsto \mathbb{R}$.
452
+
453
+ Returns
454
+ -------
455
+ field : FemField
456
+ Field obtained by projection (element of the H1-conforming finite
457
+ element space). This is also a real- or complex-valued scalar function in the
458
+ logical domain.
459
+ """
460
+ return super().__call__(fun, dofs_only = dofs_only)
461
+
462
+ #==============================================================================
463
+ class GlobalGeometricProjectorHcurl(GlobalGeometricProjector):
464
+ """
465
+ Projector from H(curl) to an H(curl)-conforming finite element space, i.e.
466
+ a finite dimensional subspace of H(curl), constructed with tensor-product
467
+ B- and M-splines in 2 or 3 dimensions.
468
+
469
+ This is a global projector constructed over a tensor-product grid in the
470
+ logical domain. The vertices of this grid are obtained as the tensor
471
+ product of the 1D splines' Greville points along each direction.
472
+
473
+ The H(curl) projector matches the "geometric" degrees of freedom of
474
+ discrete 1-forms, which are the line integrals of a vector field along cell
475
+ edges. To achieve this, each component of the vector field is projected
476
+ independently, by combining 1D histopolation along the direction of the
477
+ edges with 1D interpolation along the other directions.
478
+
479
+ Parameters
480
+ ----------
481
+ Hcurl : VectorFemSpace
482
+ H(curl)-conforming finite element space, codomain of the projection
483
+ operator.
484
+
485
+ nquads : list(int) | tuple(int)
486
+ Number of quadrature points along each direction, to be used in Gauss
487
+ quadrature rule for computing the (approximated) degrees of freedom.
488
+ """
489
+ def _structure(self, dim):
490
+ if dim == 3:
491
+ return [
492
+ ['H', 'I', 'I'],
493
+ ['I', 'H', 'I'],
494
+ ['I', 'I', 'H']
495
+ ]
496
+ elif dim == 2:
497
+ return [
498
+ ['H', 'I'],
499
+ ['I', 'H']
500
+ ]
501
+ else:
502
+ raise NotImplementedError('The Hcurl projector is only available in 2D or 3D.')
503
+
504
+ def _function(self, dim):
505
+ if dim == 3: return evaluate_dofs_3d_1form
506
+ elif dim == 2: return evaluate_dofs_2d_1form_hcurl
507
+ else:
508
+ raise NotImplementedError('The Hcurl projector is only available in 2D or 3D.')
509
+
510
+ #--------------------------------------------------------------------------
511
+ def __call__(self, fun, dofs_only = False):
512
+ r"""
513
+ Project vector function onto the H(curl)-conforming finite element
514
+ space. This happens in the logical domain $\hat{\Omega}$.
515
+
516
+ Parameters
517
+ ----------
518
+ fun : list/tuple of callables
519
+ Scalar components of the real- or complex-valued vector function to be
520
+ projected, with arguments the coordinates (x_1, ..., x_N) of a
521
+ point in the logical domain. These correspond to the coefficients
522
+ of a 1-form in the canonical basis (dx_1, ..., dx_N).
523
+
524
+ $fun_i : \hat{\Omega} \mapsto \mathbb{R}$ with i = 1, ..., N.
525
+
526
+ Returns
527
+ -------
528
+ field : FemField
529
+ Field obtained by projection (element of the H(curl)-conforming
530
+ finite element space). This is also a real- or complex-valued vector function
531
+ in the logical domain.
532
+ """
533
+ return super().__call__(fun, dofs_only = dofs_only)
534
+
535
+ #==============================================================================
536
+ class GlobalGeometricProjectorHdiv(GlobalGeometricProjector):
537
+ """
538
+ Projector from H(div) to an H(div)-conforming finite element space, i.e. a
539
+ finite dimensional subspace of H(div), constructed with tensor-product
540
+ B- and M-splines in 2 or 3 dimensions.
541
+
542
+ This is a global projector constructed over a tensor-product grid in the
543
+ logical domain. The vertices of this grid are obtained as the tensor
544
+ product of the 1D splines' Greville points along each direction.
545
+
546
+ The H(div) projector matches the "geometric" degrees of freedom of discrete
547
+ (N-1)-forms in N dimensions, which are the integrated flux of a vector
548
+ field through cell faces (in 3D) or cell edges (in 2D).
549
+
550
+ To achieve this, each component of the vector field is projected
551
+ independently, by combining histopolation along the direction(s) tangential
552
+ to the face (in 3D) or edge (in 2D), with 1D interpolation along the normal
553
+ direction.
554
+
555
+ Parameters
556
+ ----------
557
+ Hdiv : VectorFemSpace
558
+ H(div)-conforming finite element space, codomain of the projection
559
+ operator.
560
+
561
+ nquads : list(int) | tuple(int)
562
+ Number of quadrature points along each direction, to be used in Gauss
563
+ quadrature rule for computing the (approximated) degrees of freedom.
564
+ """
565
+ def _structure(self, dim):
566
+ if dim == 3:
567
+ return [
568
+ ['I', 'H', 'H'],
569
+ ['H', 'I', 'H'],
570
+ ['H', 'H', 'I']
571
+ ]
572
+ elif dim == 2:
573
+ return [
574
+ ['I', 'H'],
575
+ ['H', 'I']
576
+ ]
577
+ else:
578
+ raise NotImplementedError('The Hdiv projector is only available in 2D or 3D.')
579
+
580
+ def _function(self, dim):
581
+ if dim == 3: return evaluate_dofs_3d_2form
582
+ elif dim == 2: return evaluate_dofs_2d_1form_hdiv
583
+ else:
584
+ raise NotImplementedError('The Hdiv projector is only available in 2D or 3D.')
585
+
586
+ #--------------------------------------------------------------------------
587
+ def __call__(self, fun, dofs_only = False):
588
+ r"""
589
+ Project vector function onto the H(div)-conforming finite element
590
+ space. This happens in the logical domain $\hat{\Omega}$.
591
+
592
+ Parameters
593
+ ----------
594
+ fun : list/tuples of callable
595
+ Scalar components of the real- or complex-valued vector function to be
596
+ projected, with arguments the coordinates (x_1, ..., x_N) of a
597
+ point in the logical domain. In 3D these correspond to the
598
+ coefficients of a 2-form in the canonical basis (dx_1 ∧ dx_2,
599
+ dx_2 ∧ dx_3, dx_3 ∧ dx_1).
600
+
601
+ $fun_i : \hat{\Omega} \mapsto \mathbb{R}$ with i = 1, ..., N.
602
+
603
+ Returns
604
+ -------
605
+ field : FemField
606
+ Field obtained by projection (element of the H(div)-conforming
607
+ finite element space). This is also a real- or complex-valued vector function
608
+ in the logical domain.
609
+ """
610
+ return super().__call__(fun, dofs_only = dofs_only)
611
+
612
+ #==============================================================================
613
+ class GlobalGeometricProjectorL2(GlobalGeometricProjector):
614
+ """
615
+ Projector from L2 to an L2-conforming finite element space (i.e. a finite
616
+ dimensional subspace of L2) constructed with tensor-product M-splines in 1,
617
+ 2 or 3 dimensions.
618
+
619
+ This is a global projector constructed over a tensor-product grid in the
620
+ logical domain. The vertices of this grid are obtained as the tensor
621
+ product of the 1D splines' Greville points along each direction.
622
+
623
+ The L2 projector matches the "geometric" degrees of freedom of discrete
624
+ N-forms in N dimensions, which are line/surface/volume integrals of a
625
+ scalar field over an edge/face/cell in 1/2/3 dimension(s). To this end
626
+ histopolation is used along each direction.
627
+
628
+ Parameters
629
+ ----------
630
+ L2 : SplineSpace
631
+ L2-conforming finite element space, codomain of the projection operator
632
+
633
+ nquads : list(int) | tuple(int)
634
+ Number of quadrature points along each direction, to be used in Gauss
635
+ quadrature rule for computing the (approximated) degrees of freedom.
636
+ """
637
+ def _structure(self, dim):
638
+ return [['H'] * dim]
639
+
640
+ def _function(self, dim):
641
+ if dim == 1: return evaluate_dofs_1d_1form
642
+ elif dim == 2: return evaluate_dofs_2d_2form
643
+ elif dim == 3: return evaluate_dofs_3d_3form
644
+ else:
645
+ raise ValueError('L2 projector of dimension {} not available'.format(dim))
646
+
647
+ #--------------------------------------------------------------------------
648
+ def __call__(self, fun, dofs_only = False):
649
+ r"""
650
+ Project scalar function onto the L2-conforming finite element space.
651
+ This happens in the logical domain $\hat{\Omega}$.
652
+
653
+ Parameters
654
+ ----------
655
+ fun : callable
656
+ Real- or complex-valued scalar function to be projected, with arguments the
657
+ coordinates (x_1, ..., x_N) of a point in the logical domain. This
658
+ corresponds to the coefficient of an N-form in N dimensions, in
659
+ the canonical basis dx_1 ∧ ... ∧ dx_N.
660
+
661
+ $fun : \hat{\Omega} \mapsto \mathbb{R}$.
662
+
663
+ Returns
664
+ -------
665
+ field : FemField
666
+ Field obtained by projection (element of the L2-conforming finite
667
+ element space). This is also a real- or complex-valued scalar function in the
668
+ logical domain.
669
+ """
670
+ return super().__call__(fun, dofs_only = dofs_only)
671
+
672
+ #==============================================================================
673
+ class GlobalGeometricProjectorH1vec(GlobalGeometricProjector):
674
+ """
675
+ Projector from H1^3 = H1 x H1 x H1 to a conforming finite element space, i.e.
676
+ a finite dimensional subspace of H1^3, constructed with tensor-product
677
+ B-splines in 2 or 3 dimensions.
678
+ This is a global projector constructed over a tensor-product grid in the
679
+ logical domain. The vertices of this grid are obtained as the tensor
680
+ product of the 1D splines' Greville points along each direction.
681
+
682
+ Parameters
683
+ ----------
684
+ H1vec : VectorFemSpace
685
+ H1 x H1 x H1-conforming finite element space, codomain of the projection
686
+ operator.
687
+
688
+ nquads : list(int) | tuple(int)
689
+ Number of quadrature points along each direction, to be used in Gauss
690
+ quadrature rule for computing the (approximated) degrees of freedom.
691
+ """
692
+ def _structure(self, dim):
693
+ if dim == 3:
694
+ return [
695
+ ['I', 'I', 'I'],
696
+ ['I', 'I', 'I'],
697
+ ['I', 'I', 'I']
698
+ ]
699
+ elif dim == 2:
700
+ return [
701
+ ['I', 'I'],
702
+ ['I', 'I']
703
+ ]
704
+ else:
705
+ raise NotImplementedError('The H1vec projector is only available in 2D or 3D.')
706
+
707
+ def _function(self, dim):
708
+ if dim == 3: return evaluate_dofs_3d_vec
709
+ elif dim == 2: return evaluate_dofs_2d_vec
710
+ else:
711
+ raise NotImplementedError('The H1vec projector is only available in 2/3D.')
712
+
713
+ #--------------------------------------------------------------------------
714
+ def __call__(self, fun, dofs_only = False):
715
+ r"""
716
+ Project vector function onto the H1 x H1 x H1-conforming finite element
717
+ space. This happens in the logical domain $\hat{\Omega}$.
718
+
719
+ Parameters
720
+ ----------
721
+ fun : list/tuple of callables
722
+ Scalar components of the real-valued vector function to be
723
+ projected, with arguments the coordinates (x_1, ..., x_N) of a
724
+ point in the logical domain. These correspond to the coefficients
725
+ of a vector-field.
726
+ $fun_i : \hat{\Omega} \mapsto \mathbb{R}$ with i = 1, ..., N.
727
+
728
+ Returns
729
+ -------
730
+ field : FemField
731
+ Field obtained by projection (element of the H1^3-conforming
732
+ finite element space). This is also a real-valued vector function
733
+ in the logical domain.
734
+ """
735
+ return super().__call__(fun, dofs_only = dofs_only)
736
+
737
+ #==============================================================================
738
+ # MULTIPATCH PROJECTORS (2D)
739
+ #==============================================================================
740
+ class MultipatchGeometricProjector:
741
+ """
742
+ Global Geometric Projector base class for multipatch domains.
743
+
744
+ Parameters
745
+ ----------
746
+ space : MultipatchFemSpace
747
+ Multipatch finite element space, codomain of the projection operator.
748
+ Projector : type[GlobalGeometricProjector]
749
+ Class of the projector to instantiate for each patch.
750
+ nquads : Iterable[int]
751
+ Number of quadrature points per cell along each direction.
752
+ This is a parameter passed to the constructor of Projector.
753
+ """
754
+
755
+ def __init__(self, space, Projector, nquads=None):
756
+ assert isinstance(space, MultipatchFemSpace)
757
+ assert isinstance(Projector, type)
758
+ assert issubclass(Projector, GlobalGeometricProjector)
759
+
760
+ self._Vh = Vh = space
761
+ self._Ps = [Projector(V, nquads=nquads) for V in Vh.spaces]
762
+
763
+ def __call__(self, funs):
764
+ """
765
+ project a list of functions given in the logical domain
766
+ """
767
+ us = [P(fun) for P, fun, in zip(self._Ps, funs)]
768
+
769
+ u_c = BlockVector(self._Vh.coeff_space, blocks=[uj.coeffs for uj in us])
770
+
771
+ return FemField(self._Vh, coeffs=u_c)
772
+
773
+ #==============================================================================
774
+ # 1D DEGREES OF FREEDOM
775
+ #==============================================================================
776
+
777
+ def evaluate_dofs_1d_0form(
778
+ intp_x1, # interpolation points
779
+ F, # array of degrees of freedom (intent out)
780
+ f, # input scalar function (callable)
781
+ ):
782
+
783
+ # evaluate input functions at interpolation points (make sure that points are in [0, 1])
784
+ assert np.all(np.logical_and(intp_x1 >= 0., intp_x1 <= 1.))
785
+
786
+ E1, = np.meshgrid(intp_x1, indexing='ij')
787
+ f_pts = f(E1)
788
+
789
+ F_temp = np.zeros_like(F, order='C')
790
+
791
+ dof_kernels.evaluate_dofs_1d_0form(F_temp, f_pts)
792
+
793
+ F[:] = F_temp
794
+
795
+ #------------------------------------------------------------------------------
796
+ def evaluate_dofs_1d_1form(
797
+ quad_x1, # quadrature points
798
+ quad_w1, # quadrature weights
799
+ F, # array of degrees of freedom (intent out)
800
+ f, # input scalar function (callable)
801
+ ):
802
+
803
+ # evaluate input functions at quadrature points (make sure that points are in [0, 1])
804
+ E1, = np.meshgrid(quad_x1.flatten()%1., indexing='ij')
805
+ f_pts = f(E1)
806
+
807
+ # call kernel
808
+ F_temp = np.zeros_like(F, order='C')
809
+
810
+ dof_kernels.evaluate_dofs_1d_1form(quad_w1, F_temp, f_pts)
811
+
812
+ F[:] = F_temp
813
+
814
+ #==============================================================================
815
+ # 2D DEGREES OF FREEDOM
816
+ #==============================================================================
817
+
818
+ def evaluate_dofs_2d_0form(
819
+ intp_x1, intp_x2, # interpolation points
820
+ F, # array of degrees of freedom (intent out)
821
+ f, # input scalar function (callable)
822
+ ):
823
+
824
+ # evaluate input functions at interpolation points (make sure that points are in [0, 1])
825
+ assert np.all(np.logical_and(intp_x1 >= 0., intp_x1 <= 1.))
826
+ assert np.all(np.logical_and(intp_x2 >= 0., intp_x2 <= 1.))
827
+
828
+ E1, E2 = np.meshgrid(intp_x1, intp_x2, indexing='ij')
829
+ f_pts = f(E1, E2)
830
+
831
+ F_temp = np.zeros_like(F, order='C')
832
+
833
+ dof_kernels.evaluate_dofs_2d_0form(F_temp, f_pts)
834
+
835
+ F[:, :] = F_temp
836
+
837
+ #------------------------------------------------------------------------------
838
+ def evaluate_dofs_2d_1form_hcurl(
839
+ intp_x1, intp_x2, # interpolation points
840
+ quad_x1, quad_x2, # quadrature points
841
+ quad_w1, quad_w2, # quadrature weights
842
+ F1, F2, # arrays of degrees of freedom (intent out)
843
+ f1, f2, # input scalar functions (callable)
844
+ ):
845
+
846
+ # evaluate input functions at quadrature/interpolation points (make sure that points are in [0, 1])
847
+ assert np.all(np.logical_and(intp_x1 >= 0., intp_x1 <= 1.))
848
+ assert np.all(np.logical_and(intp_x2 >= 0., intp_x2 <= 1.))
849
+
850
+ E1, E2 = np.meshgrid(quad_x1.flatten()%1., intp_x2, indexing='ij')
851
+ f1_pts = f1(E1, E2)
852
+
853
+ E1, E2 = np.meshgrid(intp_x1, quad_x2.flatten()%1., indexing='ij')
854
+ f2_pts = f2(E1, E2)
855
+
856
+ # call kernel
857
+ F1_temp = np.zeros_like(F1, order='C')
858
+ F2_temp = np.zeros_like(F2, order='C')
859
+
860
+ dof_kernels.evaluate_dofs_2d_1form_hcurl(quad_w1, quad_w2, F1_temp, F2_temp, f1_pts, f2_pts)
861
+
862
+ F1[:, :] = F1_temp
863
+ F2[:, :] = F2_temp
864
+
865
+ #------------------------------------------------------------------------------
866
+ def evaluate_dofs_2d_1form_hdiv(
867
+ intp_x1, intp_x2, # interpolation points
868
+ quad_x1, quad_x2, # quadrature points
869
+ quad_w1, quad_w2, # quadrature weights
870
+ F1, F2, # arrays of degrees of freedom (intent out)
871
+ f1, f2, # input scalar functions (callable)
872
+ ):
873
+
874
+ # evaluate input functions at quadrature/interpolation points (make sure that points are in [0, 1])
875
+ assert np.all(np.logical_and(intp_x1 >= 0., intp_x1 <= 1.))
876
+ assert np.all(np.logical_and(intp_x2 >= 0., intp_x2 <= 1.))
877
+
878
+ E1, E2 = np.meshgrid(intp_x1, quad_x2.flatten()%1., indexing='ij')
879
+ f1_pts = f1(E1, E2)
880
+
881
+ E1, E2 = np.meshgrid(quad_x1.flatten()%1., intp_x2, indexing='ij')
882
+ f2_pts = f2(E1, E2)
883
+
884
+ # call kernel
885
+ F1_temp = np.zeros_like(F1, order='C')
886
+ F2_temp = np.zeros_like(F2, order='C')
887
+
888
+ dof_kernels.evaluate_dofs_2d_1form_hdiv(quad_w1, quad_w2, F1_temp, F2_temp, f1_pts, f2_pts)
889
+
890
+ F1[:, :] = F1_temp
891
+ F2[:, :] = F2_temp
892
+
893
+ #------------------------------------------------------------------------------
894
+ def evaluate_dofs_2d_2form(
895
+ quad_x1, quad_x2, # quadrature points
896
+ quad_w1, quad_w2, # quadrature weights
897
+ F, # array of degrees of freedom (intent out)
898
+ f, # input scalar function (callable)
899
+ ):
900
+
901
+ # evaluate input functions at quadrature points (make sure that points are in [0, 1])
902
+ E1, E2 = np.meshgrid(quad_x1.flatten()%1., quad_x2.flatten()%1., indexing='ij')
903
+ f_pts = f(E1, E2)
904
+
905
+ # call kernel
906
+ F_temp = np.zeros_like(F, order='C')
907
+
908
+ dof_kernels.evaluate_dofs_2d_2form(quad_w1, quad_w2, F_temp, f_pts)
909
+
910
+ F[:, :] = F_temp
911
+
912
+ #------------------------------------------------------------------------------
913
+ def evaluate_dofs_2d_vec(
914
+ intp_x1, intp_x2, # interpolation points
915
+ F1, F2, # array of degrees of freedom (intent out)
916
+ f1, f2, # input scalar function (callable)
917
+ ):
918
+
919
+ # evaluate input functions at interpolation points (make sure that points are in [0, 1])
920
+ assert np.all(np.logical_and(intp_x1 >= 0., intp_x1 <= 1.))
921
+ assert np.all(np.logical_and(intp_x2 >= 0., intp_x2 <= 1.))
922
+
923
+ E1, E2 = np.meshgrid(intp_x1, intp_x2, indexing='ij')
924
+ f1_pts = f1(E1, E2)
925
+ f2_pts = f2(E1, E2)
926
+
927
+ # call kernel
928
+ F1_temp = np.zeros_like(F1, order='C')
929
+ F2_temp = np.zeros_like(F2, order='C')
930
+
931
+ dof_kernels.evaluate_dofs_2d_vec(F1_temp, F2_temp, f1_pts, f2_pts)
932
+
933
+ F1[:, :] = F1_temp
934
+ F2[:, :] = F2_temp
935
+
936
+ #==============================================================================
937
+ # 3D DEGREES OF FREEDOM
938
+ #==============================================================================
939
+
940
+ def evaluate_dofs_3d_0form(
941
+ intp_x1, intp_x2, intp_x3, # interpolation points
942
+ F, # array of degrees of freedom (intent out)
943
+ f, # input scalar function (callable)
944
+ ):
945
+
946
+ # evaluate input functions at interpolation points (make sure that points are in [0, 1])
947
+ assert np.all(np.logical_and(intp_x1 >= 0., intp_x1 <= 1.))
948
+ assert np.all(np.logical_and(intp_x2 >= 0., intp_x2 <= 1.))
949
+ assert np.all(np.logical_and(intp_x3 >= 0., intp_x3 <= 1.))
950
+
951
+ E1, E2, E3 = np.meshgrid(intp_x1, intp_x2, intp_x3, indexing='ij')
952
+ f_pts = f(E1, E2, E3)
953
+
954
+ F_temp = np.zeros_like(F, order='C')
955
+
956
+ dof_kernels.evaluate_dofs_3d_0form(F_temp, f_pts)
957
+
958
+ F[:, :, :] = F_temp
959
+
960
+ #------------------------------------------------------------------------------
961
+ def evaluate_dofs_3d_1form(
962
+ intp_x1, intp_x2, intp_x3, # interpolation points
963
+ quad_x1, quad_x2, quad_x3, # quadrature points
964
+ quad_w1, quad_w2, quad_w3, # quadrature weights
965
+ F1, F2, F3, # arrays of degrees of freedom (intent out)
966
+ f1, f2, f3 # input scalar functions (callable)
967
+ ):
968
+
969
+ # evaluate input functions at quadrature/interpolation points (make sure that points are in [0, 1])
970
+ assert np.all(np.logical_and(intp_x1 >= 0., intp_x1 <= 1.))
971
+ assert np.all(np.logical_and(intp_x2 >= 0., intp_x2 <= 1.))
972
+ assert np.all(np.logical_and(intp_x3 >= 0., intp_x3 <= 1.))
973
+
974
+ E1, E2, E3 = np.meshgrid(quad_x1.flatten()%1., intp_x2, intp_x3, indexing='ij')
975
+ f1_pts = f1(E1, E2, E3)
976
+
977
+ E1, E2, E3 = np.meshgrid(intp_x1, quad_x2.flatten()%1., intp_x3, indexing='ij')
978
+ f2_pts = f2(E1, E2, E3)
979
+
980
+ E1, E2, E3 = np.meshgrid(intp_x1, intp_x2, quad_x3.flatten()%1., indexing='ij')
981
+ f3_pts = f3(E1, E2, E3)
982
+
983
+ # call kernel
984
+ F1_temp = np.zeros_like(F1, order='C')
985
+ F2_temp = np.zeros_like(F2, order='C')
986
+ F3_temp = np.zeros_like(F3, order='C')
987
+
988
+ dof_kernels.evaluate_dofs_3d_1form(quad_w1, quad_w2, quad_w3, F1_temp, F2_temp, F3_temp, f1_pts, f2_pts, f3_pts)
989
+
990
+ F1[:, :, :] = F1_temp
991
+ F2[:, :, :] = F2_temp
992
+ F3[:, :, :] = F3_temp
993
+
994
+ #------------------------------------------------------------------------------
995
+ def evaluate_dofs_3d_2form(
996
+ intp_x1, intp_x2, intp_x3, # interpolation points
997
+ quad_x1, quad_x2, quad_x3, # quadrature points
998
+ quad_w1, quad_w2, quad_w3, # quadrature weights
999
+ F1, F2, F3, # arrays of degrees of freedom (intent out)
1000
+ f1, f2, f3 # input scalar functions (callable)
1001
+ ):
1002
+
1003
+ # evaluate input functions at quadrature/interpolation points (make sure that points are in [0, 1])
1004
+ assert np.all(np.logical_and(intp_x1 >= 0., intp_x1 <= 1.))
1005
+ assert np.all(np.logical_and(intp_x2 >= 0., intp_x2 <= 1.))
1006
+ assert np.all(np.logical_and(intp_x3 >= 0., intp_x3 <= 1.))
1007
+
1008
+ E1, E2, E3 = np.meshgrid(intp_x1, quad_x2.flatten()%1., quad_x3.flatten()%1., indexing='ij')
1009
+ f1_pts = f1(E1, E2, E3)
1010
+
1011
+ E1, E2, E3 = np.meshgrid(quad_x1.flatten()%1., intp_x2, quad_x3.flatten()%1., indexing='ij')
1012
+ f2_pts = f2(E1, E2, E3)
1013
+
1014
+ E1, E2, E3 = np.meshgrid(quad_x1.flatten()%1., quad_x2.flatten()%1., intp_x3, indexing='ij')
1015
+ f3_pts = f3(E1, E2, E3)
1016
+
1017
+ # call kernel
1018
+ F1_temp = np.zeros_like(F1, order='C')
1019
+ F2_temp = np.zeros_like(F2, order='C')
1020
+ F3_temp = np.zeros_like(F3, order='C')
1021
+
1022
+ dof_kernels.evaluate_dofs_3d_2form(quad_w1, quad_w2, quad_w3, F1_temp, F2_temp, F3_temp, f1_pts, f2_pts, f3_pts)
1023
+
1024
+ F1[:, :, :] = F1_temp
1025
+ F2[:, :, :] = F2_temp
1026
+ F3[:, :, :] = F3_temp
1027
+
1028
+ #------------------------------------------------------------------------------
1029
+ def evaluate_dofs_3d_3form(
1030
+ quad_x1, quad_x2, quad_x3, # quadrature points
1031
+ quad_w1, quad_w2, quad_w3, # quadrature weights
1032
+ F, # array of degrees of freedom (intent out)
1033
+ f, # input scalar function (callable)
1034
+ ):
1035
+
1036
+ # evaluate input functions at quadrature points (make sure that points are in [0, 1])
1037
+ E1, E2, E3 = np.meshgrid(quad_x1.flatten()%1., quad_x2.flatten()%1., quad_x3.flatten()%1., indexing='ij')
1038
+ f_pts = f(E1, E2, E3)
1039
+
1040
+ # call kernel
1041
+ F_temp = np.zeros_like(F, order='C')
1042
+
1043
+ dof_kernels.evaluate_dofs_3d_3form(quad_w1, quad_w2, quad_w3, F_temp, f_pts)
1044
+
1045
+ F[:, :, :] = F_temp
1046
+
1047
+ #------------------------------------------------------------------------------
1048
+ def evaluate_dofs_3d_vec(
1049
+ intp_x1, intp_x2, intp_x3, # interpolation points
1050
+ F1, F2, F3, # array of degrees of freedom (intent out)
1051
+ f1, f2, f3, # input scalar function (callable)
1052
+ ):
1053
+
1054
+ # evaluate input functions at interpolation points (make sure that points are in [0, 1])
1055
+ assert np.all(np.logical_and(intp_x1 >= 0., intp_x1 <= 1.))
1056
+ assert np.all(np.logical_and(intp_x2 >= 0., intp_x2 <= 1.))
1057
+ assert np.all(np.logical_and(intp_x3 >= 0., intp_x3 <= 1.))
1058
+
1059
+ E1, E2, E3 = np.meshgrid(intp_x1, intp_x2, intp_x3, indexing='ij')
1060
+ f1_pts = f1(E1, E2, E3)
1061
+ f2_pts = f2(E1, E2, E3)
1062
+ f3_pts = f3(E1, E2, E3)
1063
+
1064
+ # call kernel
1065
+ F1_temp = np.zeros_like(F1, order='C')
1066
+ F2_temp = np.zeros_like(F2, order='C')
1067
+ F3_temp = np.zeros_like(F3, order='C')
1068
+
1069
+ dof_kernels.evaluate_dofs_3d_vec(F1_temp, F2_temp, F3_temp, f1_pts, f2_pts, f3_pts)
1070
+
1071
+ F1[:, :, :] = F1_temp
1072
+ F2[:, :, :] = F2_temp
1073
+ F3[:, :, :] = F3_temp