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.
Files changed (57) hide show
  1. decorators/decorators.py +25 -0
  2. fvm/__init__.py +0 -0
  3. fvm/flux.py +52 -0
  4. fvm/nonconservative_flux.py +97 -0
  5. fvm/ode.py +55 -0
  6. fvm/solver_numpy.py +297 -0
  7. fvm/timestepping.py +13 -0
  8. mesh/__init__.py +0 -0
  9. mesh/mesh.py +1239 -0
  10. mesh/mesh_extrude.py +168 -0
  11. mesh/mesh_util.py +487 -0
  12. misc/__init__.py +0 -0
  13. misc/custom_types.py +6 -0
  14. misc/interpolation.py +140 -0
  15. misc/io.py +448 -0
  16. misc/logger_config.py +18 -0
  17. misc/misc.py +218 -0
  18. model/__init__.py +0 -0
  19. model/analysis.py +147 -0
  20. model/basefunction.py +113 -0
  21. model/basemodel.py +513 -0
  22. model/boundary_conditions.py +193 -0
  23. model/initial_conditions.py +171 -0
  24. model/model.py +65 -0
  25. model/models/GN.py +70 -0
  26. model/models/advection.py +53 -0
  27. model/models/basisfunctions.py +181 -0
  28. model/models/basismatrices.py +381 -0
  29. model/models/coupled_constrained.py +60 -0
  30. model/models/poisson.py +41 -0
  31. model/models/shallow_moments.py +757 -0
  32. model/models/shallow_moments_sediment.py +378 -0
  33. model/models/shallow_moments_topo.py +423 -0
  34. model/models/shallow_moments_variants.py +1509 -0
  35. model/models/shallow_water.py +266 -0
  36. model/models/shallow_water_topo.py +111 -0
  37. model/models/shear_shallow_flow.py +594 -0
  38. model/models/sme_turbulent.py +613 -0
  39. model/models/vam.py +455 -0
  40. postprocessing/__init__.py +0 -0
  41. postprocessing/plotting.py +244 -0
  42. postprocessing/postprocessing.py +75 -0
  43. preprocessing/__init__.py +0 -0
  44. preprocessing/openfoam_moments.py +453 -0
  45. transformation/__init__.py +0 -0
  46. transformation/helpers.py +25 -0
  47. transformation/to_amrex.py +241 -0
  48. transformation/to_c.py +185 -0
  49. transformation/to_jax.py +14 -0
  50. transformation/to_numpy.py +118 -0
  51. transformation/to_openfoam.py +258 -0
  52. transformation/to_ufl.py +67 -0
  53. zoomy_core-0.1.14.dist-info/METADATA +52 -0
  54. zoomy_core-0.1.14.dist-info/RECORD +57 -0
  55. zoomy_core-0.1.14.dist-info/WHEEL +5 -0
  56. zoomy_core-0.1.14.dist-info/licenses/LICENSE +674 -0
  57. 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