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,613 @@
1
+ import numpy as np
2
+ import numpy.polynomial.legendre as L
3
+ import numpy.polynomial.chebyshev as C
4
+ from scipy.optimize import least_squares as lsq
5
+ import sympy
6
+ from sympy import Matrix, Piecewise
7
+ from sympy.abc import x
8
+
9
+ from sympy import integrate, diff
10
+ from sympy import legendre
11
+ from sympy import lambdify
12
+
13
+ from attrs import define, field
14
+ import attr
15
+ from typing import Union, Dict, List
16
+
17
+ from sympy.functions.elementary.exponential import LambertW
18
+
19
+
20
+ from library.zoomy_core.model.basemodel import (
21
+ register_sympy_attribute,
22
+ eigenvalue_dict_to_matrix,
23
+ )
24
+ from library.zoomy_core.model.basemodel import Model
25
+ from library.zoomy_core.model.models.basismatrices import Basismatrices
26
+ from library.zoomy_core.model.models.basisfunctions import Legendre_shifted, Basisfunction
27
+
28
+ from sympy.integrals.quadrature import gauss_legendre
29
+
30
+
31
+
32
+
33
+ @define(frozen=True, slots=True, kw_only=True)
34
+ class SMET(Model):
35
+ dimension: int = 2
36
+ level: int
37
+ variables: Union[list, int] = field(init=False)
38
+ positive_variables: Union[List[int], Dict[str, int], None] = attr.ib(default=attr.Factory(lambda: [1]))
39
+ aux_variables: Union[list, int] = field(default=0)
40
+ basisfunctions: Union[Basisfunction, type[Basisfunction]] = field(default=Legendre_shifted)
41
+ basismatrices: Basismatrices = field(init=False)
42
+ order_numerical_integration: int = 4
43
+
44
+ _default_parameters: dict = field(
45
+ init=False,
46
+ factory=lambda: {"g": 9.81, "ex": 0.0, "ey": 0.0, "ez": 1.0, "eps_low_water": 1e-6, "rho": 1000., 'nu': 1e-6, 'kappa': 0.41, 'B': 5.2, 'Cs': 0.17, 'yp': 0.1, 'CsW': 0.5, 'Ks': 0.001},
47
+ )
48
+
49
+ def __attrs_post_init__(self):
50
+ object.__setattr__(self, "variables", ((self.level+1)*self.dimension)+2)
51
+ # [dQ_dx, dQ_dy, deltaX, user-defined]
52
+ object.__setattr__(self, "aux_variables", 2*((self.level+1)*self.dimension+2)+1+self.aux_variables)
53
+ super().__attrs_post_init__()
54
+ aux_variables = self.aux_variables
55
+ aux_var_list = aux_variables.keys()
56
+ object.__setattr__(self, "aux_variables", register_sympy_attribute(aux_var_list, "qaux_"))
57
+
58
+ # Recompute basis matrices
59
+ object.__setattr__(self, "basisfunctions", self.basisfunctions(level=self.level))
60
+ basismatrices = Basismatrices(self.basisfunctions)
61
+ basismatrices.compute_matrices(self.level)
62
+ object.__setattr__(self, "basismatrices", basismatrices)
63
+
64
+ def get_primitives(self):
65
+ offset = self.level + 1
66
+ b = self.variables[0]
67
+ h = self.variables[1]
68
+ hinv = 1/h
69
+ ha = self.variables[2 : 2 + self.level + 1]
70
+ alpha = [ha[i] * hinv for i in range(offset)]
71
+ if self.dimension == 1:
72
+ hb = [0 for i in range(self.level+1)]
73
+ else:
74
+ hb = self.variables[2 + offset : 2 + offset + self.level + 1]
75
+ beta = [hb[i] * hinv for i in range(offset)]
76
+ return [b, h, alpha, beta, hinv]
77
+
78
+ def get_gradient(self):
79
+ offset = self.level + 1
80
+ z = sympy.Symbol('z')
81
+ phi = [self.basisfunctions.eval(k, z) for k in range(self.level+1)]
82
+
83
+
84
+ if self.dimension == 1:
85
+ grad_b = [self.aux_variables[0]]
86
+ grad_h = [self.aux_variables[1]]
87
+ dalpha_dx = self.aux_variables[2:2+offset]
88
+ dU_dx = sum([dalpha_dx[k] * phi[k] for k in range(self.level+1)])
89
+ grad_alpha = [dalpha_dx]
90
+ grad_beta = [0 for i in range(offset)]
91
+ grad_U = [[dU_dx, 0], [0, 0]]
92
+ elif self.dimension == 2:
93
+ gradient_offset = self.n_variables
94
+ grad_b = [self.aux_variables[0], self.aux_variables[0+gradient_offset]]
95
+ grad_h = [self.aux_variables[1], self.aux_variables[1+gradient_offset]]
96
+ dalpha_dx = self.aux_variables[2:2+offset]
97
+ dalpha_dy = self.aux_variables[2+gradient_offset: 2+gradient_offset+offset]
98
+ dU_dx = sum([dalpha_dx[k] * phi[k] for k in range(self.level+1)])
99
+ dU_dy = sum([dalpha_dy[k] * phi[k] for k in range(self.level+1)])
100
+ grad_alpha = [dalpha_dx, dalpha_dy]
101
+
102
+ dbeta_dx = self.aux_variables[2+offset: 2+2*offset]
103
+ dbeta_dy = self.aux_variables[2+offset+gradient_offset: 2+2*offset+gradient_offset]
104
+ dV_dx = sum([dbeta_dx[k] * phi[k] for k in range(self.level+1)])
105
+ dV_dy = sum([dbeta_dy[k] * phi[k] for k in range(self.level+1)])
106
+ grad_beta = [dbeta_dx, dbeta_dy]
107
+ grad_U = [[dU_dx, dU_dy], [dV_dx, dV_dy]]
108
+ else:
109
+ assert False
110
+
111
+
112
+
113
+ return grad_b, grad_h, grad_alpha, grad_beta, grad_U
114
+
115
+ def get_Up(self):
116
+ b, h, alpha, beta, hinv = self.get_primitives()
117
+ phi = self.basisfunctions.basis
118
+ z = sympy.Symbol('z')
119
+
120
+ # velocity components at bottom (z=0)
121
+ Up = sum(alpha[k] * phi[k].subs(z, 0) for k in range(self.level + 1))
122
+ Vp = 0
123
+ if self.dimension == 2:
124
+ Vp = sum(beta[k] * phi[k].subs(z, 0) for k in range(self.level + 1))
125
+ return [Up, Vp]
126
+
127
+ def get_ustar(self):
128
+ p = self.parameters
129
+ b, h, alpha, beta, hinv = self.get_primitives()
130
+ phi = self.basisfunctions.basis
131
+ z = sympy.Symbol('z')
132
+
133
+ [Up, Vp] = self.get_Up()
134
+
135
+ # magnitude
136
+ Umag = sympy.sqrt(Up**2 + Vp**2)
137
+ ReU = (Umag * p.yp) / p.nu
138
+
139
+
140
+ # smooth-wall fallback if Ks ~ 0
141
+ smooth = (p.Ks < 1e-8)
142
+
143
+ u_star = sympy.Piecewise(
144
+ # viscous sublayer
145
+ (sympy.sqrt(Umag * p.nu / p.yp), ReU < 30),
146
+ # rough wall (OpenFOAM form)
147
+ (p.kappa * Umag / sympy.log((p.yp * p.CsW) / p.Ks), sympy.Not(smooth)),
148
+ # smooth wall (Lambert W)
149
+ (p.kappa * Umag / sympy.LambertW((p.kappa * Umag * p.yp / p.nu)
150
+ * sympy.exp(p.kappa * p.B)), True)
151
+ )
152
+
153
+
154
+ return u_star
155
+
156
+
157
+ def get_taub(self):
158
+
159
+ p = self.parameters
160
+ b, h, alpha, beta, hinv = self.get_primitives()
161
+ phi = self.basisfunctions.basis
162
+ z = sympy.Symbol('z')
163
+
164
+ [Up, Vp] = self.get_Up()
165
+ Umag = sympy.sqrt(Up**2 + Vp**2)
166
+
167
+ u_star = self.get_ustar()
168
+
169
+ eps = 1e-12
170
+ # tangential direction (normalized velocity vector)
171
+ t_hat_x = Up / (Umag + eps)
172
+ t_hat_y = Vp / (Umag + eps)
173
+
174
+ # shear stress components (vector form)
175
+ tau_zx = - u_star**2 * t_hat_x
176
+ tau_zy = - u_star**2 * t_hat_y
177
+
178
+ # tau_zx = - 1e-4 * (Up - 0) /0.001
179
+ # tau_zy = - 1e-4 * (Vp - 0) /0.001
180
+ return [tau_zx, tau_zy]
181
+
182
+ def get_taub_linear_gradient(self):
183
+
184
+ p = self.parameters
185
+ b, h, alpha, beta, hinv = self.get_primitives()
186
+ phi = self.basisfunctions.basis
187
+ z = sympy.Symbol('z')
188
+
189
+ [Up, Vp] = self.get_Up()
190
+
191
+ tau_zx = - (p.nu + self.lm().subs(z, 0) * self.abs_Sij().subs(z, 0)) * (Up - 0) / p.yp
192
+ tau_zy = - (p.nu + self.lm().subs(z, 0) * self.abs_Sij().subs(z,0)) * (Vp - 0) / p.yp
193
+ return [tau_zx, tau_zy]
194
+
195
+
196
+ def get_deltaX(self):
197
+ gradient_offset = self.n_variables
198
+ return self.aux_variables[2*gradient_offset]
199
+
200
+
201
+ def project_2d_to_3d(self):
202
+ out = Matrix([0 for i in range(6)])
203
+ level = self.level
204
+ offset = level+1
205
+ offset_aux = self.n_aux_variables
206
+ x = self.position[0]
207
+ y = self.position[1]
208
+ z = self.position[2]
209
+
210
+ b, h, alpha, beta, hinv = self.get_primitives()
211
+ grad_b, grad_h, grad_alpha, grad_beta, grad_U = self.get_gradient()
212
+
213
+ psi = [self.basisfunctions.eval_psi(k, z) for k in range(level+1)]
214
+ phi = [self.basisfunctions.eval(k, z) for k in range(level+1)]
215
+
216
+ rho_w = 1000.
217
+ g = 9.81
218
+ u_3d = self.basismatrices.basisfunctions.reconstruct_velocity_profile_at(alpha, z)
219
+ v_3d = 0
220
+ def dot(a, b):
221
+ s = 0
222
+ for i in range(len(a)):
223
+ s += a[i] * b[i]
224
+ return s
225
+ w_3d = - grad_h[0] * dot(alpha,psi) - h * dot(grad_alpha[0],psi) + dot(alpha, phi) * (z * grad_h[0] + grad_b[0])
226
+ if self.dimension == 2:
227
+ v_3d = self.basismatrices.basisfunctions.reconstruct_velocity_profile_at(beta, z)
228
+ w_3d += - grad_h[1] * dot(beta,psi) - h * dot(grad_beta[1],psi) + dot(beta, phi) * (z * grad_h[1] + grad_b[1])
229
+
230
+ out[0] = b
231
+ out[1] = h
232
+ out[2] = u_3d
233
+ out[3] = v_3d
234
+ out[4] = w_3d
235
+ out[5] = rho_w * g * h * (1-z)
236
+ return out
237
+
238
+ def flux(self):
239
+ flux_x = Matrix([0 for i in range(self.n_variables)])
240
+ flux_y = Matrix([0 for i in range(self.n_variables)])
241
+ b, h, alpha, beta, hinv = self.get_primitives()
242
+ p = self.parameters
243
+ flux_x[1] = h * alpha[0]
244
+ flux_x[2] = p.g * p.ez * h * h / 2
245
+ for k in range(self.level + 1):
246
+ for i in range(self.level + 1):
247
+ for j in range(self.level + 1):
248
+ flux_x[k + 2] += (
249
+ h * alpha[i] * alpha[j]
250
+ * self.basismatrices.A[k, i, j]
251
+ / self.basismatrices.M[k, k]
252
+ )
253
+ if self.dimension == 2:
254
+ offset = self.level + 1
255
+ p = self.parameters
256
+ for k in range(self.level + 1):
257
+ for i in range(self.level + 1):
258
+ for j in range(self.level + 1):
259
+ flux_x[1 + k + 1 + offset] += (
260
+ h * beta[i] * alpha[j]
261
+ * self.basismatrices.A[k, i, j]
262
+ / self.basismatrices.M[k, k]
263
+ )
264
+
265
+ flux_y[1] = h * beta[0]
266
+ flux_y[2 + offset] = p.g * p.ez * h * h / 2
267
+ for k in range(self.level + 1):
268
+ for i in range(self.level + 1):
269
+ for j in range(self.level + 1):
270
+ flux_y[1 + k + 1] += (
271
+ h * beta[i] * alpha[j]
272
+ * self.basismatrices.A[k, i, j]
273
+ / self.basismatrices.M[k, k]
274
+ )
275
+ for k in range(self.level + 1):
276
+ for i in range(self.level + 1):
277
+ for j in range(self.level + 1):
278
+ flux_y[1 + k + 1 + offset] += (
279
+ h * beta[i] * beta[j]
280
+ * self.basismatrices.A[k, i, j]
281
+ / self.basismatrices.M[k, k]
282
+ )
283
+ return [flux_x, flux_y][:self.dimension]
284
+
285
+ def nonconservative_matrix(self):
286
+ nc_x = Matrix([[0 for i in range(self.n_variables)] for j in range(self.n_variables)])
287
+ nc_y = Matrix([[0 for i in range(self.n_variables)] for j in range(self.n_variables)])
288
+ b, h, alpha, beta, hinv = self.get_primitives()
289
+ p = self.parameters
290
+ um = alpha[0]
291
+ for k in range(1, self.level + 1):
292
+ nc_x[1+k + 1, 1+k + 1] += um
293
+ for k in range(self.level + 1):
294
+ for i in range(1, self.level + 1):
295
+ for j in range(1, self.level + 1):
296
+ nc_x[1+k + 1, 1+i + 1] -= (
297
+ alpha[j]
298
+ * self.basismatrices.B[k, i, j]
299
+ / self.basismatrices.M[k, k]
300
+ )
301
+ if self.dimension == 2:
302
+ offset = self.level + 1
303
+ b, h, alpha, beta, hinv = self.get_primitives()
304
+ p = self.parameters
305
+ um = alpha[0]
306
+ vm = beta[0]
307
+ for k in range(1, self.level + 1):
308
+ nc_y[1+k + 1, 1+k + 1 + offset] += um
309
+ for k in range(self.level + 1):
310
+ for i in range(1, self.level + 1):
311
+ for j in range(1, self.level + 1):
312
+ nc_x[1+k + 1, 1+i + 1] -= (
313
+ alpha[j]
314
+ * self.basismatrices.B[k, i, j]
315
+ / self.basismatrices.M[k, k]
316
+ )
317
+ nc_y[1+k + 1, 1+i + 1 + offset] -= (
318
+ alpha[j]
319
+ * self.basismatrices.B[k, i, j]
320
+ / self.basismatrices.M[k, k]
321
+ )
322
+
323
+ for k in range(1, self.level + 1):
324
+ nc_x[1+k + 1 + offset, 1+k + 1] += vm
325
+ nc_y[1+k + 1 + offset, 1+k + 1 + offset] += vm
326
+ for k in range(self.level + 1):
327
+ for i in range(1, self.level + 1):
328
+ for j in range(1, self.level + 1):
329
+ nc_x[1+k + 1 + offset, 1+i + 1] -= (
330
+ beta[j]
331
+ * self.basismatrices.B[k, i, j]
332
+ / self.basismatrices.M[k, k]
333
+ )
334
+ nc_y[1+k + 1 + offset, 1+i + 1 + offset] -= (
335
+ beta[j]
336
+ * self.basismatrices.B[k, i, j]
337
+ / self.basismatrices.M[k, k]
338
+ )
339
+ return [-nc_x, -nc_y][:self.dimension]
340
+
341
+ def eigenvalues(self):
342
+ # we delete heigher order moments (level >= 2) for analytical eigenvalues
343
+ offset = self.level + 1
344
+ A = self.normal[0] * self.quasilinear_matrix()[0]
345
+ for d in range(1, self.dimension):
346
+ A += self.normal[d] * self.quasilinear_matrix()[d]
347
+ b, h, alpha, beta, hinv = self.get_primitives()
348
+ alpha_erase = alpha[1:] if self.level >= 2 else []
349
+ beta_erase = beta[1:] if self.level >= 2 else []
350
+ for alpha_i in alpha_erase:
351
+ A = A.subs(alpha_i, 0)
352
+ for beta_i in beta_erase:
353
+ A = A.subs(beta_i, 0)
354
+ return eigenvalue_dict_to_matrix(A.eigenvals())
355
+
356
+
357
+ def Sij(self):
358
+ out = sympy.zeros(self.dimension, self.dimension)
359
+
360
+ grad_b, grad_h, grad_alpha, grad_beta, grad_U = self.get_gradient()
361
+ for d1 in range(self.dimension):
362
+ for d2 in range(self.dimension):
363
+ out[d1, d2] = 0.5 * (grad_U[d1][d2] + grad_U[d2][d1])
364
+
365
+ return out
366
+
367
+ def abs_Sij(self):
368
+ Sij = self.Sij()
369
+ out = 0
370
+ for i in range(self.dimension):
371
+ for j in range(self.dimension):
372
+ out += Sij[i,j]**2
373
+ out = sympy.sqrt(2 * out)
374
+ return out
375
+
376
+ def Szj(self):
377
+ out = sympy.zeros(self.dimension)
378
+
379
+ b, h, alpha, beta, hinv = self.get_primitives()
380
+ dphi_dz = self.basisfunctions.get_diff_basis()
381
+
382
+ for k in range(self.level+1):
383
+ out[0] += alpha[k] * dphi_dz[k]
384
+ if self.dimension == 2:
385
+ for k in range(self.level+1):
386
+ out[1] += beta[k] * dphi_dz[k]
387
+ return out
388
+
389
+ def source(self):
390
+ out = Matrix([0 for i in range(self.n_variables)])
391
+ out += self.mixing_length_vertical()
392
+ # out += self.mixing_length_vertical_linear_gradient()
393
+ out += self.smagorinsky_source()
394
+ return out
395
+
396
+ def source_implicit(self):
397
+ out = sympy.zeros(self.n_variables, self.n_variables)
398
+ # out += self.taub_implicit()
399
+ return out
400
+
401
+ def taub_implicit(self):
402
+ out = sympy.zeros(self.n_variables, self.n_variables)
403
+ b, h, alpha, beta, hinv = self.get_primitives()
404
+ phi = self.basisfunctions.basis
405
+ z = sympy.Symbol('z')
406
+ u_star = self.get_ustar()
407
+ Up = self.get_Up()
408
+ magU = sympy.sqrt(Up[0]**2 + Up[1]**2)
409
+ eps = 10**(-8)
410
+ for d in range(self.dimension):
411
+ term = u_star**2 / h /(magU+eps)
412
+ for k in range(self.level+1):
413
+ for i in range(self.level+1):
414
+ ik = 2 + k + d * (self.level + 1)
415
+ ii = 2 + i + d * (self.level + 1)
416
+ out[ik, ii] += term * phi[k].subs(z, 0) * phi[i].subs(z, 0) / self.basismatrices.M[k, k]
417
+ return out
418
+
419
+ def dflux(self):
420
+ return self.smagorinsky_dflux()
421
+
422
+ def smagorinsky_dflux(self):
423
+ """
424
+ diffusive flux due to the Smagorinsky model
425
+ """
426
+ assert "Cs" in vars(self.parameters)
427
+
428
+ dflux = [sympy.zeros(self.n_variables, 1) for d in range(self.dimension)]
429
+ z = sympy.Symbol('z')
430
+ level = self.level
431
+ offset = level+1
432
+ phi = [self.basisfunctions.eval(k, z) for k in range(level+1)]
433
+ dX = self.get_deltaX()
434
+ Cs = self.parameters.Cs
435
+
436
+ xi, wi = gauss_legendre(self.order_numerical_integration, 8)
437
+ xi = [0.5 * (x + 1) for x in xi]
438
+ wi = [0.5 * w for w in wi]
439
+ b, h, alpha, beta, hinv = self.get_primitives()
440
+
441
+ abs_Sij = self.abs_Sij()
442
+ Sij = self.Sij()
443
+
444
+ for i in range(len(xi)):
445
+ zi = xi[i]
446
+ for k in range(level+1):
447
+ for d1 in range(self.dimension):
448
+ for d2 in range(self.dimension):
449
+ dflux[d1][2 + d2 * offset + k, 0] += wi[i] * h* phi[k].subs(z, zi) * 2 * (Cs * dX)**2 * abs_Sij.subs(z, zi) * Sij[d1, d2].subs(z, zi)
450
+ for k in range(level+1):
451
+ for d in range(self.dimension):
452
+ dflux[d][2+k, 0] /= self.basismatrices.M[k, k]
453
+ return [dflux[d] for d in range(self.dimension)]
454
+
455
+
456
+
457
+
458
+ def smagorinsky_source(self):
459
+ out = Matrix([0 for i in range(self.n_variables)])
460
+ z = sympy.Symbol('z')
461
+ level = self.level
462
+ offset = level+1
463
+ phi = self.basisfunctions.basis
464
+ dphi_dz = self.basisfunctions.get_diff_basis()
465
+ dX = self.get_deltaX()
466
+ Cs = self.parameters.Cs
467
+
468
+ xi, wi = gauss_legendre(self.order_numerical_integration, 8)
469
+ xi = [0.5 * (x + 1) for x in xi]
470
+ wi = [0.5 * w for w in wi]
471
+ b, h, alpha, beta, hinv = self.get_primitives()
472
+ grad_b, grad_h, grad_alpha, grad_beta, grad_U = self.get_gradient()
473
+ sigma = [grad_b[0] + z * grad_h[0], grad_b[1] + z * grad_h[1]]
474
+
475
+
476
+ abs_Sij = self.abs_Sij()
477
+ Sij = self.Sij()
478
+
479
+ taub = self.get_taub()
480
+ for d in range(self.dimension):
481
+ for k in range(1 + self.level):
482
+ out[2+k + d*offset] += sigma[d].subs(z, 0) * taub[d] * phi[k].subs(z, 0) / self.basismatrices.M[k, k]
483
+
484
+
485
+ for i in range(len(xi)):
486
+ zi = xi[i]
487
+ for k in range(level+1):
488
+ for d1 in range(self.dimension):
489
+ for d2 in range(self.dimension):
490
+ out[2 + d2 * offset + k] -= wi[i] * sigma[d1].subs(z, zi) * 2 * (Cs * dX)**2 * abs_Sij.subs(z, zi) * Sij[d1, d2].subs(z, zi) * dphi_dz[k].subs(z, zi) / self.basismatrices.M[k, k]
491
+ return out
492
+
493
+ def lm(self):
494
+ p = self.parameters
495
+ b, h, alpha, beta, hinv = self.get_primitives()
496
+ z = sympy.Symbol('z')
497
+ return p.kappa * (z + p.yp) * (1 - z)
498
+
499
+ def mixing_length_numerical_term(self):
500
+ assert "kappa" in vars(self.parameters)
501
+ out = 0
502
+ b, h, alpha, beta, hinv = self.get_primitives()
503
+ z = sympy.Symbol('z')
504
+ phi = [self.basisfunctions.eval(k, z) for k in range(self.level+1)]
505
+ u = sum([alpha[k] * phi[k] for k in range(self.level+1)])
506
+ v = sum([beta[k] * phi[k] for k in range(self.level+1)] )
507
+ U = [u,v][:self.dimension]
508
+
509
+ for d1 in range(self.dimension):
510
+ for d2 in range(self.dimension):
511
+ out += U[d1] * U[d2]
512
+
513
+ eps = 1e-10
514
+ out += eps
515
+ out = sympy.sqrt(out)
516
+ out *= (self.lm())**2
517
+ return out
518
+
519
+
520
+ def mixing_length_vertical(self):
521
+ out = Matrix([0 for i in range(self.n_variables)])
522
+ offset = self.level + 1
523
+ b, h, alpha, beta, hinv = self.get_primitives()
524
+ p = self.parameters
525
+ z = sympy.Symbol('z')
526
+ phi = [self.basisfunctions.eval(k, z) for k in range(self.level+1)]
527
+ dphi_dz = self.basisfunctions.get_diff_basis()
528
+
529
+ Szj = self.Szj()
530
+
531
+ taub = self.get_taub()
532
+ for d in range(self.dimension):
533
+ for k in range(1 + self.level):
534
+ out[2+k + d*offset] += taub[d] * phi[k].subs(z, 0) / self.basismatrices.M[k, k]
535
+
536
+ xi, wi = gauss_legendre(self.order_numerical_integration, 8)
537
+ xi = [0.5 * (x + 1) for x in xi]
538
+ wi = [0.5 * w for w in wi]
539
+
540
+ for d in range(self.dimension):
541
+ for i in range(len(xi)):
542
+ for k in range(self.level+1):
543
+ out[2+k+d*offset] -= wi[i] * self.mixing_length_numerical_term().subs(z, xi[i]) * Szj[d].subs(z, xi[i]) * dphi_dz[k].subs(z, xi[i]) / self.basismatrices.M[k, k]
544
+
545
+ return out
546
+
547
+ def mixing_length_vertical_linear_gradient(self):
548
+ out = Matrix([0 for i in range(self.n_variables)])
549
+ offset = self.level + 1
550
+ b, h, alpha, beta, hinv = self.get_primitives()
551
+ p = self.parameters
552
+ z = sympy.Symbol('z')
553
+ phi = [self.basisfunctions.eval(k, z) for k in range(self.level+1)]
554
+ dphi_dz = self.basisfunctions.get_diff_basis()
555
+
556
+ Szj = self.Szj()
557
+
558
+ taub = self.get_taub_linear_gradient()
559
+ for d in range(self.dimension):
560
+ for k in range(1 + self.level):
561
+ out[2+k + d*offset] += taub[d] * phi[k].subs(z, 0) / self.basismatrices.M[k, k]
562
+
563
+ xi, wi = gauss_legendre(self.order_numerical_integration, 8)
564
+ xi = [0.5 * (x + 1) for x in xi]
565
+ wi = [0.5 * w for w in wi]
566
+
567
+ for d in range(self.dimension):
568
+ for i in range(len(xi)):
569
+ for k in range(self.level+1):
570
+ out[2+k+d*offset] -= wi[i] * self.mixing_length_numerical_term().subs(z, xi[i]) * Szj[d].subs(z, xi[i]) * dphi_dz[k].subs(z, xi[i]) / self.basismatrices.M[k, k]
571
+
572
+ return out
573
+
574
+
575
+
576
+ @define(frozen=True, slots=True, kw_only=True)
577
+ class SMETNum(SMET):
578
+ ref_model: Model = field(init=False)
579
+
580
+ def __attrs_post_init__(self):
581
+ super().__attrs_post_init__()
582
+ object.__setattr__(self, "ref_model", SMET(level=self.level, dimension=self.dimension, boundary_conditions=self.boundary_conditions))
583
+
584
+ def flux(self):
585
+ return [self.substitute_precomputed_denominator(f, self.variables[1], self.aux_variables.hinv) for f in self.ref_model.flux()]
586
+
587
+ def nonconservative_matrix(self):
588
+ return [self.substitute_precomputed_denominator(f, self.variables[1], self.aux_variables.hinv) for f in self.ref_model.nonconservative_matrix()]
589
+
590
+ def quasilinear_matrix(self):
591
+ return [self.substitute_precomputed_denominator(f, self.variables[1], self.aux_variables.hinv) for f in self.ref_model.quasilinear_matrix()]
592
+
593
+ def source(self):
594
+ return self.substitute_precomputed_denominator(self.ref_model.source(), self.variables[1], self.aux_variables.hinv)
595
+
596
+ def source_implicit(self):
597
+ return self.substitute_precomputed_denominator(self.ref_model.source_implicit(), self.variables[1], self.aux_variables.hinv)
598
+
599
+ def residual(self):
600
+ return self.substitute_precomputed_denominator(self.ref_model.residual(), self.variables[1], self.aux_variables.hinv)
601
+
602
+ def left_eigenvectors(self):
603
+ return self.substitute_precomputed_denominator(self.ref_model.left_eigenvectors(), self.variables[1], self.aux_variables.hinv)
604
+
605
+ def right_eigenvectors(self):
606
+ return self.substitute_precomputed_denominator(self.ref_model.right_eigenvectors(), self.variables[1], self.aux_variables.hinv)
607
+
608
+ def eigenvalues(self):
609
+ h = self.variables[1]
610
+ evs = self.substitute_precomputed_denominator(self.ref_model.eigenvalues(), self.variables[1], self.aux_variables.hinv)
611
+ for i in range(self.n_variables):
612
+ evs[i] = Piecewise((evs[i], h > 1e-8), (0, True))
613
+ return evs