zoomy-core 0.1.0__tar.gz → 0.1.2__tar.gz
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.
- {zoomy_core-0.1.0 → zoomy_core-0.1.2}/PKG-INFO +1 -1
- zoomy_core-0.1.2/library/zoomy_core/decorators/decorators.py +25 -0
- zoomy_core-0.1.2/library/zoomy_core/fvm/flux.py +97 -0
- zoomy_core-0.1.2/library/zoomy_core/fvm/nonconservative_flux.py +97 -0
- zoomy_core-0.1.2/library/zoomy_core/fvm/ode.py +55 -0
- zoomy_core-0.1.2/library/zoomy_core/fvm/solver_numpy.py +305 -0
- zoomy_core-0.1.2/library/zoomy_core/fvm/timestepping.py +13 -0
- zoomy_core-0.1.2/library/zoomy_core/mesh/gmsh_loader.py +301 -0
- zoomy_core-0.1.2/library/zoomy_core/mesh/mesh.py +1192 -0
- zoomy_core-0.1.2/library/zoomy_core/mesh/mesh_extrude.py +168 -0
- zoomy_core-0.1.2/library/zoomy_core/mesh/mesh_util.py +487 -0
- zoomy_core-0.1.2/library/zoomy_core/misc/custom_types.py +6 -0
- zoomy_core-0.1.2/library/zoomy_core/misc/gui.py +61 -0
- zoomy_core-0.1.2/library/zoomy_core/misc/interpolation.py +140 -0
- zoomy_core-0.1.2/library/zoomy_core/misc/io.py +401 -0
- zoomy_core-0.1.2/library/zoomy_core/misc/logger_config.py +18 -0
- zoomy_core-0.1.2/library/zoomy_core/misc/misc.py +216 -0
- zoomy_core-0.1.2/library/zoomy_core/misc/static_class.py +94 -0
- zoomy_core-0.1.2/library/zoomy_core/model/analysis.py +147 -0
- zoomy_core-0.1.2/library/zoomy_core/model/basefunction.py +113 -0
- zoomy_core-0.1.2/library/zoomy_core/model/basemodel.py +512 -0
- zoomy_core-0.1.2/library/zoomy_core/model/boundary_conditions.py +193 -0
- zoomy_core-0.1.2/library/zoomy_core/model/initial_conditions.py +171 -0
- zoomy_core-0.1.2/library/zoomy_core/model/model.py +63 -0
- zoomy_core-0.1.2/library/zoomy_core/model/models/GN.py +70 -0
- zoomy_core-0.1.2/library/zoomy_core/model/models/advection.py +53 -0
- zoomy_core-0.1.2/library/zoomy_core/model/models/basisfunctions.py +181 -0
- zoomy_core-0.1.2/library/zoomy_core/model/models/basismatrices.py +377 -0
- zoomy_core-0.1.2/library/zoomy_core/model/models/core.py +564 -0
- zoomy_core-0.1.2/library/zoomy_core/model/models/coupled_constrained.py +60 -0
- zoomy_core-0.1.2/library/zoomy_core/model/models/old_smm copy.py +867 -0
- zoomy_core-0.1.2/library/zoomy_core/model/models/poisson.py +41 -0
- zoomy_core-0.1.2/library/zoomy_core/model/models/shallow_moments.py +757 -0
- zoomy_core-0.1.2/library/zoomy_core/model/models/shallow_moments_sediment.py +378 -0
- zoomy_core-0.1.2/library/zoomy_core/model/models/shallow_moments_topo.py +423 -0
- zoomy_core-0.1.2/library/zoomy_core/model/models/shallow_moments_variants.py +1509 -0
- zoomy_core-0.1.2/library/zoomy_core/model/models/shallow_water.py +266 -0
- zoomy_core-0.1.2/library/zoomy_core/model/models/shallow_water_topo.py +111 -0
- zoomy_core-0.1.2/library/zoomy_core/model/models/shear_shallow_flow.py +594 -0
- zoomy_core-0.1.2/library/zoomy_core/model/models/sme_turbulent.py +613 -0
- zoomy_core-0.1.2/library/zoomy_core/model/models/swe_old.py +1018 -0
- zoomy_core-0.1.2/library/zoomy_core/model/models/vam.py +455 -0
- zoomy_core-0.1.2/library/zoomy_core/postprocessing/postprocessing.py +72 -0
- zoomy_core-0.1.2/library/zoomy_core/preprocessing/openfoam_moments.py +452 -0
- zoomy_core-0.1.2/library/zoomy_core/transformation/helpers.py +25 -0
- zoomy_core-0.1.2/library/zoomy_core/transformation/to_amrex.py +238 -0
- zoomy_core-0.1.2/library/zoomy_core/transformation/to_c.py +181 -0
- zoomy_core-0.1.2/library/zoomy_core/transformation/to_jax.py +14 -0
- zoomy_core-0.1.2/library/zoomy_core/transformation/to_numpy.py +115 -0
- zoomy_core-0.1.2/library/zoomy_core/transformation/to_openfoam.py +254 -0
- zoomy_core-0.1.2/library/zoomy_core/transformation/to_ufl.py +67 -0
- {zoomy_core-0.1.0 → zoomy_core-0.1.2}/library/zoomy_core.egg-info/PKG-INFO +1 -1
- zoomy_core-0.1.2/library/zoomy_core.egg-info/SOURCES.txt +58 -0
- {zoomy_core-0.1.0 → zoomy_core-0.1.2}/pyproject.toml +2 -2
- zoomy_core-0.1.0/library/zoomy_core.egg-info/SOURCES.txt +0 -8
- {zoomy_core-0.1.0 → zoomy_core-0.1.2}/LICENSE +0 -0
- {zoomy_core-0.1.0 → zoomy_core-0.1.2}/README.md +0 -0
- {zoomy_core-0.1.0 → zoomy_core-0.1.2}/library/zoomy_core.egg-info/dependency_links.txt +0 -0
- {zoomy_core-0.1.0 → zoomy_core-0.1.2}/library/zoomy_core.egg-info/requires.txt +0 -0
- {zoomy_core-0.1.0 → zoomy_core-0.1.2}/library/zoomy_core.egg-info/top_level.txt +0 -0
- {zoomy_core-0.1.0 → zoomy_core-0.1.2}/setup.cfg +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
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Dummy flux
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def Zero():
|
|
9
|
+
def flux(Qi, Qj, Qauxi, Qauxj, param, normal, model_functions, mesh_props=None):
|
|
10
|
+
Qout = np.zeros_like(Qi)
|
|
11
|
+
return Qout, False
|
|
12
|
+
|
|
13
|
+
return flux
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
"""
|
|
17
|
+
Lax-Friedrichs flux implementation
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def LF():
|
|
22
|
+
def flux(Qi, Qj, Qauxi, Qauxj, param, normal, model_functions, mesh_props=None):
|
|
23
|
+
assert mesh_props is not None
|
|
24
|
+
dt_dx = mesh_props.dt_dx
|
|
25
|
+
Qout = np.zeros_like(Qi)
|
|
26
|
+
flux = model_functions.flux
|
|
27
|
+
dim = normal.shape[0]
|
|
28
|
+
num_eq = Qi.shape[0]
|
|
29
|
+
for d in range(dim):
|
|
30
|
+
Fi = flux[d](Qi, Qauxi, param)
|
|
31
|
+
Fj = flux[d](Qj, Qauxj, param)
|
|
32
|
+
Qout += 0.5 * (Fi + Fj) * normal[d]
|
|
33
|
+
Qout -= 0.5 * dt_dx * (Qj - Qi)
|
|
34
|
+
return Qout, False
|
|
35
|
+
|
|
36
|
+
return flux
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
"""
|
|
40
|
+
Rusanov (local Lax-Friedrichs) flux implementation
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def LLF():
|
|
45
|
+
def flux(Qi, Qj, Qauxi, Qauxj, param, normal, model_functions, mesh_props=None):
|
|
46
|
+
EVi = model_functions.eigenvalues(Qi, Qauxi, param, normal)
|
|
47
|
+
EVj = model_functions.eigenvalues(Qj, Qauxj, param, normal)
|
|
48
|
+
assert not np.isnan(EVi).any()
|
|
49
|
+
assert not np.isnan(EVj).any()
|
|
50
|
+
smax = np.max(np.abs(np.vstack([EVi, EVj])))
|
|
51
|
+
Qout = np.zeros_like(Qi)
|
|
52
|
+
flux = model_functions.flux
|
|
53
|
+
dim = normal.shape[0]
|
|
54
|
+
num_eq = Qi.shape[0]
|
|
55
|
+
for d in range(dim):
|
|
56
|
+
Fi = flux[d](Qi, Qauxi, param)
|
|
57
|
+
Fj = flux[d](Qj, Qauxj, param)
|
|
58
|
+
Qout += 0.5 * (Fi + Fj) * normal[d]
|
|
59
|
+
Qout -= 0.5 * smax * (Qj - Qi)
|
|
60
|
+
return Qout, False
|
|
61
|
+
|
|
62
|
+
return flux
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
"""
|
|
66
|
+
Rusanov (local Lax-Friedrichs) flux implementation
|
|
67
|
+
with topography fix (e.g. for model SWEtopo)
|
|
68
|
+
with WB fix for lake at rest
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def LLF_wb():
|
|
73
|
+
def flux(Qi, Qj, Qauxi, Qauxj, param, normal, model_functions, mesh_props=None):
|
|
74
|
+
IWB = np.eye(Qi.shape[0])
|
|
75
|
+
IWB[-1, :] = 0.0
|
|
76
|
+
IWB[0, -1] = 0.0
|
|
77
|
+
EVi = np.zeros_like(Qi)
|
|
78
|
+
EVj = np.zeros_like(Qj)
|
|
79
|
+
EVi = model_functions.eigenvalues(Qi, Qauxi, param, normal)
|
|
80
|
+
EVj = model_functions.eigenvalues(Qj, Qauxj, param, normal)
|
|
81
|
+
assert not np.isnan(EVi).any()
|
|
82
|
+
assert not np.isnan(EVj).any()
|
|
83
|
+
smax = np.max(np.abs(np.vstack([EVi, EVj])))
|
|
84
|
+
Qout = np.zeros_like(Qi)
|
|
85
|
+
flux = model_functions.flux
|
|
86
|
+
dim = normal.shape[0]
|
|
87
|
+
num_eq = Qi.shape[0]
|
|
88
|
+
Fi = np.zeros((num_eq))
|
|
89
|
+
Fj = np.zeros((num_eq))
|
|
90
|
+
for d in range(dim):
|
|
91
|
+
flux[d](Qi, Qauxi, param, Fi)
|
|
92
|
+
flux[d](Qj, Qauxj, param, Fj)
|
|
93
|
+
Qout += 0.5 * (Fi + Fj) * normal[d]
|
|
94
|
+
Qout -= 0.5 * smax * IWB @ (Qj - Qi)
|
|
95
|
+
return Qout, False
|
|
96
|
+
|
|
97
|
+
return flux
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from numpy.polynomial.legendre import leggauss
|
|
3
|
+
from functools import partial
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def zero():
|
|
7
|
+
def nc_flux(Qi, Qauxi, Qj, Qauxj, parameters, normal, model):
|
|
8
|
+
return np.zeros_like(Qi), False
|
|
9
|
+
|
|
10
|
+
return nc_flux
|
|
11
|
+
|
|
12
|
+
def segmentpath(integration_order=3, scheme='rusanov'):
|
|
13
|
+
# compute integral of NC-Matrix int NC(Q(s)) ds for segment path Q(s) = Ql + (Qr-Ql)*s for s = [0,1]
|
|
14
|
+
samples, weights = leggauss(integration_order)
|
|
15
|
+
# shift from [-1, 1] to [0,1]
|
|
16
|
+
samples = 0.5 * (samples + 1)
|
|
17
|
+
weights *= 0.5
|
|
18
|
+
|
|
19
|
+
def priceC(
|
|
20
|
+
Qi, Qj, Qauxi, Qauxj, parameters, normal, svA, svB, vol_face, dt, model
|
|
21
|
+
):
|
|
22
|
+
dim = normal.shape[0]
|
|
23
|
+
n_variables = Qi.shape[0]
|
|
24
|
+
n_cells = Qi.shape[1]
|
|
25
|
+
|
|
26
|
+
def B(s):
|
|
27
|
+
out = np.zeros((n_variables, n_variables, n_cells), dtype=float)
|
|
28
|
+
tmp = np.zeros_like(out)
|
|
29
|
+
for d in range(dim):
|
|
30
|
+
tmp = model.quasilinear_matrix[d](
|
|
31
|
+
Qi + s * (Qj - Qi), Qauxi + s * (Qauxj - Qauxi), parameters
|
|
32
|
+
)
|
|
33
|
+
out = out + tmp * normal[d]
|
|
34
|
+
# out[:,:,:] += tmp * normal[d]
|
|
35
|
+
return out
|
|
36
|
+
|
|
37
|
+
Bint = np.zeros((n_variables, n_variables, n_cells))
|
|
38
|
+
for w, s in zip(weights, samples):
|
|
39
|
+
Bint += w * B(s)
|
|
40
|
+
|
|
41
|
+
Bint_sq = np.einsum("ij..., jk...->ik...", Bint, Bint)
|
|
42
|
+
I = np.zeros_like(Bint)
|
|
43
|
+
for i in range(n_variables):
|
|
44
|
+
# I[i, i, :] = 1.
|
|
45
|
+
I = I.at[i, i, :].set(1.0)
|
|
46
|
+
|
|
47
|
+
Am = (
|
|
48
|
+
0.5 * Bint
|
|
49
|
+
- (svA * svB) / (svA + svB) * 1.0 / (dt * vol_face) * I
|
|
50
|
+
- 1 / 4 * (dt * vol_face) / (svA + svB) * Bint_sq
|
|
51
|
+
)
|
|
52
|
+
# Am = 0.5* Bint - np.einsum('..., ij...->ij...', (svA * svB)/(svA + svB) * 1./(dt * vol_face) ,I) - 1/4 * np.einsum('..., ij...->ij...', (dt * vol_face)/(svA + svB) , Bint_sq)
|
|
53
|
+
|
|
54
|
+
return np.einsum("ij..., j...->i...", Am, (Qj - Qi)), False
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def rusanov(
|
|
58
|
+
Qi, Qj, Qauxi, Qauxj, parameters, normal, svA, svB, vol_face, dt, model
|
|
59
|
+
):
|
|
60
|
+
dim = normal.shape[0]
|
|
61
|
+
n_variables = Qi.shape[0]
|
|
62
|
+
n_cells = Qi.shape[1]
|
|
63
|
+
|
|
64
|
+
def B(s):
|
|
65
|
+
# out = np.zeros((n_variables, n_variables, n_cells), dtype=float)
|
|
66
|
+
# tmp = np.zeros_like(out)
|
|
67
|
+
# for d in range(dim):
|
|
68
|
+
# tmp = model.quasilinear_matrix[d](
|
|
69
|
+
# Qi + s * (Qj - Qi), Qauxi + s * (Qauxj - Qauxi), parameters
|
|
70
|
+
# )
|
|
71
|
+
# out = out + tmp * normal[d]
|
|
72
|
+
out = np.einsum("ijd..., d...->ij...", model.quasilinear_matrix(
|
|
73
|
+
Qi + s * (Qj - Qi), Qauxi + s * (Qauxj - Qauxi), parameters
|
|
74
|
+
), normal)
|
|
75
|
+
return out
|
|
76
|
+
|
|
77
|
+
Am = np.zeros((n_variables, n_variables, n_cells))
|
|
78
|
+
for w, s in zip(weights, samples):
|
|
79
|
+
Am += w * B(s)
|
|
80
|
+
|
|
81
|
+
I = np.zeros_like(Am)
|
|
82
|
+
for i in range(n_variables):
|
|
83
|
+
I[i, i, :] = 1.0
|
|
84
|
+
|
|
85
|
+
ev_i = model.eigenvalues(Qi, Qauxi, parameters, normal)
|
|
86
|
+
ev_j = model.eigenvalues(Qj, Qauxj, parameters, normal)
|
|
87
|
+
sM = np.maximum(np.abs(ev_i).max(axis=0), np.abs(ev_j).max(axis=0))
|
|
88
|
+
|
|
89
|
+
return np.einsum("ij..., j...->i...", 0.5 * Am + 0.5 * sM * I, (Qj - Qi)), np.einsum("ij..., j...->i...", 0.5 * Am - 0.5 * sM * I, (Qj - Qi)), False
|
|
90
|
+
|
|
91
|
+
if scheme == 'rusanov':
|
|
92
|
+
return rusanov
|
|
93
|
+
elif scheme=='priceC':
|
|
94
|
+
return priceC
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
|
|
@@ -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,305 @@
|
|
|
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 library.zoomy_core.misc.logger_config import logger
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
import library.zoomy_core.fvm.flux as flux
|
|
15
|
+
import library.zoomy_core.fvm.nonconservative_flux as nonconservative_flux
|
|
16
|
+
import library.zoomy_core.misc.io as io
|
|
17
|
+
from library.zoomy_core.misc.misc import Zstruct, Settings
|
|
18
|
+
import library.zoomy_core.fvm.ode as ode
|
|
19
|
+
import library.zoomy_core.fvm.timestepping as timestepping
|
|
20
|
+
from library.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
|
+
# runtime_bcs = tuple(model.bcs)
|
|
84
|
+
|
|
85
|
+
# def apply_boundary_conditions(time, Q, Qaux, parameters):
|
|
86
|
+
# for i in range(mesh.n_boundary_faces):
|
|
87
|
+
# i_face = mesh.boundary_face_face_indices[i]
|
|
88
|
+
# i_bc_func = mesh.boundary_face_function_numbers[i]
|
|
89
|
+
# q_cell = Q[:, mesh.boundary_face_cells[i]] # Shape: (Q_dim,)
|
|
90
|
+
# qaux_cell = Qaux[:, mesh.boundary_face_cells[i]]
|
|
91
|
+
# normal = mesh.face_normals[:, i_face]
|
|
92
|
+
# position = mesh.face_centers[i_face, :]
|
|
93
|
+
# position_ghost = mesh.cell_centers[:, mesh.boundary_face_ghosts[i]]
|
|
94
|
+
# distance = np.linalg.norm(position - position_ghost)
|
|
95
|
+
# q_ghost = runtime_bcs[i_bc_func](time, position, distance, q_cell, qaux_cell, parameters, normal)
|
|
96
|
+
# Q[:, mesh.boundary_face_ghosts[i]] = q_ghost
|
|
97
|
+
# return Q
|
|
98
|
+
|
|
99
|
+
def apply_boundary_conditions(time, Q, Qaux, parameters):
|
|
100
|
+
for i in range(mesh.n_boundary_faces):
|
|
101
|
+
i_face = mesh.boundary_face_face_indices[i]
|
|
102
|
+
i_bc_func = mesh.boundary_face_function_numbers[i]
|
|
103
|
+
q_cell = Q[:, mesh.boundary_face_cells[i]] # Shape: (Q_dim,)
|
|
104
|
+
qaux_cell = Qaux[:, mesh.boundary_face_cells[i]]
|
|
105
|
+
normal = mesh.face_normals[:, i_face]
|
|
106
|
+
position = mesh.face_centers[i_face, :]
|
|
107
|
+
position_ghost = mesh.cell_centers[:, mesh.boundary_face_ghosts[i]]
|
|
108
|
+
distance = np.linalg.norm(position - position_ghost)
|
|
109
|
+
q_ghost = model.boundary_conditions(i_bc_func, time, position, distance, q_cell, qaux_cell, parameters, normal)
|
|
110
|
+
Q[:, mesh.boundary_face_ghosts[i]] = q_ghost
|
|
111
|
+
return Q
|
|
112
|
+
|
|
113
|
+
return apply_boundary_conditions
|
|
114
|
+
|
|
115
|
+
def update_q(self, Q, Qaux, mesh, model, parameters):
|
|
116
|
+
"""
|
|
117
|
+
Update variables before the solve step.
|
|
118
|
+
"""
|
|
119
|
+
# This is a placeholder implementation. Replace with actual logic as needed.
|
|
120
|
+
return Q
|
|
121
|
+
|
|
122
|
+
def update_qaux(self, Q, Qaux, Qold, Qauxold, mesh, model, parameters, time, dt):
|
|
123
|
+
"""
|
|
124
|
+
Update auxiliary variables
|
|
125
|
+
"""
|
|
126
|
+
# This is a placeholder implementation. Replace with actual logic as needed.
|
|
127
|
+
return Qaux
|
|
128
|
+
|
|
129
|
+
def solve(self, mesh, model):
|
|
130
|
+
logger.error(
|
|
131
|
+
"Solver.solve() is not implemented. Please implement this method in the derived class."
|
|
132
|
+
)
|
|
133
|
+
raise NotImplementedError("Solver.solve() must be implemented in derived classes.")
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@define(frozen=True, slots=True, kw_only=True)
|
|
137
|
+
class HyperbolicSolver(Solver):
|
|
138
|
+
settings: Zstruct = field(factory=lambda: Settings.default())
|
|
139
|
+
compute_dt: Callable = field(factory=lambda: timestepping.adaptive(CFL=0.45))
|
|
140
|
+
num_flux: Callable = field(factory=lambda: flux.Zero())
|
|
141
|
+
nc_flux: Callable = field(factory=lambda: nonconservative_flux.segmentpath())
|
|
142
|
+
time_end: float = 0.1
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def __attrs_post_init__(self):
|
|
146
|
+
super().__attrs_post_init__()
|
|
147
|
+
defaults = Settings.default()
|
|
148
|
+
defaults.output.update(Zstruct(snapshots=10))
|
|
149
|
+
defaults.update(self.settings)
|
|
150
|
+
object.__setattr__(self, 'settings', defaults)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def initialize(self, mesh, model):
|
|
154
|
+
Q, Qaux = super().initialize(mesh, model)
|
|
155
|
+
Q = model.initial_conditions.apply(mesh.cell_centers, Q)
|
|
156
|
+
Qaux = model.aux_initial_conditions.apply(mesh.cell_centers, Qaux)
|
|
157
|
+
return Q, Qaux
|
|
158
|
+
|
|
159
|
+
def get_compute_max_abs_eigenvalue(self, mesh, model):
|
|
160
|
+
def compute_max_abs_eigenvalue(Q, Qaux, parameters):
|
|
161
|
+
max_abs_eigenvalue = -np.inf
|
|
162
|
+
i_cellA = mesh.face_cells[0]
|
|
163
|
+
i_cellB = mesh.face_cells[1]
|
|
164
|
+
qA = Q[:, i_cellA]
|
|
165
|
+
qB = Q[:, i_cellB]
|
|
166
|
+
qauxA = Qaux[:, i_cellA]
|
|
167
|
+
qauxB = Qaux[:, i_cellB]
|
|
168
|
+
normal = mesh.face_normals
|
|
169
|
+
evA = model.eigenvalues(qA, qauxA, parameters, normal)
|
|
170
|
+
evB = model.eigenvalues(qB, qauxB, parameters, normal)
|
|
171
|
+
max_abs_eigenvalue = np.maximum(np.abs(evA).max(axis=0), np.abs(evB).max(axis=0))
|
|
172
|
+
return max_abs_eigenvalue
|
|
173
|
+
return compute_max_abs_eigenvalue
|
|
174
|
+
|
|
175
|
+
def get_flux_operator(self, mesh, model):
|
|
176
|
+
def flux_operator(dt, Q, Qaux, parameters, dQ):
|
|
177
|
+
compute_num_flux = self.num_flux
|
|
178
|
+
compute_nc_flux = self.nc_flux
|
|
179
|
+
# Initialize dQ as zeros using jax.numpy
|
|
180
|
+
dQ = np.zeros_like(dQ)
|
|
181
|
+
|
|
182
|
+
iA = mesh.face_cells[0]
|
|
183
|
+
iB = mesh.face_cells[1]
|
|
184
|
+
|
|
185
|
+
qA = Q[:, iA]
|
|
186
|
+
qB = Q[:, iB]
|
|
187
|
+
qauxA = Qaux[:, iA]
|
|
188
|
+
qauxB = Qaux[:, iB]
|
|
189
|
+
normals = mesh.face_normals
|
|
190
|
+
face_volumes = mesh.face_volumes
|
|
191
|
+
cell_volumesA = mesh.cell_volumes[iA]
|
|
192
|
+
cell_volumesB = mesh.cell_volumes[iB]
|
|
193
|
+
svA = mesh.face_subvolumes[:, 0]
|
|
194
|
+
svB = mesh.face_subvolumes[:, 1]
|
|
195
|
+
|
|
196
|
+
Dp, Dm, failed = compute_nc_flux(
|
|
197
|
+
qA,
|
|
198
|
+
qB,
|
|
199
|
+
qauxA,
|
|
200
|
+
qauxB,
|
|
201
|
+
parameters,
|
|
202
|
+
normals,
|
|
203
|
+
svA,
|
|
204
|
+
svB,
|
|
205
|
+
face_volumes,
|
|
206
|
+
dt,
|
|
207
|
+
model,
|
|
208
|
+
)
|
|
209
|
+
flux_out = Dm * face_volumes / cell_volumesA
|
|
210
|
+
flux_in = Dp * face_volumes / cell_volumesB
|
|
211
|
+
|
|
212
|
+
dQ[:, iA]-= flux_out
|
|
213
|
+
dQ[:, iB]-= flux_in
|
|
214
|
+
return dQ
|
|
215
|
+
return flux_operator
|
|
216
|
+
|
|
217
|
+
def solve(self, mesh, model, write_output=True):
|
|
218
|
+
Q, Qaux = self.initialize(mesh, model)
|
|
219
|
+
|
|
220
|
+
Q, Qaux, parameters, mesh, model = self.create_runtime(Q, Qaux, mesh, model)
|
|
221
|
+
|
|
222
|
+
# init once with dummy values for dt
|
|
223
|
+
Qaux = self.update_qaux(Q, Qaux, Q, Qaux, mesh, model, parameters, 0.0, 1.0)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
if write_output:
|
|
227
|
+
output_hdf5_path = os.path.join(
|
|
228
|
+
self.settings.output.directory, f"{self.settings.output.filename}.h5"
|
|
229
|
+
)
|
|
230
|
+
save_fields = io.get_save_fields(output_hdf5_path, write_all=False)
|
|
231
|
+
else:
|
|
232
|
+
def save_fields(time, time_stamp, i_snapshot, Q, Qaux):
|
|
233
|
+
return i_snapshot
|
|
234
|
+
|
|
235
|
+
def run(Q, Qaux, parameters, model):
|
|
236
|
+
iteration = 0.0
|
|
237
|
+
time = 0.0
|
|
238
|
+
|
|
239
|
+
i_snapshot = 0.0
|
|
240
|
+
dt_snapshot = self.time_end / (self.settings.output.snapshots - 1)
|
|
241
|
+
if write_output:
|
|
242
|
+
io.init_output_directory(
|
|
243
|
+
self.settings.output.directory, self.settings.output.clean_directory
|
|
244
|
+
)
|
|
245
|
+
mesh.write_to_hdf5(output_hdf5_path)
|
|
246
|
+
io.save_settings(self.settings)
|
|
247
|
+
i_snapshot = save_fields(time, 0.0, i_snapshot, Q, Qaux)
|
|
248
|
+
|
|
249
|
+
Qnew = Q
|
|
250
|
+
Qauxnew = Qaux
|
|
251
|
+
|
|
252
|
+
compute_max_abs_eigenvalue = self.get_compute_max_abs_eigenvalue(mesh, model)
|
|
253
|
+
flux_operator = self.get_flux_operator(mesh, model)
|
|
254
|
+
source_operator = self.get_compute_source(mesh, model)
|
|
255
|
+
boundary_operator = self.get_apply_boundary_conditions(mesh, model)
|
|
256
|
+
Qnew = boundary_operator(time, Qnew, Qaux, parameters)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
cell_inradius_face = np.minimum(mesh.cell_inradius[mesh.face_cells[0, :]], mesh.cell_inradius[mesh.face_cells[1,:]])
|
|
260
|
+
|
|
261
|
+
while time < self.time_end:
|
|
262
|
+
Q = Qnew
|
|
263
|
+
Qaux = Qauxnew
|
|
264
|
+
|
|
265
|
+
dt = self.compute_dt(
|
|
266
|
+
Q, Qaux, parameters, cell_inradius_face, compute_max_abs_eigenvalue
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
Q1 = ode.RK1(flux_operator, Q, Qaux, parameters, dt)
|
|
270
|
+
Q2 = ode.RK1(
|
|
271
|
+
source_operator,
|
|
272
|
+
Q1,
|
|
273
|
+
Qaux,
|
|
274
|
+
parameters,
|
|
275
|
+
dt,
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
Q3 = boundary_operator(time, Q2, Qaux, parameters)
|
|
279
|
+
|
|
280
|
+
# Update solution and time
|
|
281
|
+
time += dt
|
|
282
|
+
iteration += 1
|
|
283
|
+
|
|
284
|
+
time_stamp = (i_snapshot) * dt_snapshot
|
|
285
|
+
|
|
286
|
+
Qnew = self.update_q(Q3, Qaux, mesh, model, parameters)
|
|
287
|
+
Qauxnew = self.update_qaux(Qnew, Qaux, Q, Qaux, mesh, model, parameters, time, dt)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
i_snapshot = save_fields(time, time_stamp, i_snapshot, Qnew, Qauxnew)
|
|
291
|
+
|
|
292
|
+
if iteration % 10 == 0:
|
|
293
|
+
logger.info(
|
|
294
|
+
f"iteration: {int(iteration)}, time: {float(time):.6f}, "
|
|
295
|
+
f"dt: {float(dt):.6f}, next write at time: {float(time_stamp):.6f}"
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
return Qnew, Qaux
|
|
299
|
+
|
|
300
|
+
time_start = gettime()
|
|
301
|
+
Qnew, Qaux = run(Q, Qaux, parameters, model)
|
|
302
|
+
time = gettime() - time_start
|
|
303
|
+
logger.info(f"Finished simulation with in {time:.3f} seconds")
|
|
304
|
+
return Qnew, Qaux
|
|
305
|
+
|
|
@@ -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
|