zoomy-core 0.1.1__py3-none-any.whl → 0.1.3__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.

Potentially problematic release.


This version of zoomy-core might be problematic. Click here for more details.

Files changed (53) hide show
  1. zoomy_core/decorators/decorators.py +25 -0
  2. zoomy_core/fvm/flux.py +97 -0
  3. zoomy_core/fvm/nonconservative_flux.py +97 -0
  4. zoomy_core/fvm/ode.py +55 -0
  5. zoomy_core/fvm/solver_numpy.py +305 -0
  6. zoomy_core/fvm/timestepping.py +13 -0
  7. zoomy_core/mesh/mesh.py +1234 -0
  8. zoomy_core/mesh/mesh_extrude.py +168 -0
  9. zoomy_core/mesh/mesh_util.py +487 -0
  10. zoomy_core/misc/custom_types.py +6 -0
  11. zoomy_core/misc/interpolation.py +140 -0
  12. zoomy_core/misc/io.py +438 -0
  13. zoomy_core/misc/logger_config.py +18 -0
  14. zoomy_core/misc/misc.py +216 -0
  15. zoomy_core/misc/static_class.py +94 -0
  16. zoomy_core/model/analysis.py +147 -0
  17. zoomy_core/model/basefunction.py +113 -0
  18. zoomy_core/model/basemodel.py +512 -0
  19. zoomy_core/model/boundary_conditions.py +193 -0
  20. zoomy_core/model/initial_conditions.py +171 -0
  21. zoomy_core/model/model.py +63 -0
  22. zoomy_core/model/models/GN.py +70 -0
  23. zoomy_core/model/models/advection.py +53 -0
  24. zoomy_core/model/models/basisfunctions.py +181 -0
  25. zoomy_core/model/models/basismatrices.py +377 -0
  26. zoomy_core/model/models/core.py +564 -0
  27. zoomy_core/model/models/coupled_constrained.py +60 -0
  28. zoomy_core/model/models/poisson.py +41 -0
  29. zoomy_core/model/models/shallow_moments.py +757 -0
  30. zoomy_core/model/models/shallow_moments_sediment.py +378 -0
  31. zoomy_core/model/models/shallow_moments_topo.py +423 -0
  32. zoomy_core/model/models/shallow_moments_variants.py +1509 -0
  33. zoomy_core/model/models/shallow_water.py +266 -0
  34. zoomy_core/model/models/shallow_water_topo.py +111 -0
  35. zoomy_core/model/models/shear_shallow_flow.py +594 -0
  36. zoomy_core/model/models/sme_turbulent.py +613 -0
  37. zoomy_core/model/models/vam.py +455 -0
  38. zoomy_core/postprocessing/postprocessing.py +72 -0
  39. zoomy_core/preprocessing/openfoam_moments.py +452 -0
  40. zoomy_core/transformation/helpers.py +25 -0
  41. zoomy_core/transformation/to_amrex.py +238 -0
  42. zoomy_core/transformation/to_c.py +181 -0
  43. zoomy_core/transformation/to_jax.py +14 -0
  44. zoomy_core/transformation/to_numpy.py +115 -0
  45. zoomy_core/transformation/to_openfoam.py +254 -0
  46. zoomy_core/transformation/to_ufl.py +67 -0
  47. {zoomy_core-0.1.1.dist-info → zoomy_core-0.1.3.dist-info}/METADATA +2 -1
  48. zoomy_core-0.1.3.dist-info/RECORD +51 -0
  49. zoomy_core-0.1.3.dist-info/top_level.txt +1 -0
  50. zoomy_core-0.1.1.dist-info/RECORD +0 -5
  51. zoomy_core-0.1.1.dist-info/top_level.txt +0 -1
  52. {zoomy_core-0.1.1.dist-info → zoomy_core-0.1.3.dist-info}/WHEEL +0 -0
  53. {zoomy_core-0.1.1.dist-info → zoomy_core-0.1.3.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,512 @@
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 library.zoomy_core.model.boundary_conditions import BoundaryConditions
10
+ from library.zoomy_core.model.initial_conditions import Constant, InitialConditions
11
+ from library.zoomy_core.misc.custom_types import FArray
12
+ from library.zoomy_core.misc.misc import Zstruct, ZArray
13
+ from library.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
+ for d in range(self.dimension):
359
+ JacF_d = JacF[:, :, d]
360
+ JacF_d = ZArray(JacF_d.tomatrix().T)
361
+ JacF[:, :, d] = JacF_d
362
+ return self._simplify(
363
+ JacF + self.nonconservative_matrix()
364
+ )
365
+
366
+ def source_jacobian_wrt_variables(self):
367
+ """generated automatically unless explicitly provided"""
368
+ return self._simplify(
369
+ sympy.derive_by_array(self.source(), self.variables.get_list())
370
+ )
371
+
372
+ def source_jacobian_wrt_aux_variables(self):
373
+ """generated automatically unless explicitly provided"""
374
+ return self._simplify(
375
+ sympy.derive_by_array(self.source(), self.aux_variables.get_list())
376
+ )
377
+
378
+ def residual(self):
379
+ return ZArray.zeros(self.n_variables)
380
+
381
+ def project_2d_to_3d(self):
382
+ return ZArray.zeros(6)
383
+
384
+ def project_3d_to_2d(self):
385
+ return ZArray.zeros(self.n_variables)
386
+
387
+ def eigenvalues(self):
388
+ A = self.normal[0] * self.quasilinear_matrix()[:, :, 0]
389
+ for d in range(1, self.dimension):
390
+ A += self.normal[d] * self.quasilinear_matrix()[:, :, d]
391
+ return self._simplify(eigenvalue_dict_to_matrix(sympy.Matrix(A).eigenvals()))
392
+
393
+ def left_eigenvectors(self):
394
+ return ZArray.zeros(self.n_variables, self.n_variables)
395
+
396
+ def right_eigenvectors(self):
397
+ return ZArray.zeros(self.n_variables, self.n_variables)
398
+
399
+
400
+ def substitute_precomputed_denominator(self, expr, sym, sym_inv):
401
+ """Recursively replace denominators involving `sym` with precomputed `sym_inv`.
402
+ Works for scalar, Matrix, or Array of any rank.
403
+ """
404
+ # --- Case 1: Matrix (2D) ---
405
+ if isinstance(expr, sympy.MatrixBase):
406
+ return expr.applyfunc(
407
+ lambda e: self.substitute_precomputed_denominator(e, sym, sym_inv)
408
+ )
409
+
410
+ # --- Case 2: Array (any rank) ---
411
+ if isinstance(expr, sympy.Array):
412
+ # Recursively apply substitution elementwise
413
+ new_data = [
414
+ self.substitute_precomputed_denominator(e, sym, sym_inv) for e in expr
415
+ ]
416
+ return sympy.ZArray(new_data).reshape(*expr.shape)
417
+
418
+ # --- Case 3: Scalar or general SymPy expression ---
419
+ num, den = sympy.fraction(expr)
420
+
421
+ if den.has(sym):
422
+ # split denominator into sym-dependent and independent parts
423
+ den_sym, den_rest = den.as_independent(sym, as_Add=False)
424
+ # swap naming (as_independent returns independent first)
425
+ den_rest, den_sym = den_sym, den_rest
426
+
427
+ # replace sym by sym_inv in the sym-dependent part
428
+ den_sym_repl = den_sym.xreplace({sym: sym_inv})
429
+
430
+ return (
431
+ self.substitute_precomputed_denominator(num, sym, sym_inv)
432
+ * den_sym_repl
433
+ / den_rest
434
+ )
435
+
436
+ # recurse through function arguments
437
+ elif hasattr(expr, "args") and expr.args:
438
+ return expr.func(
439
+ *[
440
+ self.substitute_precomputed_denominator(arg, sym, sym_inv)
441
+ for arg in expr.args
442
+ ]
443
+ )
444
+
445
+ # base case: atomic expression
446
+ else:
447
+ return expr
448
+
449
+
450
+ def transform_positive_variable_intput_to_list(argument, positive, n_variables):
451
+ out = [False for _ in range(n_variables)]
452
+ if positive is None:
453
+ return out
454
+ if type(positive) == type({}):
455
+ assert type(argument) == type(positive)
456
+ for i, a in enumerate(argument.keys()):
457
+ if a in positive.keys():
458
+ out[i] = positive[a]
459
+ if type(positive) == list:
460
+ for i in positive:
461
+ out[i] = True
462
+ return out
463
+
464
+
465
+ def register_sympy_attribute(argument, string_identifier="q_", positives=None):
466
+ if type(argument) == int:
467
+ positive = transform_positive_variable_intput_to_list(
468
+ argument, positives, argument
469
+ )
470
+ attributes = {
471
+ string_identifier + str(i): sympy.symbols(
472
+ string_identifier + str(i), real=True, positive=positive[i]
473
+ )
474
+ for i in range(argument)
475
+ }
476
+ elif type(argument) == type({}):
477
+ positive = transform_positive_variable_intput_to_list(
478
+ argument, positives, len(argument)
479
+ )
480
+ attributes = {
481
+ name: sympy.symbols(str(name), real=True, positive=pos)
482
+ for name, pos in zip(argument.keys(), positive)
483
+ }
484
+ elif type(argument) == list:
485
+ positive = transform_positive_variable_intput_to_list(
486
+ argument, positives, len(argument)
487
+ )
488
+ attributes = {
489
+ name: sympy.symbols(str(name), real=True, positive=pos)
490
+ for name, pos in zip(argument, positive)
491
+ }
492
+ else:
493
+ assert False
494
+ return Zstruct(**attributes)
495
+
496
+
497
+ def register_parameter_values(parameters):
498
+ if type(parameters) == int:
499
+ default_values = np.zeros(parameters, dtype=float)
500
+ elif type(parameters) == type({}):
501
+ default_values = np.array([value for value in parameters.values()])
502
+ else:
503
+ assert False
504
+ return default_values
505
+
506
+
507
+ def eigenvalue_dict_to_matrix(eigenvalues, simplify=default_simplify):
508
+ evs = []
509
+ for ev, mult in eigenvalues.items():
510
+ for i in range(mult):
511
+ evs.append(simplify(ev))
512
+ 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 library.zoomy_core.misc.misc import Zstruct, ZArray
12
+
13
+ from library.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 = Matrix(Q.get_list())
102
+ n_variables = Q.length()
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 = Matrix.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