zoomy-core 0.1.11__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 (51) hide show
  1. zoomy_core/__init__.py +7 -0
  2. zoomy_core/decorators/decorators.py +25 -0
  3. zoomy_core/fvm/flux.py +52 -0
  4. zoomy_core/fvm/nonconservative_flux.py +97 -0
  5. zoomy_core/fvm/ode.py +55 -0
  6. zoomy_core/fvm/solver_numpy.py +297 -0
  7. zoomy_core/fvm/timestepping.py +13 -0
  8. zoomy_core/mesh/mesh.py +1236 -0
  9. zoomy_core/mesh/mesh_extrude.py +168 -0
  10. zoomy_core/mesh/mesh_util.py +487 -0
  11. zoomy_core/misc/custom_types.py +6 -0
  12. zoomy_core/misc/interpolation.py +140 -0
  13. zoomy_core/misc/io.py +439 -0
  14. zoomy_core/misc/logger_config.py +18 -0
  15. zoomy_core/misc/misc.py +213 -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.11.dist-info/METADATA +225 -0
  48. zoomy_core-0.1.11.dist-info/RECORD +51 -0
  49. zoomy_core-0.1.11.dist-info/WHEEL +5 -0
  50. zoomy_core-0.1.11.dist-info/licenses/LICENSE +674 -0
  51. zoomy_core-0.1.11.dist-info/top_level.txt +1 -0
zoomy_core/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ from importlib.metadata import version, PackageNotFoundError
2
+
3
+ try:
4
+ __version__ = version("zoomy-core")
5
+ except PackageNotFoundError:
6
+ # Package not installed, e.g. running from source
7
+ __version__ = "0.0.0"
@@ -0,0 +1,25 @@
1
+
2
+ def require(requirement):
3
+ """
4
+ Decorator to check if a requirement is met before executing the decorated function.
5
+
6
+ Parameters:
7
+ - requirement (str): The requirement string to evaluate. Should evaluate to True or False.
8
+
9
+ Returns:
10
+ - wrapper: The decorated function that will check the requirement before executing.
11
+ """
12
+
13
+ # decorator to check the assertion given in requirements given the settings
14
+ def req_decorator(func):
15
+ @wraps(func)
16
+ def wrapper(settings, *args, **kwargs):
17
+ requirement_evaluated = eval(requirement)
18
+ if not requirement_evaluated:
19
+ print("Requirement {}: {}".format(requirement, requirement_evaluated))
20
+ assert requirement_evaluated
21
+ return func(settings, *args, **kwargs)
22
+
23
+ return wrapper
24
+
25
+ return req_decorator
zoomy_core/fvm/flux.py ADDED
@@ -0,0 +1,52 @@
1
+ import numpy as np
2
+
3
+
4
+ class Flux:
5
+ def get_flux_operator(self, model):
6
+ pass
7
+
8
+
9
+ class Zero(Flux):
10
+ def get_flux_operator(self, model):
11
+ def compute(Qi, Qj, Qauxi, Qauxj, parameters, normal, Vi, Vj, Vij, dt):
12
+ return np.zeros_like(Qi)
13
+
14
+ return compute
15
+
16
+
17
+ class LaxFriedrichs(Flux):
18
+ """
19
+ Lax-Friedrichs flux implementation
20
+ """
21
+ def get_flux_operator(self, model):
22
+ def compute(Qi, Qj, Qauxi, Qauxj, parameters, normal, Vi, Vj, Vij, dt):
23
+ Fi = np.einsum("id..., d...-> i...", model.flux(Qi, Qauxi, parameters), normal)
24
+ Fj = np.einsum("id..., d...-> i...", model.flux(Qj, Qauxj, parameters), normal)
25
+ Qout = 0.5 * (Fi + Fj)
26
+ Qout -= 0.5 * dt / (Vi + Vj) * (Qj - Qi)
27
+ return Qout
28
+ return compute
29
+
30
+ class Rusanov(Flux):
31
+ def __init__(self, identity_matrix=None):
32
+ if not identity_matrix:
33
+ self.Id = lambda n: np.eye(n)
34
+ else:
35
+ self.Id = identity_matrix
36
+ """
37
+ Rusanov (local Lax-Friedrichs) flux implementation
38
+ """
39
+ def get_flux_operator(self, model):
40
+ Id_single = self.Id(model.n_variables)
41
+ def compute(Qi, Qj, Qauxi, Qauxj, parameters, normal, Vi, Vj, Vij, dt):
42
+ EVi = model.eigenvalues(Qi, Qauxi, parameters, normal)
43
+ EVj = model.eigenvalues(Qj, Qauxj, parameters, normal)
44
+ smax = np.max(np.abs(np.hstack([EVi, EVj])))
45
+ Id = np.stack([Id_single]*Qi.shape[1], axis=2) # (n_eq, n_eq, n_points)
46
+ Fi = np.einsum("id..., d...-> i...", model.flux(Qi, Qauxi, parameters), normal)
47
+ Fj = np.einsum("id..., d...-> i...", model.flux(Qj, Qauxj, parameters), normal)
48
+ Qout = 0.5 * (Fi + Fj)
49
+ Qout -= 0.5 * smax * np.einsum("ij..., jk...-> ik...", Id, (Qj - Qi))
50
+ return Qout
51
+ return compute
52
+
@@ -0,0 +1,97 @@
1
+ import numpy as np
2
+ from numpy.polynomial.legendre import leggauss
3
+ from functools import partial
4
+
5
+ class NonconservativeFlux:
6
+ def get_flux_operator(self, model):
7
+ pass
8
+
9
+
10
+ class Zero(NonconservativeFlux):
11
+ def get_flux_operator(self, model):
12
+ def compute(Qi, Qj, Qauxi, Qauxj, parameters, normal, Vi, Vj, Vij, dt):
13
+ return np.zeros_like(Qi), np.zeros_like(Qi)
14
+
15
+ return compute
16
+
17
+ class Rusanov(NonconservativeFlux):
18
+ def __init__(self, integration_order=3, identity_matrix=None, eps=1e-10):
19
+ self.integration_order = integration_order
20
+ samples, weights = leggauss(integration_order)
21
+ # shift from [-1, 1] to [0,1]
22
+ samples = 0.5 * (samples + 1)
23
+ weights *= 0.5
24
+ self.wi = np.array(weights)
25
+ self.xi = np.array(samples)
26
+ self.Id = identity_matrix if identity_matrix else lambda n: np.eye(n)
27
+ self.eps = eps
28
+
29
+ def _get_A(self, model):
30
+ def A(q, qaux, parameters, n):
31
+ # q : (n_dof,)
32
+ # evaluate the matrices A_d
33
+ _A = model.quasilinear_matrix(q, qaux, parameters)
34
+ return np.einsum('d...,ijd...->ij...', n, _A)
35
+ return A
36
+
37
+ def _integrate_path(self, model):
38
+ compute_A = self._get_A(model)
39
+ def compute(Qi, Qj,
40
+ Qauxi, Qauxj,
41
+ parameters,
42
+ normal):
43
+ dQ = Qj - Qi
44
+ dQaux = Qauxj - Qauxi
45
+
46
+ A_int = np.zeros((Qi.shape[0], Qi.shape[0], Qi.shape[1]))
47
+
48
+ for xi, wi in zip(self.xi, self.wi):
49
+ q_path = Qi + xi * dQ
50
+ qaux_path = Qauxi + xi * dQaux
51
+ A = compute_A(q_path, qaux_path, parameters, normal)
52
+ A_int += wi * A
53
+ return A_int
54
+ return compute
55
+
56
+
57
+
58
+
59
+ def get_flux_operator(self, model):
60
+ compute_path_integral = self._integrate_path(model)
61
+ Id_single = self.Id(model.n_variables)
62
+
63
+ def compute(
64
+ Qi,
65
+ Qj,
66
+ Qauxi,
67
+ Qauxj,
68
+ parameters,
69
+ normal,
70
+ Vi,
71
+ Vj,
72
+ Vij,
73
+ dt
74
+ ):
75
+ """
76
+ Vectorised Rusanov fluctuation.
77
+
78
+ Shapes
79
+ ------
80
+ Qi, Qj : (n_dof , N) states for the two cells
81
+ Qauxi, Qauxj : (n_aux , N)
82
+ parameters : (n_param ,)
83
+ normal : (dim , N) or (dim,) oriented outward for cell "i"
84
+ Vi : (N,) or scalar cell volume
85
+ Vij : (N,) or scalar face measure
86
+ """
87
+ A_int = compute_path_integral(Qi, Qj, Qauxi, Qauxj, parameters, normal)
88
+ sM = np.maximum(np.abs(model.eigenvalues(Qi, Qauxi, parameters, normal)).max(axis=0), np.abs(model.eigenvalues(Qj, Qauxj, parameters, normal)).max(axis=0))
89
+ Id = np.stack([Id_single]*Qi.shape[1], axis=2)
90
+
91
+ dQ = Qj - Qi
92
+ Dp = np.einsum("ij..., j...-> i...", 0.5 * (A_int + sM * Id), dQ)
93
+ Dm = np.einsum("ij..., j...-> i...", 0.5 * (A_int - sM * Id), dQ)
94
+ return Dp, Dm
95
+
96
+ return compute
97
+
zoomy_core/fvm/ode.py ADDED
@@ -0,0 +1,55 @@
1
+ import numpy as np
2
+
3
+
4
+ def RK1(func, Q, Qaux, param, dt, func_jac=None, func_bc=None):
5
+ dQ = np.zeros_like(Q)
6
+ dQ = func(dt, Q, Qaux, param, dQ)
7
+ return Q + dt * dQ
8
+
9
+
10
+ def RK2(func, Q, Qaux, param, dt, func_jac=None, func_bc=None):
11
+ """
12
+ heun scheme
13
+ """
14
+ dQ = np.zeros_like(Q)
15
+ Q0 = np.array(Q)
16
+ dQ = func(dt, Q, Qaux, param, dQ)
17
+ Q1 = Q + dt * dQ
18
+ dQ = func(dt, Q1, Qaux, param, dQ)
19
+ Q2 = Q1 + dt * dQ
20
+ return 0.5 * (Q0 + Q2)
21
+
22
+
23
+ def RK3(func, Q, Qaux, param, dt, func_jac=None, func_bc=None):
24
+ """ """
25
+ dQ = np.zeros_like(Q)
26
+ Q0 = np.array(Q)
27
+ dQ = func(dt, Q, Qaux, param, dQ)
28
+ Q1 = Q + dt * dQ
29
+ dQ = func(dt, Q1, Qaux, param, dQ)
30
+ Q2 = 3.0 / 4 * Q0 + 1.0 / 4 * (Q1 + dt * dQ)
31
+ dQ = func(dt, Q2, Qaux, param, dQ)
32
+ Q3 = 1.0 / 3 * Q0 + 2 / 3 * (Q2 + dt * dQ)
33
+ # TODO see old implementation
34
+ # func(dt, Q3, Qaux, param, dQ)
35
+ return Q3
36
+
37
+
38
+ def RKimplicit(func, Q, Qaux, param, dt, func_jac=None, func_bc=None):
39
+ """
40
+ implicit euler
41
+ """
42
+ assert func_jac is not None
43
+ Jac = np.zeros((Q.shape[0], Q.shape[0], Q.shape[1]), dtype=float)
44
+ dQ = np.zeros_like(Q)
45
+ I = np.eye(Q.shape[0])
46
+
47
+ dQ = func(dt, Q, Qaux, param, dQ)
48
+ Jac = func_jac(dt, Q, Qaux, param, Jac)
49
+
50
+ b = Q + dt * dQ
51
+ for i in range(Q.shape[1]):
52
+ A = I - dt * Jac[:, :, i]
53
+ b[:, i] += -dt * np.dot(Jac[:, :, i], Q[:, i])
54
+ Q[:, i] = np.linalg.solve(A, b[:, i])
55
+ return Q
@@ -0,0 +1,297 @@
1
+ import os
2
+ from time import time as gettime
3
+
4
+ import numpy as np
5
+ from attr import define
6
+
7
+ from typing import Callable
8
+ from attrs import define, field
9
+
10
+ from zoomy_core.misc.logger_config import logger
11
+
12
+
13
+
14
+ import zoomy_core.fvm.flux as fvmflux
15
+ import zoomy_core.fvm.nonconservative_flux as nonconservative_flux
16
+ import zoomy_core.misc.io as io
17
+ from zoomy_core.misc.misc import Zstruct, Settings
18
+ import zoomy_core.fvm.ode as ode
19
+ import zoomy_core.fvm.timestepping as timestepping
20
+ from zoomy_core.transformation.to_numpy import NumpyRuntimeModel
21
+
22
+
23
+ @define(frozen=True, slots=True, kw_only=True)
24
+ class Solver():
25
+ settings: Zstruct = field(factory=lambda: Settings.default())
26
+
27
+ def __attrs_post_init__(self):
28
+ defaults = Settings.default()
29
+ defaults.update(self.settings)
30
+ object.__setattr__(self, 'settings', defaults)
31
+
32
+
33
+ def initialize(self, mesh, model):
34
+ # model.boundary_conditions.initialize(
35
+ # mesh,
36
+ # model.time,
37
+ # model.position,
38
+ # model.distance,
39
+ # model.variables,
40
+ # model.aux_variables,
41
+ # model.parameters,
42
+ # model.normal,
43
+ # )
44
+
45
+ n_variables = model.n_variables
46
+ n_cells = mesh.n_cells
47
+ n_aux_variables = model.aux_variables.length()
48
+
49
+ Q = np.empty((n_variables, n_cells), dtype=float)
50
+ Qaux = np.empty((n_aux_variables, n_cells), dtype=float)
51
+ return Q, Qaux
52
+
53
+ def create_runtime(self, Q, Qaux, mesh, model):
54
+ mesh.resolve_periodic_bcs(model.boundary_conditions)
55
+ Q, Qaux = np.asarray(Q), np.asarray(Qaux)
56
+ parameters = np.asarray(model.parameter_values)
57
+ runtime_model = NumpyRuntimeModel(model)
58
+ return Q, Qaux, parameters, mesh, runtime_model
59
+
60
+ def get_compute_source(self, mesh, model):
61
+ def compute_source(dt, Q, Qaux, parameters, dQ):
62
+ dQ = model.source(
63
+ Q[:, :],
64
+ Qaux[:, :],
65
+ parameters,
66
+ )
67
+ return dQ
68
+
69
+ return compute_source
70
+
71
+ def get_compute_source_jacobian_wrt_variables(self, mesh, model):
72
+ def compute_source(dt, Q, Qaux, parameters, dQ):
73
+ dQ = model.source_jacobian_wrt_variables(
74
+ Q[:, : mesh.n_inner_cells],
75
+ Qaux[:, : mesh.n_inner_cells],
76
+ parameters,
77
+ )
78
+ return dQ
79
+
80
+ return compute_source
81
+
82
+ def get_apply_boundary_conditions(self, mesh, model):
83
+
84
+ def apply_boundary_conditions(time, Q, Qaux, parameters):
85
+ for i in range(mesh.n_boundary_faces):
86
+ i_face = mesh.boundary_face_face_indices[i]
87
+ i_bc_func = mesh.boundary_face_function_numbers[i]
88
+ q_cell = Q[:, mesh.boundary_face_cells[i]] # Shape: (Q_dim,)
89
+ qaux_cell = Qaux[:, mesh.boundary_face_cells[i]]
90
+ normal = mesh.face_normals[:, i_face]
91
+ position = mesh.face_centers[i_face, :]
92
+ position_ghost = mesh.cell_centers[:, mesh.boundary_face_ghosts[i]]
93
+ distance = np.linalg.norm(position - position_ghost)
94
+ q_ghost = model.boundary_conditions(i_bc_func, time, position, distance, q_cell, qaux_cell, parameters, normal)
95
+ Q[:, mesh.boundary_face_ghosts[i]] = q_ghost
96
+ return Q
97
+
98
+ return apply_boundary_conditions
99
+
100
+ def update_q(self, Q, Qaux, mesh, model, parameters):
101
+ """
102
+ Update variables before the solve step.
103
+ """
104
+ # This is a placeholder implementation. Replace with actual logic as needed.
105
+ return Q
106
+
107
+ def update_qaux(self, Q, Qaux, Qold, Qauxold, mesh, model, parameters, time, dt):
108
+ """
109
+ Update auxiliary variables
110
+ """
111
+ # This is a placeholder implementation. Replace with actual logic as needed.
112
+ return Qaux
113
+
114
+ def solve(self, mesh, model):
115
+ logger.error(
116
+ "Solver.solve() is not implemented. Please implement this method in the derived class."
117
+ )
118
+ raise NotImplementedError("Solver.solve() must be implemented in derived classes.")
119
+
120
+
121
+ @define(frozen=True, slots=True, kw_only=True)
122
+ class HyperbolicSolver(Solver):
123
+ settings: Zstruct = field(factory=lambda: Settings.default())
124
+ compute_dt: Callable = field(factory=lambda: timestepping.adaptive(CFL=0.45))
125
+ flux: fvmflux.Flux = field(factory=lambda: fvmflux.Zero())
126
+ nc_flux: nonconservative_flux.NonconservativeFlux = field(
127
+ factory=lambda: nonconservative_flux.Rusanov()
128
+ )
129
+ time_end: float = 0.1
130
+
131
+
132
+ def __attrs_post_init__(self):
133
+ super().__attrs_post_init__()
134
+ defaults = Settings.default()
135
+ defaults.output.update(Zstruct(snapshots=10))
136
+ defaults.update(self.settings)
137
+ object.__setattr__(self, 'settings', defaults)
138
+
139
+
140
+ def initialize(self, mesh, model):
141
+ Q, Qaux = super().initialize(mesh, model)
142
+ Q = model.initial_conditions.apply(mesh.cell_centers, Q)
143
+ Qaux = model.aux_initial_conditions.apply(mesh.cell_centers, Qaux)
144
+ return Q, Qaux
145
+
146
+ def get_compute_max_abs_eigenvalue(self, mesh, model):
147
+ def compute_max_abs_eigenvalue(Q, Qaux, parameters):
148
+ max_abs_eigenvalue = -np.inf
149
+ i_cellA = mesh.face_cells[0]
150
+ i_cellB = mesh.face_cells[1]
151
+ qA = Q[:, i_cellA]
152
+ qB = Q[:, i_cellB]
153
+ qauxA = Qaux[:, i_cellA]
154
+ qauxB = Qaux[:, i_cellB]
155
+ normal = mesh.face_normals
156
+ evA = model.eigenvalues(qA, qauxA, parameters, normal)
157
+ evB = model.eigenvalues(qB, qauxB, parameters, normal)
158
+ max_abs_eigenvalue = np.maximum(np.abs(evA).max(axis=0), np.abs(evB).max(axis=0))
159
+ return max_abs_eigenvalue
160
+ return compute_max_abs_eigenvalue
161
+
162
+ def get_flux_operator(self, mesh, model):
163
+ compute_num_flux = self.flux.get_flux_operator(model)
164
+ compute_nc_flux = self.nc_flux.get_flux_operator(model)
165
+ def flux_operator(dt, Q, Qaux, parameters, dQ):
166
+
167
+ # Initialize dQ as zeros using jax.numpy
168
+ dQ = np.zeros_like(dQ)
169
+
170
+ iA = mesh.face_cells[0]
171
+ iB = mesh.face_cells[1]
172
+
173
+ qA = Q[:, iA]
174
+ qB = Q[:, iB]
175
+ qauxA = Qaux[:, iA]
176
+ qauxB = Qaux[:, iB]
177
+ normals = mesh.face_normals
178
+ face_volumes = mesh.face_volumes
179
+ cell_volumesA = mesh.cell_volumes[iA]
180
+ cell_volumesB = mesh.cell_volumes[iB]
181
+ svA = mesh.face_subvolumes[:, 0]
182
+ svB = mesh.face_subvolumes[:, 1]
183
+
184
+ Dp, Dm = compute_nc_flux(
185
+ qA,
186
+ qB,
187
+ qauxA,
188
+ qauxB,
189
+ parameters,
190
+ normals,
191
+ svA,
192
+ svB,
193
+ face_volumes,
194
+ dt,
195
+ )
196
+ flux_out = Dm * face_volumes / cell_volumesA
197
+ flux_in = Dp * face_volumes / cell_volumesB
198
+
199
+
200
+ # dQ[:, iA]-= flux_out does not guarantee correct accumulation
201
+ # dQ[:, iB]-= flux_in
202
+ np.add.at(dQ, (slice(None), iA), -flux_out)
203
+ np.add.at(dQ, (slice(None), iB), -flux_in)
204
+ return dQ
205
+ return flux_operator
206
+
207
+ def solve(self, mesh, model, write_output=True):
208
+ Q, Qaux = self.initialize(mesh, model)
209
+
210
+ Q, Qaux, parameters, mesh, model = self.create_runtime(Q, Qaux, mesh, model)
211
+
212
+ # init once with dummy values for dt
213
+ Qaux = self.update_qaux(Q, Qaux, Q, Qaux, mesh, model, parameters, 0.0, 1.0)
214
+
215
+
216
+ if write_output:
217
+ output_hdf5_path = os.path.join(
218
+ self.settings.output.directory, f"{self.settings.output.filename}.h5"
219
+ )
220
+ save_fields = io.get_save_fields(output_hdf5_path, write_all=False)
221
+ else:
222
+ def save_fields(time, time_stamp, i_snapshot, Q, Qaux):
223
+ return i_snapshot
224
+
225
+
226
+ def run(Q, Qaux, parameters, model):
227
+ iteration = 0.0
228
+ time = 0.0
229
+
230
+ i_snapshot = 0.0
231
+ dt_snapshot = self.time_end / (self.settings.output.snapshots - 1)
232
+ if write_output:
233
+ io.init_output_directory(
234
+ self.settings.output.directory, self.settings.output.clean_directory
235
+ )
236
+ mesh.write_to_hdf5(output_hdf5_path)
237
+ io.save_settings(self.settings)
238
+ i_snapshot = save_fields(time, 0.0, i_snapshot, Q, Qaux)
239
+
240
+ Qnew = Q
241
+ Qauxnew = Qaux
242
+
243
+ compute_max_abs_eigenvalue = self.get_compute_max_abs_eigenvalue(mesh, model)
244
+ flux_operator = self.get_flux_operator(mesh, model)
245
+ source_operator = self.get_compute_source(mesh, model)
246
+ boundary_operator = self.get_apply_boundary_conditions(mesh, model)
247
+ Qnew = boundary_operator(time, Qnew, Qaux, parameters)
248
+
249
+
250
+ cell_inradius_face = np.minimum(mesh.cell_inradius[mesh.face_cells[0, :]], mesh.cell_inradius[mesh.face_cells[1,:]])
251
+ cell_inradius_face = cell_inradius_face.min()
252
+
253
+ while time < self.time_end:
254
+ Q = Qnew
255
+ Qaux = Qauxnew
256
+
257
+ dt = self.compute_dt(
258
+ Q, Qaux, parameters, cell_inradius_face, compute_max_abs_eigenvalue
259
+ )
260
+
261
+ Q1 = ode.RK1(flux_operator, Q, Qaux, parameters, dt)
262
+ Q2 = ode.RK1(
263
+ source_operator,
264
+ Q1,
265
+ Qaux,
266
+ parameters,
267
+ dt,
268
+ )
269
+
270
+ Q3 = boundary_operator(time, Q2, Qaux, parameters)
271
+
272
+ # Update solution and time
273
+ time += dt
274
+ iteration += 1
275
+
276
+ time_stamp = (i_snapshot) * dt_snapshot
277
+
278
+ Qnew = self.update_q(Q3, Qaux, mesh, model, parameters)
279
+ Qauxnew = self.update_qaux(Qnew, Qaux, Q, Qaux, mesh, model, parameters, time, dt)
280
+
281
+
282
+ i_snapshot = save_fields(time, time_stamp, i_snapshot, Qnew, Qauxnew)
283
+
284
+ if iteration % 10 == 0:
285
+ logger.info(
286
+ f"iteration: {int(iteration)}, time: {float(time):.6f}, "
287
+ f"dt: {float(dt):.6f}, next write at time: {float(time_stamp):.6f}"
288
+ )
289
+
290
+ return Qnew, Qaux
291
+
292
+ time_start = gettime()
293
+ Qnew, Qaux = run(Q, Qaux, parameters, model)
294
+ time = gettime() - time_start
295
+ logger.info(f"Finished simulation with in {time:.3f} seconds")
296
+ return Qnew, Qaux
297
+
@@ -0,0 +1,13 @@
1
+ def constant(dt=0.1):
2
+ def compute_dt(Q, Qaux, parameters, min_inradius, compute_max_abs_eigenvalue):
3
+ return dt
4
+
5
+ return compute_dt
6
+
7
+
8
+ def adaptive(CFL=0.9):
9
+ def compute_dt(Q, Qaux, parameters, min_inradius, compute_max_abs_eigenvalue):
10
+ ev_abs_max = compute_max_abs_eigenvalue(Q, Qaux, parameters)
11
+ return (CFL * 2 * min_inradius / ev_abs_max).min()
12
+
13
+ return compute_dt