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,286 @@
|
|
|
1
|
+
from typing import Iterable
|
|
2
|
+
|
|
3
|
+
from sympy import Expr, ImmutableDenseMatrix, Matrix
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from sympde.expr.basic import BasicForm
|
|
8
|
+
from sympde.expr.evaluation import KernelExpression
|
|
9
|
+
from sympde.topology.space import ScalarFunction, VectorFunction, IndexedVectorFunction
|
|
10
|
+
from sympde.topology.space import ProductSpace, VectorFunctionSpace
|
|
11
|
+
from sympde.topology.datatype import H1SpaceType, L2SpaceType, UndefinedSpaceType
|
|
12
|
+
from sympde.topology.derivatives import get_atom_logical_derivatives
|
|
13
|
+
from sympde.topology.derivatives import _logical_partial_derivatives
|
|
14
|
+
|
|
15
|
+
from feectools.fem.basic import FemSpace
|
|
16
|
+
from feectools.linalg.stencil import StencilMatrix, StencilInterfaceMatrix
|
|
17
|
+
from feectools.linalg.basic import ComposedLinearOperator
|
|
18
|
+
from feectools.api.utilities import flatten
|
|
19
|
+
from feectools.api.ast.utilities import math_atoms_as_str, get_max_partial_derivatives
|
|
20
|
+
|
|
21
|
+
# TODO [YG 01.08.2025]: Avoid importing anything from feectools.pyccel
|
|
22
|
+
from feectools.pyccel.ast.core import _atomic
|
|
23
|
+
|
|
24
|
+
__all__ = (
|
|
25
|
+
'compute_max_nderiv',
|
|
26
|
+
'compute_imports',
|
|
27
|
+
'compute_free_arguments',
|
|
28
|
+
'collect_spaces',
|
|
29
|
+
'compute_diag_len',
|
|
30
|
+
'construct_test_space_arguments',
|
|
31
|
+
'construct_trial_space_arguments',
|
|
32
|
+
'construct_quad_grids_arguments',
|
|
33
|
+
'do_nothing',
|
|
34
|
+
'extract_stencil_mats',
|
|
35
|
+
'reset_arrays',
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
#==============================================================================
|
|
39
|
+
def compute_max_nderiv(kernel_expr: KernelExpression) -> int:
|
|
40
|
+
"""
|
|
41
|
+
Compute the highest derivative order in the given kernel expression.
|
|
42
|
+
|
|
43
|
+
Parameters
|
|
44
|
+
----------
|
|
45
|
+
kernel_expr : KernelExpression (from sympde.expr.evaluation)
|
|
46
|
+
|
|
47
|
+
Returns
|
|
48
|
+
-------
|
|
49
|
+
nderiv : int
|
|
50
|
+
The highest order of derivation in `terminal_expr`.
|
|
51
|
+
|
|
52
|
+
"""
|
|
53
|
+
assert isinstance(kernel_expr, KernelExpression)
|
|
54
|
+
|
|
55
|
+
terminal_expr = kernel_expr.expr
|
|
56
|
+
if not isinstance(terminal_expr, (ImmutableDenseMatrix, Matrix)):
|
|
57
|
+
terminal_expr = ImmutableDenseMatrix([[terminal_expr]])
|
|
58
|
+
n_rows, n_cols = terminal_expr.shape
|
|
59
|
+
|
|
60
|
+
atoms_types = (ScalarFunction, VectorFunction, IndexedVectorFunction)
|
|
61
|
+
extended_atoms_types = atoms_types + _logical_partial_derivatives
|
|
62
|
+
|
|
63
|
+
nderiv = 0
|
|
64
|
+
for i_row in range(n_rows):
|
|
65
|
+
for i_col in range(n_cols):
|
|
66
|
+
texpr = terminal_expr[i_row, i_col]
|
|
67
|
+
atoms = _atomic(texpr, cls=extended_atoms_types)
|
|
68
|
+
Fs = [get_atom_logical_derivatives(a) for a in atoms]
|
|
69
|
+
d = get_max_partial_derivatives(texpr, logical=True, F=Fs)
|
|
70
|
+
nderiv = max(nderiv, max(d.values()))
|
|
71
|
+
|
|
72
|
+
return nderiv
|
|
73
|
+
|
|
74
|
+
#==============================================================================
|
|
75
|
+
def compute_imports(expr: Expr,
|
|
76
|
+
spaces: Iterable[FemSpace],
|
|
77
|
+
*,
|
|
78
|
+
openmp: bool
|
|
79
|
+
) -> dict[str, list[str]]:
|
|
80
|
+
"""
|
|
81
|
+
Compute all the imports to be added to the generated Python code.
|
|
82
|
+
|
|
83
|
+
Parameters
|
|
84
|
+
----------
|
|
85
|
+
expr : sympy.Expr
|
|
86
|
+
The integrand expression of a BilinearForm, LinearForm, or
|
|
87
|
+
Functional. This is a pure SymPy expression where SymPDE partial
|
|
88
|
+
derivatives have been converted to SymPy symbols. See Notes.
|
|
89
|
+
|
|
90
|
+
spaces : iterable of feectools.fem.FemSpace
|
|
91
|
+
The discrete spaces which define the finite element representation
|
|
92
|
+
of a BilinearForm, LinearForm, or Functional.
|
|
93
|
+
|
|
94
|
+
openmp : bool
|
|
95
|
+
Whether or not OpenMP pragmas and functions are used in the code.
|
|
96
|
+
|
|
97
|
+
Returns
|
|
98
|
+
-------
|
|
99
|
+
imports : dict[str, list[str]]
|
|
100
|
+
A dictionary whose keys are the names of the Python modules to be
|
|
101
|
+
imported, and whose values are the names of the corresponding
|
|
102
|
+
objects (variables, functions, classes) to be imported from the
|
|
103
|
+
modules.
|
|
104
|
+
|
|
105
|
+
Notes
|
|
106
|
+
-----
|
|
107
|
+
Assume that we start from an object of type BilinearForm, LinearForm,
|
|
108
|
+
or Functional. We take the integrand and expand its vector operations
|
|
109
|
+
with TerminalExpr(), then pull back from physical to logical
|
|
110
|
+
coordinates with LogicalExpr(), and finally convert the symbolic
|
|
111
|
+
partial derivatives with SymbolicExpr(). Where:
|
|
112
|
+
- TerminalExpr is defined in sympde.expr.evaluation
|
|
113
|
+
- LogicalExpr is defined in sympde.topology.mapping
|
|
114
|
+
- SymbolicExpr is defined in sympde.topology.mapping
|
|
115
|
+
|
|
116
|
+
The resulting expression `expr` can be passed to this function.
|
|
117
|
+
"""
|
|
118
|
+
assert isinstance(expr, Expr)
|
|
119
|
+
assert all(isinstance(V, FemSpace) for V in spaces)
|
|
120
|
+
assert isinstance(openmp, bool)
|
|
121
|
+
|
|
122
|
+
# Determine the type of scalar quantities to be managed in the code
|
|
123
|
+
dtypes = [getattr(V.symbolic_space, 'codomain_type', 'real') for V in spaces]
|
|
124
|
+
assert all(t in ['complex', 'real'] for t in dtypes)
|
|
125
|
+
dtype = 'complex' if 'complex' in dtypes else 'real'
|
|
126
|
+
|
|
127
|
+
# TODO uncomment this line when we have a SesquilinearForm defined in SymPDE
|
|
128
|
+
#assert isinstance(expr, SesquilinearForm)
|
|
129
|
+
|
|
130
|
+
#... Compute the imports
|
|
131
|
+
math_library = 'cmath' if dtype=='complex' else 'math' # Function names are the same
|
|
132
|
+
math_imports = math_atoms_as_str(expr, 'math')
|
|
133
|
+
numpy_imports = ['array', 'zeros', 'zeros_like', 'floor']
|
|
134
|
+
|
|
135
|
+
imports = {'numpy': numpy_imports}
|
|
136
|
+
if math_imports:
|
|
137
|
+
imports[math_library] = math_imports
|
|
138
|
+
if openmp:
|
|
139
|
+
imports['pyccel.stdlib.internal.openmp'] = ['omp_get_thread_num']
|
|
140
|
+
#...
|
|
141
|
+
|
|
142
|
+
return imports
|
|
143
|
+
|
|
144
|
+
#==============================================================================
|
|
145
|
+
def compute_free_arguments(expr: BasicForm, kernel_expr: KernelExpression) -> tuple[str]:
|
|
146
|
+
"""
|
|
147
|
+
The string representation (i.e. the names) of the free arguments in
|
|
148
|
+
the given BilinearForm, LinearForm, or Functional.
|
|
149
|
+
|
|
150
|
+
Parameters
|
|
151
|
+
----------
|
|
152
|
+
expr : BilinearForm | LinearForm | Functional
|
|
153
|
+
The expression of which we want to compute the free arguments.
|
|
154
|
+
|
|
155
|
+
kernel_expr : sympde.expr.evaluation.KernelExpression
|
|
156
|
+
The atomic representation of the form, which is obtained after using
|
|
157
|
+
LogicalExpr (if there is a mapping) and TerminalExpr on `expr`.
|
|
158
|
+
|
|
159
|
+
Returns
|
|
160
|
+
-------
|
|
161
|
+
tuple[str]
|
|
162
|
+
The string representation (i.e. the names) of the free arguments in
|
|
163
|
+
the given BilinearForm, LinearForm, or Functional.
|
|
164
|
+
|
|
165
|
+
"""
|
|
166
|
+
assert isinstance(expr, BasicForm)
|
|
167
|
+
assert isinstance(kernel_expr, KernelExpression)
|
|
168
|
+
|
|
169
|
+
free_args_dict = expr.get_free_variables()
|
|
170
|
+
free_args_str = tuple(str(a) for a in free_args_dict)
|
|
171
|
+
|
|
172
|
+
return free_args_str
|
|
173
|
+
|
|
174
|
+
#==============================================================================
|
|
175
|
+
def collect_spaces(space, *args):
|
|
176
|
+
"""
|
|
177
|
+
This function collect the arguments used in the assembly function
|
|
178
|
+
|
|
179
|
+
Parameters
|
|
180
|
+
----------
|
|
181
|
+
space: <FunctionSpace>
|
|
182
|
+
the symbolic space
|
|
183
|
+
|
|
184
|
+
args : <list>
|
|
185
|
+
list of discrete space components like basis values, spans, ...
|
|
186
|
+
|
|
187
|
+
Returns
|
|
188
|
+
-------
|
|
189
|
+
args : <list>
|
|
190
|
+
list of discrete space components elements used in the asembly
|
|
191
|
+
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
if isinstance(space, ProductSpace):
|
|
195
|
+
spaces = space.spaces
|
|
196
|
+
indices = []
|
|
197
|
+
i = 0
|
|
198
|
+
for space in spaces:
|
|
199
|
+
if isinstance(space, VectorFunctionSpace):
|
|
200
|
+
if isinstance(space.kind, (H1SpaceType, L2SpaceType, UndefinedSpaceType)):
|
|
201
|
+
indices.append(i)
|
|
202
|
+
else:
|
|
203
|
+
indices += [i+j for j in range(space.ldim)]
|
|
204
|
+
i = i + space.ldim
|
|
205
|
+
else:
|
|
206
|
+
indices.append(i)
|
|
207
|
+
i = i + 1
|
|
208
|
+
args = [[e[i] for i in indices] for e in args]
|
|
209
|
+
|
|
210
|
+
elif isinstance(space, VectorFunctionSpace):
|
|
211
|
+
if isinstance(space.kind, (H1SpaceType, L2SpaceType, UndefinedSpaceType)):
|
|
212
|
+
args = [[e[0]] for e in args]
|
|
213
|
+
|
|
214
|
+
return args
|
|
215
|
+
|
|
216
|
+
#==============================================================================
|
|
217
|
+
def compute_diag_len(p, md, mc):
|
|
218
|
+
n = ((np.ceil((p+1)/mc)-1)*md).astype('int')
|
|
219
|
+
n = n-np.minimum(0, n-p)+p+1
|
|
220
|
+
return n.astype('int')
|
|
221
|
+
|
|
222
|
+
#==============================================================================
|
|
223
|
+
def construct_test_space_arguments(basis_values):
|
|
224
|
+
space = basis_values.space
|
|
225
|
+
test_basis = basis_values.basis
|
|
226
|
+
spans = basis_values.spans
|
|
227
|
+
test_degrees = space.degree
|
|
228
|
+
pads = space.pads
|
|
229
|
+
multiplicity = space.multiplicity
|
|
230
|
+
|
|
231
|
+
test_basis, test_degrees, spans = collect_spaces(space.symbolic_space, test_basis, test_degrees, spans)
|
|
232
|
+
|
|
233
|
+
test_basis = flatten(test_basis)
|
|
234
|
+
test_degrees = flatten(test_degrees)
|
|
235
|
+
spans = flatten(spans)
|
|
236
|
+
pads = flatten(pads)
|
|
237
|
+
multiplicity = flatten(multiplicity)
|
|
238
|
+
pads = [p*m for p,m in zip(pads, multiplicity)]
|
|
239
|
+
return test_basis, test_degrees, spans, pads, multiplicity
|
|
240
|
+
|
|
241
|
+
def construct_trial_space_arguments(basis_values):
|
|
242
|
+
space = basis_values.space
|
|
243
|
+
trial_basis = basis_values.basis
|
|
244
|
+
trial_degrees = space.degree
|
|
245
|
+
pads = space.pads
|
|
246
|
+
multiplicity = space.multiplicity
|
|
247
|
+
trial_basis, trial_degrees = collect_spaces(space.symbolic_space, trial_basis, trial_degrees)
|
|
248
|
+
|
|
249
|
+
trial_basis = flatten(trial_basis)
|
|
250
|
+
trial_degrees = flatten(trial_degrees)
|
|
251
|
+
pads = flatten(pads)
|
|
252
|
+
multiplicity = flatten(multiplicity)
|
|
253
|
+
pads = [p*m for p,m in zip(pads, multiplicity)]
|
|
254
|
+
return trial_basis, trial_degrees, pads, multiplicity
|
|
255
|
+
|
|
256
|
+
#==============================================================================
|
|
257
|
+
def construct_quad_grids_arguments(grid, use_weights=True):
|
|
258
|
+
points = grid.points
|
|
259
|
+
if use_weights:
|
|
260
|
+
weights = grid.weights
|
|
261
|
+
quads = flatten(list(zip(points, weights)))
|
|
262
|
+
else:
|
|
263
|
+
quads = flatten(list(zip(points)))
|
|
264
|
+
|
|
265
|
+
nquads = flatten(grid.nquads)
|
|
266
|
+
n_elements = grid.n_elements
|
|
267
|
+
return n_elements, quads, nquads
|
|
268
|
+
|
|
269
|
+
#==============================================================================
|
|
270
|
+
def do_nothing(*args):
|
|
271
|
+
return 0
|
|
272
|
+
|
|
273
|
+
#==============================================================================
|
|
274
|
+
def extract_stencil_mats(mats):
|
|
275
|
+
new_mats = []
|
|
276
|
+
for M in mats:
|
|
277
|
+
if isinstance(M, (StencilInterfaceMatrix, StencilMatrix)):
|
|
278
|
+
new_mats.append(M)
|
|
279
|
+
elif isinstance(M, ComposedLinearOperator):
|
|
280
|
+
new_mats += [i for i in M.multiplicants if isinstance(i, (StencilInterfaceMatrix, StencilMatrix))]
|
|
281
|
+
return new_mats
|
|
282
|
+
|
|
283
|
+
#==============================================================================
|
|
284
|
+
def reset_arrays(*args):
|
|
285
|
+
for a in args:
|
|
286
|
+
a[:]= 0.j if a.dtype==complex else 0.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from sympde.expr.expr import (
|
|
4
|
+
BilinearForm as sym_BilinearForm,
|
|
5
|
+
LinearForm as sym_LinearForm,
|
|
6
|
+
Functional as sym_Functional
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
from .basic import BasicDiscrete
|
|
10
|
+
from .fem import DiscreteFunctional
|
|
11
|
+
from .fem import DiscreteLinearForm
|
|
12
|
+
from .fem import DiscreteBilinearForm
|
|
13
|
+
from .fem_bilinear_form import DiscreteBilinearForm as DiscreteBilinearForm_SF
|
|
14
|
+
from .fem_common import reset_arrays
|
|
15
|
+
from .utilities import random_string
|
|
16
|
+
|
|
17
|
+
__all__ = ('DiscreteSumForm',)
|
|
18
|
+
|
|
19
|
+
#==============================================================================
|
|
20
|
+
class DiscreteSumForm(BasicDiscrete):
|
|
21
|
+
|
|
22
|
+
def __init__(self, a, kernel_expr, *args, **kwargs):
|
|
23
|
+
|
|
24
|
+
# Sum factorization is only implemented for bilinear forms in 3D, in
|
|
25
|
+
# which case we use it by default. A 2D implementation should be the
|
|
26
|
+
# next step, hence we allow the user to pass `sum_factorization=True`
|
|
27
|
+
# even if not supported yet. In the case of linear forms or functionals
|
|
28
|
+
# this option is irrelevant for now, so we ignore it.
|
|
29
|
+
#
|
|
30
|
+
# In every case we remove the `sum_factorization` key from the dict
|
|
31
|
+
# in order to avoid errors, because none of the class constructors
|
|
32
|
+
# accept this argument.
|
|
33
|
+
sum_factorization = kwargs.pop('sum_factorization', a.ldim == 3)
|
|
34
|
+
|
|
35
|
+
# TODO Uncomment when the SesquilinearForm exist in SymPDE
|
|
36
|
+
#if not isinstance(a, (sym_BilinearForm, sym_SesquilinearForm, sym_LinearForm, sym_Functional)):
|
|
37
|
+
# raise TypeError('> Expecting a symbolic BilinearForm, SesquilinearForm, LinearForm, Functional')
|
|
38
|
+
if not isinstance(a, (sym_BilinearForm, sym_LinearForm, sym_Functional)):
|
|
39
|
+
raise TypeError('> Expecting a symbolic BilinearForm, LinearForm, Functional')
|
|
40
|
+
|
|
41
|
+
self._expr = a
|
|
42
|
+
backend = kwargs.pop('backend', None)
|
|
43
|
+
self._backend = backend
|
|
44
|
+
|
|
45
|
+
folder = kwargs.get('folder', None)
|
|
46
|
+
self._folder = self._initialize_folder(folder)
|
|
47
|
+
|
|
48
|
+
# create a module name if not given
|
|
49
|
+
tag = random_string(8)
|
|
50
|
+
|
|
51
|
+
# ...
|
|
52
|
+
forms = []
|
|
53
|
+
free_args = []
|
|
54
|
+
self._kernel_expr = kernel_expr
|
|
55
|
+
operator = None
|
|
56
|
+
for e in kernel_expr:
|
|
57
|
+
if isinstance(a, sym_LinearForm):
|
|
58
|
+
kwargs['update_ghost_regions'] = False
|
|
59
|
+
ah = DiscreteLinearForm(a, e, *args, backend=backend, **kwargs)
|
|
60
|
+
kwargs['vector'] = ah._vector
|
|
61
|
+
operator = ah._vector
|
|
62
|
+
|
|
63
|
+
# TODO Uncomment when the SesquilinearForm exist in SymPDE
|
|
64
|
+
# elif isinstance(a, sym_SesquilinearForm):
|
|
65
|
+
# kwargs['update_ghost_regions'] = False
|
|
66
|
+
# ah = DiscreteSesquilinearForm(a, e, *args, assembly_backend=backend, **kwargs)
|
|
67
|
+
# kwargs['matrix'] = ah._matrix
|
|
68
|
+
# operator = ah._matrix
|
|
69
|
+
|
|
70
|
+
elif isinstance(a, sym_BilinearForm):
|
|
71
|
+
kwargs['update_ghost_regions'] = False
|
|
72
|
+
if sum_factorization:
|
|
73
|
+
ah = DiscreteBilinearForm_SF(a, e, *args, assembly_backend=backend, **kwargs)
|
|
74
|
+
else:
|
|
75
|
+
ah = DiscreteBilinearForm(a, e, *args, assembly_backend=backend, **kwargs)
|
|
76
|
+
kwargs['matrix'] = ah._matrix
|
|
77
|
+
operator = ah._matrix
|
|
78
|
+
|
|
79
|
+
elif isinstance(a, sym_Functional):
|
|
80
|
+
ah = DiscreteFunctional(a, e, *args, backend=backend, **kwargs)
|
|
81
|
+
|
|
82
|
+
forms.append(ah)
|
|
83
|
+
free_args.extend(ah.free_args)
|
|
84
|
+
|
|
85
|
+
if isinstance(a, sym_BilinearForm):
|
|
86
|
+
is_broken = len(args[0].domain)>1
|
|
87
|
+
if self._backend is not None and is_broken:
|
|
88
|
+
for mat in kwargs['matrix']._blocks.values():
|
|
89
|
+
mat.set_backend(backend)
|
|
90
|
+
elif self._backend is not None:
|
|
91
|
+
kwargs['matrix'].set_backend(backend)
|
|
92
|
+
|
|
93
|
+
self._forms = forms
|
|
94
|
+
self._operator = operator
|
|
95
|
+
self._free_args = tuple(set(free_args))
|
|
96
|
+
self._is_functional = isinstance(a, sym_Functional)
|
|
97
|
+
# ...
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def forms(self):
|
|
101
|
+
return self._forms
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def free_args(self):
|
|
105
|
+
return self._free_args
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def is_functional(self):
|
|
109
|
+
return self._is_functional
|
|
110
|
+
|
|
111
|
+
def assemble(self, *, reset=True, **kwargs):
|
|
112
|
+
if not self.is_functional:
|
|
113
|
+
if reset :
|
|
114
|
+
reset_arrays(*[i for M in self.forms for i in M.global_matrices])
|
|
115
|
+
|
|
116
|
+
for form in self.forms:
|
|
117
|
+
form.assemble(reset=False, **kwargs)
|
|
118
|
+
self._operator.exchange_assembly_data()
|
|
119
|
+
return self._operator
|
|
120
|
+
else:
|
|
121
|
+
M = [form.assemble(**kwargs) for form in self.forms]
|
|
122
|
+
M = np.sum(M)
|
|
123
|
+
return M
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
import subprocess # nosec B404
|
|
3
|
+
import platform
|
|
4
|
+
import re
|
|
5
|
+
from packaging.version import Version
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
__all__ = ('PSYDAC_DEFAULT_FOLDER', 'PSYDAC_BACKENDS')
|
|
9
|
+
|
|
10
|
+
#==============================================================================
|
|
11
|
+
|
|
12
|
+
PSYDAC_DEFAULT_FOLDER = {'name':'__psydac__'}
|
|
13
|
+
|
|
14
|
+
# ... defining PSYDAC backends
|
|
15
|
+
PSYDAC_BACKEND_PYTHON = {'name': 'python', 'tag':'python', 'openmp':False}
|
|
16
|
+
|
|
17
|
+
PSYDAC_BACKEND_GPYCCEL = {'name': 'pyccel',
|
|
18
|
+
'compiler_family': 'GNU',
|
|
19
|
+
'flags' : '-O3 -ffast-math',
|
|
20
|
+
'folder' : '__gpyccel__',
|
|
21
|
+
'tag' : 'gpyccel',
|
|
22
|
+
'openmp' : False}
|
|
23
|
+
|
|
24
|
+
PSYDAC_BACKEND_IPYCCEL = {'name': 'pyccel',
|
|
25
|
+
'compiler_family': 'intel',
|
|
26
|
+
'flags' : '-O3',
|
|
27
|
+
'folder' : '__ipyccel__',
|
|
28
|
+
'tag' :'ipyccel',
|
|
29
|
+
'openmp' : False}
|
|
30
|
+
|
|
31
|
+
PSYDAC_BACKEND_PGPYCCEL = {'name': 'pyccel',
|
|
32
|
+
'compiler_family': 'PGI',
|
|
33
|
+
'flags' : '-O3 -Munroll',
|
|
34
|
+
'folder' : '__pgpyccel__',
|
|
35
|
+
'tag' : 'pgpyccel',
|
|
36
|
+
'openmp' : False}
|
|
37
|
+
|
|
38
|
+
PSYDAC_BACKEND_NVPYCCEL = {'name': 'pyccel',
|
|
39
|
+
'compiler_family': 'nvidia',
|
|
40
|
+
'flags' : '-O3 -Munroll',
|
|
41
|
+
'folder' : '__nvpyccel__',
|
|
42
|
+
'tag' : 'nvpyccel',
|
|
43
|
+
'openmp' : False}
|
|
44
|
+
# ...
|
|
45
|
+
|
|
46
|
+
# Get gfortran version
|
|
47
|
+
gfortran_version_output = subprocess.check_output(['gfortran', '--version']).decode('utf-8') # nosec B603, B607
|
|
48
|
+
gfortran_version_string = re.search(r"(\d+\.\d+\.\d+)", gfortran_version_output).group()
|
|
49
|
+
gfortran_version = Version(gfortran_version_string)
|
|
50
|
+
|
|
51
|
+
# Platform-dependent flags
|
|
52
|
+
if platform.system() == "Darwin" and platform.machine() == 'arm64' and gfortran_version >= Version("14"):
|
|
53
|
+
|
|
54
|
+
# Apple silicon requires architecture-specific flags (see https://github.com/pyccel/psydac/pull/411)
|
|
55
|
+
# which are only available on GCC version >= 14
|
|
56
|
+
cpu_brand = subprocess.check_output(['sysctl','-n','machdep.cpu.brand_string']).decode('utf-8').strip() # nosec B603, B607
|
|
57
|
+
if cpu_brand.startswith("Apple M"):
|
|
58
|
+
# Example: "Apple M3 Pro (virtual)" --> " -mcpu=apple-m3"
|
|
59
|
+
cpu_flag = '-'.join(cpu_brand.lower().split()[:2])
|
|
60
|
+
PSYDAC_BACKEND_GPYCCEL['flags'] += f' -mcpu={cpu_flag}'
|
|
61
|
+
else:
|
|
62
|
+
# TODO: Support later Apple CPU models. Perhaps the CPU naming scheme could be easily guessed
|
|
63
|
+
# based on the output of 'sysctl -n machdep.cpu.brand_string', but I wouldn't rely on this
|
|
64
|
+
# guess unless it has been manually verified. Loud errors are better than silent failures!
|
|
65
|
+
raise SystemError(f"Unsupported Apple CPU '{cpu_brand}'.")
|
|
66
|
+
|
|
67
|
+
else:
|
|
68
|
+
# Default architecture flags
|
|
69
|
+
PSYDAC_BACKEND_GPYCCEL['flags'] += ' -march=native -mtune=native'
|
|
70
|
+
if platform.machine() == 'x86_64':
|
|
71
|
+
PSYDAC_BACKEND_GPYCCEL['flags'] += ' -mavx'
|
|
72
|
+
|
|
73
|
+
#==============================================================================
|
|
74
|
+
|
|
75
|
+
# List of all available backends for accelerating Python code
|
|
76
|
+
PSYDAC_BACKENDS = {
|
|
77
|
+
'python' : PSYDAC_BACKEND_PYTHON,
|
|
78
|
+
'pyccel-gcc' : PSYDAC_BACKEND_GPYCCEL,
|
|
79
|
+
'pyccel-intel' : PSYDAC_BACKEND_IPYCCEL,
|
|
80
|
+
'pyccel-pgi' : PSYDAC_BACKEND_PGPYCCEL,
|
|
81
|
+
'pyccel-nvidia': PSYDAC_BACKEND_NVPYCCEL,
|
|
82
|
+
}
|