zoomy-core 0.1.14__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.
- decorators/decorators.py +25 -0
- fvm/__init__.py +0 -0
- fvm/flux.py +52 -0
- fvm/nonconservative_flux.py +97 -0
- fvm/ode.py +55 -0
- fvm/solver_numpy.py +297 -0
- fvm/timestepping.py +13 -0
- mesh/__init__.py +0 -0
- mesh/mesh.py +1239 -0
- mesh/mesh_extrude.py +168 -0
- mesh/mesh_util.py +487 -0
- misc/__init__.py +0 -0
- misc/custom_types.py +6 -0
- misc/interpolation.py +140 -0
- misc/io.py +448 -0
- misc/logger_config.py +18 -0
- misc/misc.py +218 -0
- model/__init__.py +0 -0
- model/analysis.py +147 -0
- model/basefunction.py +113 -0
- model/basemodel.py +513 -0
- model/boundary_conditions.py +193 -0
- model/initial_conditions.py +171 -0
- model/model.py +65 -0
- model/models/GN.py +70 -0
- model/models/advection.py +53 -0
- model/models/basisfunctions.py +181 -0
- model/models/basismatrices.py +381 -0
- model/models/coupled_constrained.py +60 -0
- model/models/poisson.py +41 -0
- model/models/shallow_moments.py +757 -0
- model/models/shallow_moments_sediment.py +378 -0
- model/models/shallow_moments_topo.py +423 -0
- model/models/shallow_moments_variants.py +1509 -0
- model/models/shallow_water.py +266 -0
- model/models/shallow_water_topo.py +111 -0
- model/models/shear_shallow_flow.py +594 -0
- model/models/sme_turbulent.py +613 -0
- model/models/vam.py +455 -0
- postprocessing/__init__.py +0 -0
- postprocessing/plotting.py +244 -0
- postprocessing/postprocessing.py +75 -0
- preprocessing/__init__.py +0 -0
- preprocessing/openfoam_moments.py +453 -0
- transformation/__init__.py +0 -0
- transformation/helpers.py +25 -0
- transformation/to_amrex.py +241 -0
- transformation/to_c.py +185 -0
- transformation/to_jax.py +14 -0
- transformation/to_numpy.py +118 -0
- transformation/to_openfoam.py +258 -0
- transformation/to_ufl.py +67 -0
- zoomy_core-0.1.14.dist-info/METADATA +52 -0
- zoomy_core-0.1.14.dist-info/RECORD +57 -0
- zoomy_core-0.1.14.dist-info/WHEEL +5 -0
- zoomy_core-0.1.14.dist-info/licenses/LICENSE +674 -0
- zoomy_core-0.1.14.dist-info/top_level.txt +8 -0
model/basemodel.py
ADDED
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
from typing import Callable, Union
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import sympy
|
|
5
|
+
from attrs import define, field
|
|
6
|
+
from sympy import init_printing, powsimp, zeros
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
from zoomy_core.model.boundary_conditions import BoundaryConditions
|
|
10
|
+
from zoomy_core.model.initial_conditions import Constant, InitialConditions
|
|
11
|
+
from zoomy_core.misc.custom_types import FArray
|
|
12
|
+
from zoomy_core.misc.misc import Zstruct, ZArray
|
|
13
|
+
from zoomy_core.model.basefunction import Function
|
|
14
|
+
|
|
15
|
+
init_printing()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def default_simplify(expr):
|
|
19
|
+
return powsimp(expr, combine="all", force=False, deep=True)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@define(frozen=True, slots=True, kw_only=True)
|
|
23
|
+
class Model:
|
|
24
|
+
"""
|
|
25
|
+
Generic (virtual) model implementation.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
boundary_conditions: BoundaryConditions
|
|
29
|
+
|
|
30
|
+
name: str = "Model"
|
|
31
|
+
dimension: int = 1
|
|
32
|
+
|
|
33
|
+
initial_conditions: InitialConditions = field(factory=Constant)
|
|
34
|
+
aux_initial_conditions: InitialConditions = field(factory=Constant)
|
|
35
|
+
|
|
36
|
+
parameters: Zstruct = field(factory=lambda: Zstruct())
|
|
37
|
+
|
|
38
|
+
time: sympy.Symbol = field(
|
|
39
|
+
init=False, factory=lambda: sympy.symbols("t", real=True)
|
|
40
|
+
)
|
|
41
|
+
distance: sympy.Symbol = field(
|
|
42
|
+
init=False, factory=lambda: sympy.symbols("dX", real=True)
|
|
43
|
+
)
|
|
44
|
+
position: Zstruct = field(
|
|
45
|
+
init=False, factory=lambda: register_sympy_attribute(3, "X")
|
|
46
|
+
)
|
|
47
|
+
number_of_points_3d: int = field(factory=lambda: 10)
|
|
48
|
+
|
|
49
|
+
_simplify: Callable = field(factory=lambda: default_simplify)
|
|
50
|
+
|
|
51
|
+
# Derived fields initialized in __attrs_post_init__
|
|
52
|
+
_default_parameters: dict = field(init=False, factory=dict)
|
|
53
|
+
n_variables: int = field(init=False)
|
|
54
|
+
n_aux_variables: int = field(init=False)
|
|
55
|
+
n_parameters: int = field(init=False)
|
|
56
|
+
variables: Zstruct = field(init=False, default=1)
|
|
57
|
+
|
|
58
|
+
positive_variables: Union[dict, list, None] = field(default=None)
|
|
59
|
+
aux_variables: Zstruct = field(default=0)
|
|
60
|
+
parameter_values: FArray = field(init=False)
|
|
61
|
+
normal: ZArray = field(init=False)
|
|
62
|
+
|
|
63
|
+
z_3d: Zstruct = field(init=False, default=number_of_points_3d)
|
|
64
|
+
u_3d: Zstruct = field(init=False, default=number_of_points_3d)
|
|
65
|
+
p_3d: Zstruct = field(init=False, default=number_of_points_3d)
|
|
66
|
+
alpha_3d: Zstruct = field(init=False, default=number_of_points_3d)
|
|
67
|
+
|
|
68
|
+
_flux: Function = field(init=False)
|
|
69
|
+
_dflux: Function = field(init=False)
|
|
70
|
+
_nonconservative_matrix: Function = field(init=False)
|
|
71
|
+
_quasilinear_matrix: Function = field(init=False)
|
|
72
|
+
_eigenvalues: Function = field(init=False)
|
|
73
|
+
_left_eigenvectors: Function = field(init=False)
|
|
74
|
+
_right_eigenvectors: Function = field(init=False)
|
|
75
|
+
_source: Function = field(init=False)
|
|
76
|
+
_source_jacobian_wrt_variables: Function = field(init=False)
|
|
77
|
+
_source_jacobian_wrt_aux_variables: Function = field(init=False)
|
|
78
|
+
_residual: Function = field(init=False)
|
|
79
|
+
_project_2d_to_3d: Function = field(init=False)
|
|
80
|
+
_project_3d_to_2d: Function = field(init=False)
|
|
81
|
+
_boundary_conditions: Function = field(init=False)
|
|
82
|
+
|
|
83
|
+
def __attrs_post_init__(self):
|
|
84
|
+
updated_default_parameters = {
|
|
85
|
+
**self._default_parameters,
|
|
86
|
+
**self.parameters.as_dict(),
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# Use object.__setattr__ because class is frozen
|
|
90
|
+
object.__setattr__(
|
|
91
|
+
self,
|
|
92
|
+
"variables",
|
|
93
|
+
register_sympy_attribute(self.variables, "q", self.positive_variables),
|
|
94
|
+
)
|
|
95
|
+
object.__setattr__(
|
|
96
|
+
self, "aux_variables", register_sympy_attribute(self.aux_variables, "qaux")
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
object.__setattr__(
|
|
100
|
+
self,
|
|
101
|
+
"parameters",
|
|
102
|
+
register_sympy_attribute(updated_default_parameters, "p"),
|
|
103
|
+
)
|
|
104
|
+
object.__setattr__(
|
|
105
|
+
self,
|
|
106
|
+
"parameter_values",
|
|
107
|
+
register_parameter_values(updated_default_parameters),
|
|
108
|
+
)
|
|
109
|
+
object.__setattr__(
|
|
110
|
+
self,
|
|
111
|
+
"normal",
|
|
112
|
+
register_sympy_attribute(
|
|
113
|
+
["n" + str(i) for i in range(self.dimension)], "n"
|
|
114
|
+
),
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
object.__setattr__(self, "n_variables", self.variables.length())
|
|
118
|
+
object.__setattr__(self, "n_aux_variables", self.aux_variables.length())
|
|
119
|
+
object.__setattr__(self, "n_parameters", self.parameters.length())
|
|
120
|
+
|
|
121
|
+
object.__setattr__(
|
|
122
|
+
self, "z_3d", register_sympy_attribute(self.number_of_points_3d, "z")
|
|
123
|
+
)
|
|
124
|
+
object.__setattr__(
|
|
125
|
+
self, "u_3d", register_sympy_attribute(self.number_of_points_3d, "u")
|
|
126
|
+
)
|
|
127
|
+
object.__setattr__(
|
|
128
|
+
self, "p_3d", register_sympy_attribute(self.number_of_points_3d, "p")
|
|
129
|
+
)
|
|
130
|
+
object.__setattr__(
|
|
131
|
+
self,
|
|
132
|
+
"alpha_3d",
|
|
133
|
+
register_sympy_attribute(self.number_of_points_3d, "alpha"),
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
object.__setattr__(
|
|
137
|
+
self,
|
|
138
|
+
"_flux",
|
|
139
|
+
Function(
|
|
140
|
+
name="flux",
|
|
141
|
+
definition=self.flux(),
|
|
142
|
+
args=Zstruct(
|
|
143
|
+
variables=self.variables,
|
|
144
|
+
aux_variables=self.aux_variables,
|
|
145
|
+
parameters=self.parameters,
|
|
146
|
+
),
|
|
147
|
+
),
|
|
148
|
+
)
|
|
149
|
+
object.__setattr__(
|
|
150
|
+
self,
|
|
151
|
+
"_dflux",
|
|
152
|
+
Function(
|
|
153
|
+
name="dflux",
|
|
154
|
+
definition=self.dflux(),
|
|
155
|
+
args=Zstruct(
|
|
156
|
+
variables=self.variables,
|
|
157
|
+
aux_variables=self.aux_variables,
|
|
158
|
+
parameters=self.parameters,
|
|
159
|
+
),
|
|
160
|
+
),
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
object.__setattr__(
|
|
164
|
+
self,
|
|
165
|
+
"_nonconservative_matrix",
|
|
166
|
+
Function(
|
|
167
|
+
name="nonconservative_matrix",
|
|
168
|
+
definition=self.nonconservative_matrix(),
|
|
169
|
+
args=Zstruct(
|
|
170
|
+
variables=self.variables,
|
|
171
|
+
aux_variables=self.aux_variables,
|
|
172
|
+
parameters=self.parameters,
|
|
173
|
+
),
|
|
174
|
+
),
|
|
175
|
+
)
|
|
176
|
+
object.__setattr__(
|
|
177
|
+
self,
|
|
178
|
+
"_quasilinear_matrix",
|
|
179
|
+
Function(
|
|
180
|
+
name="quasilinear_matrix",
|
|
181
|
+
definition=self.quasilinear_matrix(),
|
|
182
|
+
args=Zstruct(
|
|
183
|
+
variables=self.variables,
|
|
184
|
+
aux_variables=self.aux_variables,
|
|
185
|
+
parameters=self.parameters,
|
|
186
|
+
),
|
|
187
|
+
),
|
|
188
|
+
)
|
|
189
|
+
object.__setattr__(
|
|
190
|
+
self,
|
|
191
|
+
"_eigenvalues",
|
|
192
|
+
Function(
|
|
193
|
+
name="eigenvalues",
|
|
194
|
+
definition=self.eigenvalues(),
|
|
195
|
+
args=Zstruct(
|
|
196
|
+
variables=self.variables,
|
|
197
|
+
aux_variables=self.aux_variables,
|
|
198
|
+
parameters=self.parameters,
|
|
199
|
+
normal=self.normal,
|
|
200
|
+
),
|
|
201
|
+
),
|
|
202
|
+
)
|
|
203
|
+
object.__setattr__(
|
|
204
|
+
self,
|
|
205
|
+
"_left_eigenvectors",
|
|
206
|
+
Function(
|
|
207
|
+
name="left_eigenvectors",
|
|
208
|
+
definition=self.left_eigenvectors(),
|
|
209
|
+
args=Zstruct(
|
|
210
|
+
variables=self.variables,
|
|
211
|
+
aux_variables=self.aux_variables,
|
|
212
|
+
parameters=self.parameters,
|
|
213
|
+
normal=self.normal,
|
|
214
|
+
),
|
|
215
|
+
),
|
|
216
|
+
)
|
|
217
|
+
object.__setattr__(
|
|
218
|
+
self,
|
|
219
|
+
"_right_eigenvectors",
|
|
220
|
+
Function(
|
|
221
|
+
name="right_eigenvectors",
|
|
222
|
+
definition=self.right_eigenvectors(),
|
|
223
|
+
args=Zstruct(
|
|
224
|
+
variables=self.variables,
|
|
225
|
+
aux_variables=self.aux_variables,
|
|
226
|
+
parameters=self.parameters,
|
|
227
|
+
normal=self.normal,
|
|
228
|
+
),
|
|
229
|
+
),
|
|
230
|
+
)
|
|
231
|
+
object.__setattr__(
|
|
232
|
+
self,
|
|
233
|
+
"_source",
|
|
234
|
+
Function(
|
|
235
|
+
name="source",
|
|
236
|
+
definition=self.source(),
|
|
237
|
+
args=Zstruct(
|
|
238
|
+
variables=self.variables,
|
|
239
|
+
aux_variables=self.aux_variables,
|
|
240
|
+
parameters=self.parameters,
|
|
241
|
+
),
|
|
242
|
+
),
|
|
243
|
+
)
|
|
244
|
+
object.__setattr__(
|
|
245
|
+
self,
|
|
246
|
+
"_source_jacobian_wrt_variables",
|
|
247
|
+
Function(
|
|
248
|
+
name="source_jacobian_wrt_variables",
|
|
249
|
+
definition=self.source_jacobian_wrt_variables(),
|
|
250
|
+
args=Zstruct(
|
|
251
|
+
variables=self.variables,
|
|
252
|
+
aux_variables=self.aux_variables,
|
|
253
|
+
parameters=self.parameters,
|
|
254
|
+
),
|
|
255
|
+
),
|
|
256
|
+
)
|
|
257
|
+
object.__setattr__(
|
|
258
|
+
self,
|
|
259
|
+
"_source_jacobian_wrt_aux_variables",
|
|
260
|
+
Function(
|
|
261
|
+
name="source_jacobian_wrt_aux_variables",
|
|
262
|
+
definition=self.source_jacobian_wrt_aux_variables(),
|
|
263
|
+
args=Zstruct(
|
|
264
|
+
variables=self.variables,
|
|
265
|
+
aux_variables=self.aux_variables,
|
|
266
|
+
parameters=self.parameters,
|
|
267
|
+
),
|
|
268
|
+
),
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
object.__setattr__(
|
|
272
|
+
self,
|
|
273
|
+
"_residual",
|
|
274
|
+
Function(
|
|
275
|
+
name="residual",
|
|
276
|
+
definition=self.residual(),
|
|
277
|
+
args=Zstruct(
|
|
278
|
+
time=self.time,
|
|
279
|
+
position=self.position,
|
|
280
|
+
distance=self.distance,
|
|
281
|
+
variables=self.variables,
|
|
282
|
+
aux_variables=self.aux_variables,
|
|
283
|
+
parameters=self.parameters,
|
|
284
|
+
),
|
|
285
|
+
),
|
|
286
|
+
)
|
|
287
|
+
object.__setattr__(
|
|
288
|
+
self,
|
|
289
|
+
"_project_2d_to_3d",
|
|
290
|
+
Function(
|
|
291
|
+
name="project_2d_to_3d",
|
|
292
|
+
definition=self.project_2d_to_3d(),
|
|
293
|
+
args=Zstruct(
|
|
294
|
+
Z=self.position[2],
|
|
295
|
+
variables=self.variables,
|
|
296
|
+
aux_variables=self.aux_variables,
|
|
297
|
+
parameters=self.parameters,
|
|
298
|
+
),
|
|
299
|
+
),
|
|
300
|
+
)
|
|
301
|
+
object.__setattr__(
|
|
302
|
+
self,
|
|
303
|
+
"_project_3d_to_2d",
|
|
304
|
+
Function(
|
|
305
|
+
name="project_3d_to_2d",
|
|
306
|
+
definition=self.project_3d_to_2d(),
|
|
307
|
+
args=Zstruct(
|
|
308
|
+
variables=self.variables,
|
|
309
|
+
aux_variables=self.aux_variables,
|
|
310
|
+
parameters=self.parameters,
|
|
311
|
+
),
|
|
312
|
+
),
|
|
313
|
+
)
|
|
314
|
+
object.__setattr__(
|
|
315
|
+
self,
|
|
316
|
+
"_boundary_conditions",
|
|
317
|
+
self.boundary_conditions.get_boundary_condition_function(
|
|
318
|
+
self.time,
|
|
319
|
+
self.position,
|
|
320
|
+
self.distance,
|
|
321
|
+
self.variables,
|
|
322
|
+
self.aux_variables,
|
|
323
|
+
self.parameters,
|
|
324
|
+
self.normal,
|
|
325
|
+
),
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
def flux(self):
|
|
329
|
+
return ZArray.zeros(
|
|
330
|
+
self.n_variables,
|
|
331
|
+
self.dimension,
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
def dflux(self):
|
|
335
|
+
return ZArray.zeros(
|
|
336
|
+
self.n_variables,
|
|
337
|
+
self.dimension,
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
def nonconservative_matrix(self):
|
|
341
|
+
return ZArray.zeros(
|
|
342
|
+
self.n_variables,
|
|
343
|
+
self.n_variables,
|
|
344
|
+
self.dimension,
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
def source(self):
|
|
348
|
+
return ZArray.zeros(self.n_variables)
|
|
349
|
+
return zeros(self.n_variables, 1)
|
|
350
|
+
|
|
351
|
+
def quasilinear_matrix(self):
|
|
352
|
+
"""generated automatically unless explicitly provided"""
|
|
353
|
+
# NC = [ZArray.zeros(self.n_variables, self.n_variables) for _ in range(self.dimension)]
|
|
354
|
+
# NC = ZArray.zeros(
|
|
355
|
+
# self.n_variables, self.n_variables, self.dimension
|
|
356
|
+
# )
|
|
357
|
+
JacF = ZArray(sympy.derive_by_array(self.flux(), self.variables.get_list()))
|
|
358
|
+
JacF_d = ZArray.zeros(*JacF.shape)
|
|
359
|
+
for d in range(self.dimension):
|
|
360
|
+
JacF_d = JacF[:, :, d]
|
|
361
|
+
JacF_d = ZArray(JacF_d.tomatrix().T)
|
|
362
|
+
JacF[:, :, d] = JacF_d
|
|
363
|
+
return self._simplify(
|
|
364
|
+
JacF + self.nonconservative_matrix()
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
def source_jacobian_wrt_variables(self):
|
|
368
|
+
"""generated automatically unless explicitly provided"""
|
|
369
|
+
return self._simplify(
|
|
370
|
+
sympy.derive_by_array(self.source(), self.variables.get_list())
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
def source_jacobian_wrt_aux_variables(self):
|
|
374
|
+
"""generated automatically unless explicitly provided"""
|
|
375
|
+
return self._simplify(
|
|
376
|
+
sympy.derive_by_array(self.source(), self.aux_variables.get_list())
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
def residual(self):
|
|
380
|
+
return ZArray.zeros(self.n_variables)
|
|
381
|
+
|
|
382
|
+
def project_2d_to_3d(self):
|
|
383
|
+
return ZArray.zeros(6)
|
|
384
|
+
|
|
385
|
+
def project_3d_to_2d(self):
|
|
386
|
+
return ZArray.zeros(self.n_variables)
|
|
387
|
+
|
|
388
|
+
def eigenvalues(self):
|
|
389
|
+
A = self.normal[0] * self.quasilinear_matrix()[:, :, 0]
|
|
390
|
+
for d in range(1, self.dimension):
|
|
391
|
+
A += self.normal[d] * self.quasilinear_matrix()[:, :, d]
|
|
392
|
+
return self._simplify(eigenvalue_dict_to_matrix(sympy.Matrix(A).eigenvals()))
|
|
393
|
+
|
|
394
|
+
def left_eigenvectors(self):
|
|
395
|
+
return ZArray.zeros(self.n_variables, self.n_variables)
|
|
396
|
+
|
|
397
|
+
def right_eigenvectors(self):
|
|
398
|
+
return ZArray.zeros(self.n_variables, self.n_variables)
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def substitute_precomputed_denominator(self, expr, sym, sym_inv):
|
|
402
|
+
"""Recursively replace denominators involving `sym` with precomputed `sym_inv`.
|
|
403
|
+
Works for scalar, Matrix, or Array of any rank.
|
|
404
|
+
"""
|
|
405
|
+
# --- Case 1: Matrix (2D) ---
|
|
406
|
+
if isinstance(expr, sympy.MatrixBase):
|
|
407
|
+
return expr.applyfunc(
|
|
408
|
+
lambda e: self.substitute_precomputed_denominator(e, sym, sym_inv)
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
# --- Case 2: Array (any rank) ---
|
|
412
|
+
if isinstance(expr, sympy.Array):
|
|
413
|
+
# Recursively apply substitution elementwise
|
|
414
|
+
new_data = [
|
|
415
|
+
self.substitute_precomputed_denominator(e, sym, sym_inv) for e in expr
|
|
416
|
+
]
|
|
417
|
+
return sympy.ZArray(new_data).reshape(*expr.shape)
|
|
418
|
+
|
|
419
|
+
# --- Case 3: Scalar or general SymPy expression ---
|
|
420
|
+
num, den = sympy.fraction(expr)
|
|
421
|
+
|
|
422
|
+
if den.has(sym):
|
|
423
|
+
# split denominator into sym-dependent and independent parts
|
|
424
|
+
den_sym, den_rest = den.as_independent(sym, as_Add=False)
|
|
425
|
+
# swap naming (as_independent returns independent first)
|
|
426
|
+
den_rest, den_sym = den_sym, den_rest
|
|
427
|
+
|
|
428
|
+
# replace sym by sym_inv in the sym-dependent part
|
|
429
|
+
den_sym_repl = den_sym.xreplace({sym: sym_inv})
|
|
430
|
+
|
|
431
|
+
return (
|
|
432
|
+
self.substitute_precomputed_denominator(num, sym, sym_inv)
|
|
433
|
+
* den_sym_repl
|
|
434
|
+
/ den_rest
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
# recurse through function arguments
|
|
438
|
+
elif hasattr(expr, "args") and expr.args:
|
|
439
|
+
return expr.func(
|
|
440
|
+
*[
|
|
441
|
+
self.substitute_precomputed_denominator(arg, sym, sym_inv)
|
|
442
|
+
for arg in expr.args
|
|
443
|
+
]
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
# base case: atomic expression
|
|
447
|
+
else:
|
|
448
|
+
return expr
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def transform_positive_variable_intput_to_list(argument, positive, n_variables):
|
|
452
|
+
out = [False for _ in range(n_variables)]
|
|
453
|
+
if positive is None:
|
|
454
|
+
return out
|
|
455
|
+
if type(positive) == type({}):
|
|
456
|
+
assert type(argument) == type(positive)
|
|
457
|
+
for i, a in enumerate(argument.keys()):
|
|
458
|
+
if a in positive.keys():
|
|
459
|
+
out[i] = positive[a]
|
|
460
|
+
if type(positive) == list:
|
|
461
|
+
for i in positive:
|
|
462
|
+
out[i] = True
|
|
463
|
+
return out
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def register_sympy_attribute(argument, string_identifier="q_", positives=None):
|
|
467
|
+
if type(argument) == int:
|
|
468
|
+
positive = transform_positive_variable_intput_to_list(
|
|
469
|
+
argument, positives, argument
|
|
470
|
+
)
|
|
471
|
+
attributes = {
|
|
472
|
+
string_identifier + str(i): sympy.symbols(
|
|
473
|
+
string_identifier + str(i), real=True, positive=positive[i]
|
|
474
|
+
)
|
|
475
|
+
for i in range(argument)
|
|
476
|
+
}
|
|
477
|
+
elif type(argument) == type({}):
|
|
478
|
+
positive = transform_positive_variable_intput_to_list(
|
|
479
|
+
argument, positives, len(argument)
|
|
480
|
+
)
|
|
481
|
+
attributes = {
|
|
482
|
+
name: sympy.symbols(str(name), real=True, positive=pos)
|
|
483
|
+
for name, pos in zip(argument.keys(), positive)
|
|
484
|
+
}
|
|
485
|
+
elif type(argument) == list:
|
|
486
|
+
positive = transform_positive_variable_intput_to_list(
|
|
487
|
+
argument, positives, len(argument)
|
|
488
|
+
)
|
|
489
|
+
attributes = {
|
|
490
|
+
name: sympy.symbols(str(name), real=True, positive=pos)
|
|
491
|
+
for name, pos in zip(argument, positive)
|
|
492
|
+
}
|
|
493
|
+
else:
|
|
494
|
+
assert False
|
|
495
|
+
return Zstruct(**attributes)
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def register_parameter_values(parameters):
|
|
499
|
+
if type(parameters) == int:
|
|
500
|
+
default_values = np.zeros(parameters, dtype=float)
|
|
501
|
+
elif type(parameters) == type({}):
|
|
502
|
+
default_values = np.array([value for value in parameters.values()])
|
|
503
|
+
else:
|
|
504
|
+
assert False
|
|
505
|
+
return default_values
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
def eigenvalue_dict_to_matrix(eigenvalues, simplify=default_simplify):
|
|
509
|
+
evs = []
|
|
510
|
+
for ev, mult in eigenvalues.items():
|
|
511
|
+
for i in range(mult):
|
|
512
|
+
evs.append(simplify(ev))
|
|
513
|
+
return ZArray(evs)
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from time import time as get_time
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
import sympy
|
|
6
|
+
from sympy import Matrix
|
|
7
|
+
|
|
8
|
+
from attr import define, field
|
|
9
|
+
from typing import Callable, List
|
|
10
|
+
|
|
11
|
+
from zoomy_core.misc.misc import Zstruct, ZArray
|
|
12
|
+
|
|
13
|
+
from zoomy_core.model.basefunction import Function
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@define(slots=True, frozen=False, kw_only=True)
|
|
17
|
+
class BoundaryCondition:
|
|
18
|
+
tag: str
|
|
19
|
+
|
|
20
|
+
"""
|
|
21
|
+
Default implementation. The required data for the 'ghost cell' is the data from the interior cell. Can be overwritten e.g. to implement periodic boundary conditions.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def compute_boundary_condition(self, time, X, dX, Q, Qaux, parameters, normal):
|
|
25
|
+
print("BoundaryCondition is a virtual class. Use one if its derived classes!")
|
|
26
|
+
assert False
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@define(slots=True, frozen=False, kw_only=True)
|
|
30
|
+
class Extrapolation(BoundaryCondition):
|
|
31
|
+
def compute_boundary_condition(self, time, X, dX, Q, Qaux, parameters, normal):
|
|
32
|
+
return ZArray(Q)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@define(slots=True, frozen=False, kw_only=True)
|
|
36
|
+
class InflowOutflow(BoundaryCondition):
|
|
37
|
+
prescribe_fields: dict[int, float]
|
|
38
|
+
|
|
39
|
+
def compute_boundary_condition(self, time, X, dX, Q, Qaux, parameters, normal):
|
|
40
|
+
Qout = ZArray(Q)
|
|
41
|
+
for k, v in self.prescribe_fields.items():
|
|
42
|
+
Qout[k] = eval(v)
|
|
43
|
+
return Qout
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@define(slots=True, frozen=False, kw_only=True)
|
|
47
|
+
class Lambda(BoundaryCondition):
|
|
48
|
+
prescribe_fields: dict[int, float]
|
|
49
|
+
|
|
50
|
+
def compute_boundary_condition(self, time, X, dX, Q, Qaux, parameters, normal):
|
|
51
|
+
Qout = ZArray(Q)
|
|
52
|
+
for k, v in self.prescribe_fields.items():
|
|
53
|
+
Qout[k] = v(time, X, dX, Q, Qaux, parameters, normal)
|
|
54
|
+
return Qout
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _sympy_interpolate_data(time, timeline, data):
|
|
58
|
+
assert timeline.shape[0] == data.shape[0]
|
|
59
|
+
conditions = (((data[0], time <= timeline[0])),)
|
|
60
|
+
for i in range(timeline.shape[0] - 1):
|
|
61
|
+
t0 = timeline[i]
|
|
62
|
+
t1 = timeline[i + 1]
|
|
63
|
+
y0 = data[i]
|
|
64
|
+
y1 = data[i + 1]
|
|
65
|
+
conditions += (
|
|
66
|
+
(-(time - t1) / (t1 - t0) * y0 + (time - t0) / (t1 - t0) * y1, time <= t1),
|
|
67
|
+
)
|
|
68
|
+
conditions += (((data[-1], time > timeline[-1])),)
|
|
69
|
+
return sympy.Piecewise(*conditions)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@define(slots=True, frozen=False, kw_only=True)
|
|
73
|
+
class FromData(BoundaryCondition):
|
|
74
|
+
prescribe_fields: dict[int, np.ndarray]
|
|
75
|
+
timeline: np.ndarray
|
|
76
|
+
|
|
77
|
+
def compute_boundary_condition(self, time, X, dX, Q, Qaux, parameters, normal):
|
|
78
|
+
# Extrapolate all fields
|
|
79
|
+
Qout = ZArray(Q)
|
|
80
|
+
|
|
81
|
+
# Set the fields which are prescribed in boundary condition dict
|
|
82
|
+
time_start = get_time()
|
|
83
|
+
for k, v in self.prescribe_fields.items():
|
|
84
|
+
interp_func = _sympy_interpolate_data(time, self.timeline, v)
|
|
85
|
+
Qout[k] = 2 * interp_func - Q[k]
|
|
86
|
+
return Qout
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@define(slots=True, frozen=False, kw_only=True)
|
|
90
|
+
class Wall(BoundaryCondition):
|
|
91
|
+
"""
|
|
92
|
+
momentum_field_indices: list(int): indicate which fields need to be mirrored at the wall
|
|
93
|
+
permeability: float : 1.0 corresponds to a perfect reflection (impermeable wall)
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
momentum_field_indices: List[List[int]] = [[1, 2]]
|
|
97
|
+
permeability: float = 0.0
|
|
98
|
+
wall_slip: float = 1.0
|
|
99
|
+
|
|
100
|
+
def compute_boundary_condition(self, time, X, dX, Q, Qaux, parameters, normal):
|
|
101
|
+
q = ZArray(Q)
|
|
102
|
+
n_variables = q.shape[0]
|
|
103
|
+
momentum_list = [Matrix([q[k] for k in l]) for l in self.momentum_field_indices]
|
|
104
|
+
dim = momentum_list[0].shape[0]
|
|
105
|
+
n = Matrix(normal[:dim])
|
|
106
|
+
out = ZArray.zeros(n_variables)
|
|
107
|
+
out = q
|
|
108
|
+
momentum_list_wall = []
|
|
109
|
+
for momentum in momentum_list:
|
|
110
|
+
normal_momentum_coef = momentum.dot(n)
|
|
111
|
+
transverse_momentum = momentum - normal_momentum_coef * n
|
|
112
|
+
momentum_wall = (
|
|
113
|
+
self.wall_slip * transverse_momentum
|
|
114
|
+
- (1 - self.permeability) * normal_momentum_coef * n
|
|
115
|
+
)
|
|
116
|
+
momentum_list_wall.append(momentum_wall)
|
|
117
|
+
for l, momentum_wall in zip(self.momentum_field_indices, momentum_list_wall):
|
|
118
|
+
for i_k, k in enumerate(l):
|
|
119
|
+
out[k] = momentum_wall[i_k]
|
|
120
|
+
return out
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@define(slots=True, frozen=False, kw_only=True)
|
|
124
|
+
class RoughWall(Wall):
|
|
125
|
+
CsW: float = 0.5 # roughness constant
|
|
126
|
+
Ks: float = 0.001 # roughness height
|
|
127
|
+
|
|
128
|
+
def compute_boundary_condition(self, time, X, dX, Q, Qaux, parameters, normal):
|
|
129
|
+
slip_length = dX * sympy.ln((dX * self.CsW) / self.Ks)
|
|
130
|
+
# wall_slip = (1-2*dX/slip_length)
|
|
131
|
+
# wall_slip = sympy.Min(sympy.Max(wall_slip, 0.0), 1.0)
|
|
132
|
+
f = dX / slip_length
|
|
133
|
+
wall_slip = (1 - f) / (1 + f)
|
|
134
|
+
self.wall_slip = wall_slip
|
|
135
|
+
return super().compute_boundary_condition(
|
|
136
|
+
time, X, dX, Q, Qaux, parameters, normal
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@define(slots=True, frozen=False, kw_only=True)
|
|
141
|
+
class Periodic(BoundaryCondition):
|
|
142
|
+
periodic_to_physical_tag: str
|
|
143
|
+
|
|
144
|
+
def compute_boundary_condition(self, time, X, dX, Q, Qaux, parameters, normal):
|
|
145
|
+
return ZArray(Q)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@define(slots=True, frozen=True)
|
|
149
|
+
class BoundaryConditions:
|
|
150
|
+
_boundary_conditions: List[BoundaryCondition]
|
|
151
|
+
_boundary_functions: List[Callable] = field(init=False)
|
|
152
|
+
_boundary_tags: List[str] = field(init=False)
|
|
153
|
+
|
|
154
|
+
def __attrs_post_init__(self):
|
|
155
|
+
tags_unsorted = [bc.tag for bc in self._boundary_conditions]
|
|
156
|
+
order = np.argsort(tags_unsorted)
|
|
157
|
+
object.__setattr__(
|
|
158
|
+
self,
|
|
159
|
+
"_boundary_functions",
|
|
160
|
+
[self._boundary_conditions[i].compute_boundary_condition for i in order],
|
|
161
|
+
)
|
|
162
|
+
object.__setattr__(
|
|
163
|
+
self,
|
|
164
|
+
"_boundary_tags",
|
|
165
|
+
[self._boundary_conditions[i].tag for i in order],
|
|
166
|
+
)
|
|
167
|
+
object.__setattr__(
|
|
168
|
+
self, "_boundary_conditions", [self._boundary_conditions[i] for i in order]
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
def get_boundary_condition_function(self, time, X, dX, Q, Qaux, parameters, normal):
|
|
172
|
+
bc_idx = sympy.Symbol("bc_idx", integer=True)
|
|
173
|
+
bc_func = sympy.Piecewise(
|
|
174
|
+
*(
|
|
175
|
+
(func(time, X.get_list(), dX, Q.get_list(), Qaux.get_list(), parameters.get_list(), normal.get_list()), sympy.Eq(bc_idx, i))
|
|
176
|
+
for i, func in enumerate(self._boundary_functions)
|
|
177
|
+
)
|
|
178
|
+
)
|
|
179
|
+
func = Function(
|
|
180
|
+
name="boundary_conditions",
|
|
181
|
+
args=Zstruct(
|
|
182
|
+
idx=bc_idx,
|
|
183
|
+
time=time,
|
|
184
|
+
position=X,
|
|
185
|
+
distance=dX,
|
|
186
|
+
variables=Q,
|
|
187
|
+
aux_variables=Qaux,
|
|
188
|
+
parameters=parameters,
|
|
189
|
+
normal=normal,
|
|
190
|
+
),
|
|
191
|
+
definition=bc_func,
|
|
192
|
+
)
|
|
193
|
+
return func
|