zoomy-core 0.1.14__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- decorators/decorators.py +25 -0
- fvm/__init__.py +0 -0
- fvm/flux.py +52 -0
- fvm/nonconservative_flux.py +97 -0
- fvm/ode.py +55 -0
- fvm/solver_numpy.py +297 -0
- fvm/timestepping.py +13 -0
- mesh/__init__.py +0 -0
- mesh/mesh.py +1239 -0
- mesh/mesh_extrude.py +168 -0
- mesh/mesh_util.py +487 -0
- misc/__init__.py +0 -0
- misc/custom_types.py +6 -0
- misc/interpolation.py +140 -0
- misc/io.py +448 -0
- misc/logger_config.py +18 -0
- misc/misc.py +218 -0
- model/__init__.py +0 -0
- model/analysis.py +147 -0
- model/basefunction.py +113 -0
- model/basemodel.py +513 -0
- model/boundary_conditions.py +193 -0
- model/initial_conditions.py +171 -0
- model/model.py +65 -0
- model/models/GN.py +70 -0
- model/models/advection.py +53 -0
- model/models/basisfunctions.py +181 -0
- model/models/basismatrices.py +381 -0
- model/models/coupled_constrained.py +60 -0
- model/models/poisson.py +41 -0
- model/models/shallow_moments.py +757 -0
- model/models/shallow_moments_sediment.py +378 -0
- model/models/shallow_moments_topo.py +423 -0
- model/models/shallow_moments_variants.py +1509 -0
- model/models/shallow_water.py +266 -0
- model/models/shallow_water_topo.py +111 -0
- model/models/shear_shallow_flow.py +594 -0
- model/models/sme_turbulent.py +613 -0
- model/models/vam.py +455 -0
- postprocessing/__init__.py +0 -0
- postprocessing/plotting.py +244 -0
- postprocessing/postprocessing.py +75 -0
- preprocessing/__init__.py +0 -0
- preprocessing/openfoam_moments.py +453 -0
- transformation/__init__.py +0 -0
- transformation/helpers.py +25 -0
- transformation/to_amrex.py +241 -0
- transformation/to_c.py +185 -0
- transformation/to_jax.py +14 -0
- transformation/to_numpy.py +118 -0
- transformation/to_openfoam.py +258 -0
- transformation/to_ufl.py +67 -0
- zoomy_core-0.1.14.dist-info/METADATA +52 -0
- zoomy_core-0.1.14.dist-info/RECORD +57 -0
- zoomy_core-0.1.14.dist-info/WHEEL +5 -0
- zoomy_core-0.1.14.dist-info/licenses/LICENSE +674 -0
- zoomy_core-0.1.14.dist-info/top_level.txt +8 -0
decorators/decorators.py
ADDED
|
@@ -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
|
fvm/__init__.py
ADDED
|
File without changes
|
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
|
+
|
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
|
fvm/solver_numpy.py
ADDED
|
@@ -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
|
+
|
fvm/timestepping.py
ADDED
|
@@ -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
|
mesh/__init__.py
ADDED
|
File without changes
|