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
feectools/fem/grid.py ADDED
@@ -0,0 +1,181 @@
1
+ # coding: utf-8
2
+ #
3
+ # Copyright 2018 Yaman Güçlü
4
+
5
+ import numpy as np
6
+
7
+ from feectools.core.bsplines import elements_spans
8
+ from feectools.core.bsplines import quadrature_grid
9
+ from feectools.core.bsplines import basis_ders_on_quad_grid
10
+ from feectools.core.bsplines import elevate_knots
11
+ from feectools.utilities.quadratures import gauss_legendre
12
+ from feectools.fem.splines import SplineSpace
13
+
14
+ __all__ = ('FemAssemblyGrid',)
15
+
16
+ #==============================================================================
17
+ class FemAssemblyGrid:
18
+ """
19
+ Class that collects all 1D information local to process that are necessary
20
+ for the correct assembly of l.h.s. matrix and r.h.s. vector in a finite
21
+ element method.
22
+
23
+ This works in the case of clamped and periodic splines, for a global or
24
+ distributed domain.
25
+
26
+ A 'TensorFemSpace' object will create one object of this class for each
27
+ 1D space.
28
+
29
+ Parameters
30
+ ----------
31
+ space : SplineSpace
32
+ 1D finite element space.
33
+
34
+ start : int
35
+ Index of first element local to process.
36
+
37
+ end : int
38
+ Index of last element local to process.
39
+
40
+ nquads : int
41
+ Number of quadrature points used in the Gauss-Legendre quadrature formula.
42
+
43
+ nderiv : int
44
+ Number of basis functions' derivatives to be precomputed at the Gauss
45
+ points (default: 1).
46
+
47
+ """
48
+ def __init__(self, space, start, end, *, nquads, nderiv=1):
49
+
50
+ assert isinstance(space, SplineSpace)
51
+ assert isinstance(start, int)
52
+ assert isinstance(end, int)
53
+ assert isinstance(nquads, int)
54
+ assert isinstance(nderiv, int)
55
+
56
+ # Useful shortcuts
57
+ T = space.knots # knots sequence
58
+ degree = space.degree # spline degree
59
+ n = space.nbasis # total number of control points
60
+ grid = space.breaks # breakpoints
61
+
62
+ # Gauss-legendre quadrature rule
63
+ # print('[psy4str] here is a problem')
64
+ u, w = gauss_legendre(nquads) # TODO [psy4str] OLD DOESN'T WORK
65
+ # u, w = gauss_legendre(nquads + 1) # TODO [psy4str] NEW WORKS BUT WHY?
66
+
67
+ #-------------------------------------------
68
+ # GLOBAL GRID
69
+ #-------------------------------------------
70
+
71
+ # Lists of quadrature coordinates and weights on each element
72
+ global_points, global_weights = quadrature_grid(grid, u, w)
73
+
74
+ # List of basis function values on each element
75
+ global_basis = basis_ders_on_quad_grid(T, degree, global_points, nderiv, space.basis)
76
+
77
+ # List of spans on each element
78
+ # (Span is global index of last non-vanishing basis function)
79
+ global_spans = elements_spans(T, degree)
80
+
81
+ grid = grid[start : end + 2]
82
+ spans = global_spans [start : end + 1].copy()
83
+ basis = global_basis [start : end + 1].copy()
84
+ points = global_points [start : end + 1].copy()
85
+ weights = global_weights[start : end + 1].copy()
86
+
87
+ #-------------------------------------------
88
+ # DATA STORAGE IN OBJECT
89
+ #-------------------------------------------
90
+
91
+ # Quadrature data on extended distributed domain
92
+ self._num_elements = len(grid) - 1
93
+ self._num_quad_pts = len(u)
94
+ self._spans = spans
95
+ self._basis = basis
96
+ self._points = points
97
+ self._weights = weights
98
+ self._indices = tuple(range(start, end + 1))
99
+ self._quad_rule_x = u
100
+ self._quad_rule_w = w
101
+
102
+ # Local index of start/end elements of domain partitioning
103
+ self._local_element_start = 0
104
+ self._local_element_end = self._num_elements - 1
105
+
106
+ # ...
107
+ @property
108
+ def num_elements(self):
109
+ """ Number of elements over which integration should be performed.
110
+ """
111
+ return self._num_elements
112
+
113
+ # ...
114
+ @property
115
+ def num_quad_pts(self):
116
+ """ Number of quadrature points in each element.
117
+ """
118
+ return self._num_quad_pts
119
+
120
+ # ...
121
+ @property
122
+ def spans(self):
123
+ """ Span index in each element.
124
+ """
125
+ return self._spans
126
+
127
+ # ...
128
+ @property
129
+ def basis(self):
130
+ """ Basis function values (and their derivatives) at each quadrature point.
131
+ """
132
+ return self._basis
133
+
134
+ # ...
135
+ @property
136
+ def points(self):
137
+ """ Location of each quadrature point.
138
+ """
139
+ return self._points
140
+
141
+ # ...
142
+ @property
143
+ def weights(self):
144
+ """ Weight assigned to each quadrature point.
145
+ """
146
+ return self._weights
147
+
148
+ # ...
149
+ @property
150
+ def indices(self):
151
+ """ Global index of each element used in assembly process.
152
+ """
153
+ return self._indices
154
+
155
+ # ...
156
+ @property
157
+ def quad_rule_x(self):
158
+ """ Coordinates of quadrature points on canonical interval [-1,1].
159
+ """
160
+ return self._quad_rule_x
161
+
162
+ # ...
163
+ @property
164
+ def quad_rule_w(self):
165
+ """ Weights assigned to quadrature points on canonical interval [-1,1].
166
+ """
167
+ return self._quad_rule_w
168
+
169
+ # ...
170
+ @property
171
+ def local_element_start(self):
172
+ """ Local index of first element owned by process.
173
+ """
174
+ return self._local_element_start
175
+
176
+ # ...
177
+ @property
178
+ def local_element_end(self):
179
+ """ Local index of last element owned by process.
180
+ """
181
+ return self._local_element_end
@@ -0,0 +1,344 @@
1
+ # -*- coding: UTF-8 -*-
2
+ import os
3
+
4
+ import numpy as np
5
+
6
+ from feectools.ddm.cart import CartDecomposition, InterfaceCartDecomposition, create_interfaces_cart
7
+ from feectools.core.bsplines import elements_spans
8
+ from feectools.fem.vector import VectorFemSpace
9
+
10
+
11
+ __all__ = (
12
+ 'partition_coefficients',
13
+ 'get_minus_starts_ends',
14
+ 'get_plus_starts_ends',
15
+ 'create_cart',
16
+ 'construct_interface_spaces',
17
+ 'construct_reduced_interface_spaces'
18
+ )
19
+
20
+
21
+ def partition_coefficients(domain_decomposition, spaces, min_blocks=None):
22
+ """
23
+ Partition the coefficients starting from the grid decomposition.
24
+
25
+ Parameters
26
+ ----------
27
+
28
+ domain_decomposition: DomainDecomposition
29
+ The distributed topological domain.
30
+
31
+ spaces: list of SplineSpace
32
+ The 1d spline spaces that construct the tensor fem space.
33
+
34
+ min_blocks: list of int
35
+ The minimum number of coefficients owned by a process.
36
+
37
+ Returns
38
+ -------
39
+
40
+ global_starts: list of list of int
41
+ The starts of the coefficients for every process along each direction.
42
+
43
+ global_ends: list of list of int
44
+ The ends of the coefficients for every process along each direction.
45
+
46
+ """
47
+ npts = [V.nbasis for V in spaces]
48
+ multiplicity = [V.multiplicity for V in spaces]
49
+
50
+ ndims = len(npts)
51
+ global_starts = [None] * ndims
52
+ global_ends = [None] * ndims
53
+
54
+ for axis in range(ndims):
55
+ es = domain_decomposition.global_element_starts[axis]
56
+ ee = domain_decomposition.global_element_ends [axis]
57
+ m = multiplicity[axis]
58
+
59
+ global_ends [axis] = m*(ee+1)-1
60
+ global_ends [axis][-1] = npts[axis]-1
61
+ global_starts[axis] = np.array([0] + (global_ends[axis][:-1]+1).tolist())
62
+
63
+ if min_blocks is None:
64
+ min_blocks = [None] * ndims
65
+
66
+ for s, e, V, mb in zip(global_starts, global_ends, spaces, min_blocks):
67
+ if V.periodic or mb is None:
68
+ assert all(e-s+1 >= V.degree), f"Local number of elements (after domain decomposition) is to small for spline degree p={V.degree}: {e-s+1} is not >= {V.degree} everywhere.\n \
69
+ You can:\n \
70
+ 1. increase Nel\n \
71
+ 2. lower p\n \
72
+ 3. decrease the MPI size."
73
+ else:
74
+ assert all(e-s+1 >= mb)
75
+
76
+ return global_starts, global_ends
77
+
78
+ #------------------------------------------------------------------------------
79
+ def get_minus_starts_ends(plus_starts, plus_ends, minus_npts, plus_npts, minus_axis, plus_axis,
80
+ minus_ext, plus_ext, minus_pads, plus_pads, minus_shifts, plus_shifts,
81
+ diff):
82
+ """
83
+ Compute the coefficients needed by the minus patch in a given interface.
84
+ """
85
+ starts = [max(0,s-m*p) for s,m,p in zip(plus_starts, minus_shifts, minus_pads)]
86
+ ends = [min(n,e+m*p) for e,n,m,p in zip(plus_ends, minus_npts, minus_shifts, minus_pads)]
87
+ starts[minus_axis] = 0 if minus_ext == -1 else ends[minus_axis]-minus_pads[minus_axis]
88
+ ends[minus_axis] = ends[minus_axis] if minus_ext == 1 else minus_pads[minus_axis]
89
+ return starts, ends
90
+
91
+ #------------------------------------------------------------------------------
92
+ def get_plus_starts_ends(minus_starts, minus_ends, minus_npts, plus_npts, minus_axis, plus_axis,
93
+ minus_ext, plus_ext, minus_pads, plus_pads, minus_shifts, plus_shifts,
94
+ diff):
95
+ """
96
+ Compute the coefficients needed by the plus patch in a given interface.
97
+ """
98
+ starts = [max(0,s-m*p) for s,m,p in zip(minus_starts, plus_shifts, plus_pads)]
99
+ ends = [min(n,e+m*p) for e,n,m,p in zip(minus_ends, plus_npts, plus_shifts, plus_pads)]
100
+ starts[plus_axis] = 0 if plus_ext == -1 else ends[plus_axis]-plus_pads[plus_axis]
101
+ ends[plus_axis] = ends[plus_axis] if plus_ext == 1 else plus_pads[plus_axis]
102
+ return starts, ends
103
+
104
+ #------------------------------------------------------------------------------
105
+ def create_cart(domain_decomposition, spaces):
106
+ """
107
+ Compute the cartesian decomposition of the coefficient space.
108
+ Two different cases are possible:
109
+
110
+ - Single patch :
111
+ We distribute the coefficients using all the processes provided by the given communicator.
112
+ - Multiple patches :
113
+ We decompose the provided communicator in a list of smaller disjoint intra-communicators,
114
+ and decompose the coefficients of each patch with an assigned intra-communicator.
115
+
116
+ Parameters
117
+ ----------
118
+ domain_decomposition : DomainDecomposition | tuple of DomainDecomposition
119
+
120
+ spaces : list of list of 1D global Spline spaces
121
+ The 1D global spline spaces that will be distributed.
122
+
123
+ Returns
124
+ -------
125
+ cart : tuple of CartDecomposition
126
+ Cartesian decomposition of the coefficient space for each patch in the domain.
127
+
128
+ """
129
+
130
+ if len(spaces) == 1:
131
+ domain_decomposition = domain_decomposition[0]
132
+ spaces = spaces[0]
133
+ npts = [V.nbasis for V in spaces]
134
+ pads = [V._pads for V in spaces]
135
+ multiplicity = [V.multiplicity for V in spaces]
136
+
137
+ global_starts, global_ends = partition_coefficients(domain_decomposition, spaces)
138
+
139
+ carts = [CartDecomposition(
140
+ domain_decomposition = domain_decomposition,
141
+ npts = npts,
142
+ global_starts = global_starts,
143
+ global_ends = global_ends,
144
+ pads = pads,
145
+ shifts = multiplicity)]
146
+ else:
147
+ carts = []
148
+ for i in range(len(spaces)):
149
+ npts = [V.nbasis for V in spaces[i]]
150
+ pads = [V._pads for V in spaces[i]]
151
+ multiplicity = [V.multiplicity for V in spaces[i]]
152
+
153
+ global_starts, global_ends = partition_coefficients(
154
+ domain_decomposition[i],
155
+ spaces[i],
156
+ min_blocks=[p+1 for p in pads])
157
+
158
+ new_cart = CartDecomposition(
159
+ domain_decomposition = domain_decomposition[i],
160
+ npts = npts,
161
+ global_starts = global_starts,
162
+ global_ends = global_ends,
163
+ pads = pads,
164
+ shifts = multiplicity)
165
+
166
+ carts.append(new_cart)
167
+
168
+ carts = tuple(carts)
169
+
170
+ return carts
171
+
172
+ #------------------------------------------------------------------------------
173
+ def construct_interface_spaces(domain_decomposition, g_spaces, carts, interiors, connectivity):
174
+ """
175
+ Create the fem spaces for each interface in the domain given by the connectivity.
176
+
177
+ Parameters
178
+ ----------
179
+ domain_decomposition : DomainDecomposition
180
+
181
+ g_spaces : dict
182
+ dictionary that contains the tensor-fem space for each patch.
183
+
184
+ cart: <CartDecomposition|MultiCartDecomposition>
185
+ Cartesian decomposition of the coefficient space.
186
+
187
+ interiors: list of Sympde.topology.Domain
188
+ List of the multipatch domain interiors.
189
+
190
+ connectivity: dict
191
+ The connectivity of the multipatch domain.
192
+ """
193
+ if not connectivity:return
194
+ comm = domain_decomposition.comm
195
+ interfaces_cart = None
196
+ if comm is not None:
197
+ if connectivity:
198
+ communication_info = (get_minus_starts_ends, get_plus_starts_ends)
199
+ interfaces_cart = create_interfaces_cart(domain_decomposition, carts, connectivity.copy(), communication_info=communication_info)
200
+
201
+ for i,j in connectivity:
202
+ if comm is None:
203
+ cart_minus = carts[i]
204
+ cart_plus = carts[j]
205
+ else:
206
+ if not carts[i].is_comm_null and not carts[j].is_comm_null:
207
+ cart_minus = carts[i]
208
+ cart_plus = carts[j]
209
+ elif (i,j) in interfaces_cart:
210
+ cart_minus = interfaces_cart[i,j]
211
+ cart_plus = interfaces_cart[i,j]
212
+ else:
213
+ continue
214
+
215
+ ((axis_minus, ext_minus), (axis_plus , ext_plus)) = connectivity[i, j]
216
+
217
+ g_spaces[interiors[i]].create_interface_space(axis_minus, ext_minus, cart=cart_minus)
218
+ g_spaces[interiors[j]].create_interface_space(axis_plus , ext_plus , cart=cart_plus)
219
+
220
+ max_ncells = tuple(max(ni,nj) for ni,nj in zip(g_spaces[interiors[i]].ncells,g_spaces[interiors[j]].ncells))
221
+ cart_minus = g_spaces[interiors[i]].get_refined_space(max_ncells).coeff_space.cart
222
+ cart_plus = g_spaces[interiors[j]].get_refined_space(max_ncells).coeff_space.cart
223
+ if isinstance(cart_minus, InterfaceCartDecomposition):
224
+ cart = InterfaceCartDecomposition(cart_minus._cart_minus, cart_plus._cart_plus,
225
+ cart_minus._comm, [axis_minus, axis_plus], [ext_minus, ext_plus],
226
+ [cart_minus.ranks_in_topo_minus, cart_plus.ranks_in_topo_plus],
227
+ [cart_minus._local_group_minus, cart_plus._local_group_plus],
228
+ [cart_minus._local_comm_minus, cart_plus._local_comm_plus],
229
+ [cart_minus._root_rank_minus, cart_plus._root_rank_plus],
230
+ [], reduce_elements=True)
231
+
232
+ cart_minus = cart
233
+ cart_plus = cart
234
+ cart.set_interface_communication_infos(get_minus_starts_ends, get_plus_starts_ends)
235
+
236
+ if any(nci!=ncj for nci,ncj in zip(max_ncells, g_spaces[interiors[i]].ncells)):
237
+ g_spaces[interiors[i]].get_refined_space(max_ncells).create_interface_space(axis_minus, ext_minus, cart=cart_minus)
238
+ g_spaces[interiors[j]].get_refined_space(max_ncells).create_interface_space(axis_plus , ext_plus , cart=cart_plus)
239
+
240
+ #------------------------------------------------------------------------------
241
+ def construct_reduced_interface_spaces(spaces, reduced_spaces, interiors, connectivity):
242
+ """
243
+ Create the reduced spaces for the interface coefficients.
244
+
245
+ Parameters
246
+ ----------
247
+
248
+ spaces: dict
249
+ The tensor FEM spaces that we want to reduce for each patch.
250
+
251
+ reduced_spaces: dict
252
+ The reduced coefficient space for each patch.
253
+
254
+ interiors: list of Sympde.topology.Domain
255
+ The patches that construct the multipatch domain.
256
+
257
+ connectivity: dict
258
+ The connectivity of the multipatch domain.
259
+
260
+ """
261
+ for i,j in connectivity:
262
+ ((axis_i, ext_i), (axis_j , ext_j)) = connectivity[i, j]
263
+
264
+ patch_i = interiors[i]
265
+ patch_j = interiors[j]
266
+ space_i = spaces[patch_i].interfaces.get((axis_i, ext_i), None)
267
+ space_j = spaces[patch_j].interfaces.get((axis_j, ext_j), None)
268
+
269
+ if space_i is None or space_j is None: continue
270
+
271
+ cart_i = space_i.coeff_space.cart
272
+ cart_j = space_j.coeff_space.cart
273
+
274
+ if isinstance(cart_i, InterfaceCartDecomposition):
275
+ assert cart_i is cart_j
276
+ if isinstance(reduced_spaces[patch_i], VectorFemSpace):
277
+ for Vi,Vj in zip(reduced_spaces[patch_i].spaces, reduced_spaces[patch_j].spaces):
278
+ npts_i = [Vik.nbasis for Vik in Vi.spaces]
279
+ npts_j = [Vik.nbasis for Vik in Vj.spaces]
280
+ global_starts_i = Vi.coeff_space.cart.global_starts
281
+ global_starts_j = Vj.coeff_space.cart.global_starts
282
+ global_ends_i = Vi.coeff_space.cart.global_ends
283
+ global_ends_j = Vj.coeff_space.cart.global_ends
284
+ shifts_i = Vi.coeff_space.cart.shifts
285
+ shifts_j = Vj.coeff_space.cart.shifts
286
+ cart_ij = cart_i.reduce_npts(Vi.coeff_space.cart, Vj.coeff_space.cart)
287
+ cart_ij.set_interface_communication_infos(get_minus_starts_ends, get_plus_starts_ends)
288
+ Vi.create_interface_space(axis_i, ext_i, cart=cart_ij)
289
+ Vj.create_interface_space(axis_j, ext_j, cart=cart_ij)
290
+
291
+ cart = InterfaceCartDecomposition(cart_ij._cart_minus, cart_ij._cart_plus,
292
+ cart_ij._comm, [axis_i, axis_j], [ext_i, ext_j],
293
+ [cart_ij.ranks_in_topo_minus, cart_ij.ranks_in_topo_plus],
294
+ [cart_ij._local_group_minus, cart_ij._local_group_plus],
295
+ [cart_ij._local_comm_minus, cart_ij._local_comm_plus],
296
+ [cart_ij._root_rank_minus, cart_ij._root_rank_plus],
297
+ [], reduce_elements=True)
298
+
299
+ cart.set_interface_communication_infos(get_minus_starts_ends, get_plus_starts_ends)
300
+ max_ncells = tuple(max(ni,nj) for ni,nj in zip(Vi.ncells,Vj.ncells))
301
+ Vi.get_refined_space(max_ncells).create_interface_space(axis_i, ext_i, cart=cart)
302
+ Vj.get_refined_space(max_ncells).create_interface_space(axis_j, ext_j, cart=cart)
303
+ else:
304
+ Vi = reduced_spaces[patch_i]
305
+ Vj = reduced_spaces[patch_j]
306
+ npts_i = [Vik.nbasis for Vik in Vi.spaces]
307
+ npts_j = [Vik.nbasis for Vik in Vj.spaces]
308
+ cart_ij = cart_i.reduce_npts(Vi.coeff_space.cart, Vj.coeff_space.cart)
309
+ cart_ij.set_interface_communication_infos(get_minus_starts_ends, get_plus_starts_ends)
310
+ Vi.create_interface_space(axis_i, ext_i, cart=cart_ij)
311
+ Vj.create_interface_space(axis_j, ext_j, cart=cart_ij)
312
+
313
+ cart = InterfaceCartDecomposition(cart_ij._cart_minus, cart_ij._cart_plus,
314
+ cart_ij._comm, [axis_i, axis_j], [ext_i, ext_j],
315
+ [cart_ij.ranks_in_topo_minus, cart_ij.ranks_in_topo_plus],
316
+ [cart_ij._local_group_minus, cart_ij._local_group_plus],
317
+ [cart_ij._local_comm_minus, cart_ij._local_comm_plus],
318
+ [cart_ij._root_rank_minus, cart_ij._root_rank_plus],
319
+ [], reduce_elements=True)
320
+
321
+ cart.set_interface_communication_infos(get_minus_starts_ends, get_plus_starts_ends)
322
+ max_ncells = tuple(max(ni,nj) for ni,nj in zip(Vi.ncells,Vj.ncells))
323
+ Vi.get_refined_space(max_ncells).create_interface_space(axis_i, ext_i, cart=cart)
324
+ Vj.get_refined_space(max_ncells).create_interface_space(axis_j, ext_j, cart=cart)
325
+ else:
326
+ if isinstance(reduced_spaces[patch_i], VectorFemSpace):
327
+ for Vi,Vj in zip(reduced_spaces[patch_i].spaces, reduced_spaces[patch_j].spaces):
328
+ Vi.create_interface_space(axis_i, ext_i, cart=Vi.coeff_space.cart)
329
+ Vj.create_interface_space(axis_j , ext_j , cart=Vj.coeff_space.cart)
330
+ max_ncells = tuple(max(ni,nj) for ni,nj in zip(Vi.ncells,Vj.ncells))
331
+ cart_i = Vi.get_refined_space(max_ncells).coeff_space.cart
332
+ cart_j = Vj.get_refined_space(max_ncells).coeff_space.cart
333
+ Vi.get_refined_space(max_ncells).create_interface_space(axis_i, ext_i, cart=cart_i)
334
+ Vj.get_refined_space(max_ncells).create_interface_space(axis_j, ext_j, cart=cart_j)
335
+ else:
336
+ Vi = reduced_spaces[patch_i]
337
+ Vj = reduced_spaces[patch_j]
338
+ Vi.create_interface_space(axis_i, ext_i, cart=Vi.coeff_space.cart)
339
+ Vj.create_interface_space(axis_j , ext_j , cart=Vj.coeff_space.cart)
340
+ max_ncells = tuple(max(ni,nj) for ni,nj in zip(Vi.ncells,Vj.ncells))
341
+ cart_i = Vi.get_refined_space(max_ncells).coeff_space.cart
342
+ cart_j = Vj.get_refined_space(max_ncells).coeff_space.cart
343
+ Vi.get_refined_space(max_ncells).create_interface_space(axis_i, ext_i, cart=cart_i)
344
+ Vj.get_refined_space(max_ncells).create_interface_space(axis_j, ext_j, cart=cart_j)
@@ -0,0 +1,160 @@
1
+ import numpy as np
2
+
3
+ from sympde.topology import element_of
4
+ from sympde.topology.space import ScalarFunction
5
+ from sympde.topology.mapping import Mapping
6
+ from sympde.calculus import dot
7
+ from sympde.expr.expr import LinearForm, integral
8
+
9
+ from feectools.api.settings import PSYDAC_BACKENDS
10
+
11
+ from feectools.linalg.kron import KroneckerDenseMatrix
12
+ from feectools.core.bsplines import hrefinement_matrix
13
+ from feectools.linalg.stencil import StencilVectorSpace
14
+ from feectools.fem.basic import FemSpace
15
+
16
+ __all__ = ('knots_to_insert', 'knot_insertion_projection_operator')
17
+
18
+ def knots_to_insert(coarse_grid, fine_grid, tol=1e-14):
19
+ """ Compute the point difference between the fine grid and coarse grid."""
20
+ # assert len(coarse_grid)*2-2 == len(fine_grid)-1
21
+ indices1 = (np.abs(fine_grid [:,None] - coarse_grid) < tol).any(0)
22
+ indices2 = ~(np.abs(coarse_grid[:,None] - fine_grid ) < tol).any(0)
23
+
24
+ intersection = coarse_grid[indices1]
25
+ T = fine_grid[indices2]
26
+
27
+ assert abs(intersection-coarse_grid).max()<tol
28
+ return T
29
+
30
+
31
+ def knot_insertion_projection_operator(domain, codomain):
32
+ """
33
+ Compute the projection operator based on the knot insertion technique.
34
+
35
+ Return a linear operator which projects an element of the domain to an
36
+ element of the codomain. Domain and codomain are scalar spline spaces over
37
+ a cuboid, built as the tensor product of 1D spline spaces. In particular,
38
+ domain and codomain have the same multi-degree (p1, p2, ...).
39
+
40
+ This function returns a LinearOperator K working at the level of the
41
+ spline coefficients, which are represented by StencilVector objects.
42
+
43
+ Thanks to the tensor-product structure of the spline spaces, the projection
44
+ operator is the Kronecker product of 1D projection operators K[i] operating
45
+ between 1D spaces. Each 1D operators is represented by a dense matrix:
46
+
47
+ K = K[0] x K[1] x ...
48
+
49
+ For each dimension i the 1D grids defined by the breakpoints of the two
50
+ spaces are assumed to be identical, or one nested into the other. Let nd[i]
51
+ and nc[i] be the number of cells along dimension i for domain and codomain,
52
+ respectively. We then have three different cases:
53
+
54
+ 1. nd[i] == nc[i]:
55
+ The two 1D grids are assumed identical, and K[i] is the identity matrix.
56
+
57
+ 2. nd[i] < nc[i]:
58
+ The 1D grid of the domain is assumed nested into the 1D grid of the
59
+ codomain, hence the 1D spline space of the domain is a subspace of the
60
+ 1D spline space of the codomain. In this case we build K[i] using the
61
+ knot insertion algorithm.
62
+
63
+ 3. nd[i] > nc[i]:
64
+ The 1D grid of the codomain is assumed nested into the 1D grid of the
65
+ domain, hence the 1D spline space of the codomain is a subspace of the
66
+ 1D spline space of the domain. In this case we build K[i] as the
67
+ transpose of the matrix obtained using the knot insertion algorithm from
68
+ the codomain to the domain.
69
+
70
+ Parameters
71
+ ----------
72
+ domain : TensorFemSpace
73
+ Domain of the projection operator.
74
+
75
+ codomain : TensorFemSpace
76
+ Codomain of the projection operator.
77
+
78
+ Returns
79
+ -------
80
+ KroneckerDenseMatrix
81
+ Matrix representation of the projection operator. This is a
82
+ LinearOperator acting on the spline coefficients.
83
+
84
+ """
85
+ ops = []
86
+ for d, c in zip(domain.spaces, codomain.spaces):
87
+
88
+ if d.ncells > c.ncells:
89
+ Ts = knots_to_insert(c.breaks, d.breaks)
90
+ P = hrefinement_matrix(Ts, c.degree, c.knots)
91
+
92
+ if d.basis == 'M':
93
+ assert c.basis == 'M'
94
+ P = np.diag(1 / d._scaling_array) @ P @ np.diag(c._scaling_array)
95
+
96
+ ops.append(P.T)
97
+
98
+ elif d.ncells < c.ncells:
99
+ Ts = knots_to_insert(d.breaks, c.breaks)
100
+ P = hrefinement_matrix(Ts, d.degree, d.knots)
101
+
102
+ if d.basis == 'M':
103
+ assert c.basis == 'M'
104
+ P = np.diag(1 / c._scaling_array) @ P @ np.diag(d._scaling_array)
105
+
106
+ ops.append(P)
107
+
108
+ else:
109
+ ops.append(np.eye(d.nbasis))
110
+
111
+ return KroneckerDenseMatrix(domain.coeff_space, codomain.coeff_space, *ops)
112
+
113
+
114
+ def get_dual_dofs(Vh, f, domain_h, backend_language="python", return_format='stencil_array'):
115
+ """
116
+ return the dual dofs tilde_sigma_i(f) = < Lambda_i, f >_{L2} i = 1, .. dim(Vh)) of a given function f, as a stencil array or numpy array
117
+
118
+ Parameters
119
+ ----------
120
+ Vh : FemSpace
121
+ The discrete space for the dual dofs
122
+
123
+ f : <sympy.Expr>
124
+ The function used for evaluation
125
+
126
+ domain_h :
127
+ The discrete domain corresponding to Vh
128
+
129
+ backend_language: <str>
130
+ The backend used to accelerate the code
131
+
132
+ return_format: <str>
133
+ The format of the dofs, can be 'stencil_array' or 'numpy_array'
134
+
135
+ Returns
136
+ -------
137
+ tilde_f: <Vector|ndarray>
138
+ The dual dofs
139
+ """
140
+
141
+ from feectools.api.discretization import discretize
142
+
143
+ assert isinstance(Vh, FemSpace)
144
+
145
+ V = Vh.symbolic_space
146
+ v = element_of(V, name='v')
147
+
148
+ if Vh.is_vector_valued:
149
+ expr = dot(f,v)
150
+ else:
151
+ expr = f*v
152
+
153
+ l = LinearForm(v, integral( V.domain, expr))
154
+ lh = discretize(l, domain_h, Vh, backend=PSYDAC_BACKENDS[backend_language])
155
+ tilde_f = lh.assemble()
156
+
157
+ if return_format == 'numpy_array':
158
+ return tilde_f.toarray()
159
+ else:
160
+ return tilde_f