Skip to content

Commit 6e4a495

Browse files
schreiberxSCHREIBER Martin
andauthored
Martin burgers (#20)
* first test * updates for playground * burgers update * further updates * cleanups * u * updated test * u * added IMEX12 and IMEX21 * dahlquist arbitrary t0 integrator * u --------- Co-authored-by: SCHREIBER Martin <martin.schreiber@inria.fr>
1 parent 38b9c19 commit 6e4a495

23 files changed

Lines changed: 1935 additions & 1 deletion
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
from qmat.playgrounds.martin.diff_eqs.de_solver import DESolver
3+
from qmat.playgrounds.martin.diff_eqs.burgers import Burgers
4+
from qmat.playgrounds.martin.diff_eqs.dahlquist2 import Dahlquist2
5+
6+
__all__ = [
7+
"DESolver",
8+
"Burgers",
9+
"Dahlquist2",
10+
]
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import numpy as np
2+
from qmat.playgrounds.martin.diff_eqs.de_solver import DESolver
3+
4+
5+
class Burgers(DESolver):
6+
"""
7+
Class to handle the 1D viscous Burgers' equation.
8+
"""
9+
10+
def __init__(self, N: int, nu: float, domain_size: float = 2.0 * np.pi):
11+
# Resolution
12+
self._N: int = N
13+
14+
# Viscosity
15+
self._nu: float = nu
16+
17+
# Domain size
18+
self._domain_size: float = domain_size
19+
20+
# Prepare spectral differentiation values
21+
self._d_dx_ = 1j * np.fft.fftfreq(self._N, d=1.0 / self._N) * 2.0 * np.pi / self._domain_size
22+
23+
def _d_dx(self, u: np.ndarray) -> np.ndarray:
24+
"""Compute the spatial derivative of `u` using spectral methods.
25+
26+
Parameters
27+
----------
28+
u : np.ndarray
29+
Array of shape (N,) representing the solution at the current time step.
30+
31+
Returns
32+
-------
33+
du_dx : np.ndarray
34+
Array of shape (N,) representing the spatial derivative of `u`.
35+
"""
36+
u_hat = np.fft.fft(u)
37+
du_dx_hat = u_hat * self._d_dx_
38+
du_dx = np.fft.ifft(du_dx_hat).real
39+
return du_dx
40+
41+
def initial_u0(self, mode: str) -> np.ndarray:
42+
"""Compute some initial conditions for the 1D viscous Burgers' equation."""
43+
44+
if mode == "sine":
45+
x = np.linspace(0, self._domain_size, self._N, endpoint=False)
46+
u0 = np.sin(x)
47+
48+
elif mode == "hat":
49+
x = np.linspace(0, self._domain_size, self._N, endpoint=False)
50+
u0 = np.where((x >= np.pi / 2) & (x <= 3 * np.pi / 2), 1.0, 0.0)
51+
52+
elif mode == "random":
53+
np.random.seed(42)
54+
u0 = np.random.rand(self._N)
55+
56+
else:
57+
raise ValueError(f"Unknown initial condition mode: {mode}")
58+
59+
return u0
60+
61+
def evalF(self, u: np.ndarray, t: float) -> np.ndarray:
62+
"""
63+
Evaluate the right-hand side of the 1D viscous Burgers' equation.
64+
65+
Parameters
66+
----------
67+
u : np.ndarray
68+
Array of shape (N,) representing the solution at the current time step.
69+
70+
Returns
71+
-------
72+
f : np.ndarray
73+
Array of shape (N,) representing the right-hand side evaluated at `u`.
74+
"""
75+
# Compute spatial derivatives using spectral methods
76+
u_hat = np.fft.fft(u)
77+
du_dx_hat = self._d_dx_ * u_hat
78+
d2u_dx2_hat = (self._d_dx_**2) * u_hat
79+
80+
du_dx = np.fft.ifft(du_dx_hat).real
81+
d2u_dx2 = np.fft.ifft(d2u_dx2_hat).real
82+
83+
f = -u * du_dx + self._nu * d2u_dx2
84+
return f
85+
86+
def int_f(self, u0: np.ndarray, dt: float, t: float = 0.0) -> np.ndarray:
87+
"""
88+
Compute the analytical solution of the 1D viscous Burgers' equation at time `t`.
89+
90+
See
91+
https://gitlab.inria.fr/sweet/sweet/-/blob/6f20b19f246bf6fcc7ace1b69567326d1da78635/src/programs/_pde_burgers/time/Burgers_Cart2D_TS_ln_cole_hopf.cpp
92+
93+
Parameters
94+
----------
95+
u0 : np.ndarray
96+
Array of shape (N,) representing the initial condition.
97+
t : float
98+
Time at which to evaluate the analytical solution.
99+
100+
Returns
101+
-------
102+
u_analytical : np.ndarray
103+
Array of shape (N,) representing the analytical solution at time `t`.
104+
"""
105+
106+
if self._nu < 0.05:
107+
print("Viscosity is very small which can lead to errors in analytical solution!")
108+
109+
u0_hat = np.fft.fft(u0)
110+
111+
# Divide by d/dx operator in spectral space
112+
tmp = np.zeros_like(u0_hat, dtype=complex)
113+
tmp[1:] = u0_hat[1:] / self._d_dx_[1:]
114+
115+
# Back to physical space
116+
phi = np.fft.ifft(tmp).real
117+
118+
# Apply exp(...)
119+
phi = np.exp(-phi / (2 * self._nu))
120+
121+
phi_hat = np.fft.fft(phi)
122+
123+
# Solve directly the heat equation in spectral space with exponential integration
124+
phi_hat = phi_hat * np.exp(self._nu * self._d_dx_**2 * (t+dt))
125+
126+
phi = np.fft.ifft(phi_hat)
127+
phi = np.log(phi)
128+
129+
phi_hat = np.fft.fft(phi)
130+
131+
u1_hat = -2.0 * self._nu * phi_hat * self._d_dx_
132+
return np.fft.ifft(u1_hat).real
133+
134+
def test(self):
135+
"""
136+
Run test for currently set up Burgers instance.
137+
"""
138+
x = np.linspace(0, self._domain_size, self._N, endpoint=False)
139+
w = 2.0 * np.pi / self._domain_size
140+
u0 = np.sin(x * w)
141+
142+
u1_analytical = np.cos(x * w) * w
143+
u1_num = self._d_dx(u0)
144+
145+
error: float = np.max(np.abs(u1_num - u1_analytical))
146+
147+
if error > 1e-10:
148+
raise AssertionError(f"Test failed: error {error} too large for domain size {self._domain_size}.")
149+
150+
def run_tests(self):
151+
"""
152+
Run basic tests to verify the correctness of the implementation.
153+
154+
This doesn't change the current instance, but will create new instances.
155+
"""
156+
157+
for domain_size in [2.0 * np.pi, 1.0, 9.0]:
158+
burgers = Burgers(N=128, nu=0.01, domain_size=domain_size)
159+
burgers.test()
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import numpy as np
2+
from qmat.playgrounds.martin.diff_eqs.de_solver import DESolver
3+
4+
5+
class Dahlquist(DESolver):
6+
"""
7+
Standard Dahlquist test equation with two eigenvalues.
8+
Optionally, some external frequency forcing can be added which is
9+
configurable through `ext_scalar`.
10+
11+
u(t) = exp(t*(lam1+lam2))*u(0) + s*sin(t)
12+
13+
d/dt u(t) = (lam1 + lam2) * (u(t) - s*sin(t)) + s*cos(t)
14+
"""
15+
16+
def __init__(self, lam1: complex, lam2: complex, ext_scalar: float = 0.0):
17+
# Lambda 1
18+
self.lam1: float = lam1
19+
20+
# Lambda 2
21+
self.lam2: float = lam2
22+
23+
# Scaling of external sin(t) frequency. Set to 0 to deactivate.
24+
self.ext_scalar = ext_scalar
25+
26+
def initial_u0(self, mode: str = None) -> np.ndarray:
27+
return np.array([1.0 + 0.0j], dtype=np.complex128)
28+
29+
def picardF(self, u: np.ndarray, dt: float, t: float) -> np.ndarray:
30+
"""
31+
Exactly integrate over one time step using the analytical solution
32+
(=Picard).
33+
"""
34+
lam = self.lam1 + self.lam2
35+
s = self.ext_scalar
36+
assert isinstance(t, float)
37+
retval = np.exp(t * lam) * u + s * np.sin(t)
38+
39+
assert retval.shape == u.shape
40+
return retval
41+
42+
def evalF(self, u: np.ndarray, t: float) -> np.ndarray:
43+
lam = self.lam1 + self.lam2
44+
s = self.ext_scalar
45+
retval = lam * (u - s * np.sin(t)) + s * np.cos(t)
46+
47+
assert retval.shape == u.shape
48+
return retval
49+
50+
def evalF1(self, u: np.ndarray, t: float) -> np.ndarray:
51+
lam = self.lam1
52+
retval = lam * u
53+
54+
assert retval.shape == u.shape
55+
return retval
56+
57+
def evalF2(self, u: np.ndarray, t: float) -> np.ndarray:
58+
lam = self.lam2
59+
s = self.ext_scalar
60+
retval = lam * (u - s * np.sin(t)) + s * np.cos(t)
61+
62+
assert retval.shape == u.shape
63+
return retval
64+
65+
def fSolve(self, u: np.ndarray, dt: float, t: float) -> np.ndarray:
66+
t1 = t + dt
67+
lam = self.lam1 + self.lam2
68+
s = self.ext_scalar
69+
70+
rhs = u - s * dt * (lam * np.sin(t1) - np.cos(t1))
71+
retval = rhs / (1.0 - dt * lam)
72+
73+
assert retval.shape == u.shape
74+
return retval
75+
76+
def fSolve1(self, u: np.ndarray, dt: float, t: float) -> np.ndarray:
77+
retval = u / (1.0 - dt * self.lam1)
78+
79+
assert retval.shape == u.shape
80+
return retval
81+
82+
def fSolve2(self, u: np.ndarray, dt: float, t: float) -> np.ndarray:
83+
t1 = t + dt
84+
lam = self.lam2
85+
s = self.ext_scalar
86+
87+
rhs = u - s * dt * (lam * np.sin(t1) - np.cos(t1))
88+
retval = rhs / (1.0 - dt * lam)
89+
90+
assert retval.shape == u.shape
91+
return retval
92+
93+
def int_f_t0(self, u0: np.ndarray, dt: float) -> np.ndarray:
94+
lam = self.lam1 + self.lam2
95+
s = self.ext_scalar
96+
assert isinstance(dt, float)
97+
retval = np.exp(dt * lam) * u0 + s * np.sin(dt)
98+
99+
assert retval.shape == u0.shape
100+
return retval
101+
102+
def int_f(self, u0: np.ndarray, dt: float, t: float = 0.0) -> np.ndarray:
103+
"""
104+
Integrate the solution from t0 to t1.
105+
"""
106+
107+
assert isinstance(t, (float, int))
108+
assert isinstance(dt, (float, int))
109+
110+
if t == 0:
111+
return self.int_f_t0(u0, dt=dt)
112+
113+
# Lambda
114+
lam = self.lam1 + self.lam2
115+
116+
s = self.ext_scalar
117+
118+
retval = np.exp(dt * lam) * (u0 - s*np.sin(t)) + s * np.sin(t+dt)
119+
120+
assert retval.shape == u0.shape
121+
return retval
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import numpy as np
2+
from qmat.playgrounds.martin.diff_eqs.de_solver import DESolver
3+
4+
5+
class Dahlquist2(DESolver):
6+
"""
7+
Modified Dahlquist test equation with superposition of two
8+
frequencies in the solution u(t).
9+
10+
u(t) = 0.5*(exp(lam1*t) + exp(lam2*t)) * u(0)
11+
"""
12+
13+
def __init__(self, lam1: complex, lam2: complex, s: float = 0.6):
14+
"""
15+
:param lam1: First eigenvalue (complex)
16+
:param lam2: Second eigenvalue (complex)
17+
:param s: Weighting between the two exponentials in the solution
18+
"""
19+
self.lam1: complex = lam1
20+
self.lam2: complex = lam2
21+
# Weighting between the two exponentials in the solution
22+
# to avoid division by 0
23+
self.s: float = s
24+
25+
assert 0 <= self.s <= 1, "s must be in [0,1]"
26+
27+
def initial_u0(self, mode: str = None) -> np.ndarray:
28+
return np.array([1.0 + 0.0j], dtype=np.complex128)
29+
30+
def evalF(self, u: np.ndarray, t: float) -> np.ndarray:
31+
retval = (
32+
(self.lam1 * self.s * np.exp(t * self.lam1) + self.lam2 * (1.0 - self.s) * np.exp(t * self.lam2))
33+
/ (self.s * np.exp(t * self.lam1) + (1.0 - self.s) * np.exp(t * self.lam2))
34+
* u
35+
)
36+
assert retval.shape == u.shape
37+
return retval
38+
39+
def int_f(self, u0: np.ndarray, dt: float, t: float = 0.0) -> np.ndarray:
40+
assert isinstance(dt, float)
41+
tfinal = t + dt
42+
retval = (self.s * np.exp(tfinal * self.lam1) + (1.0 - self.s) * np.exp(tfinal * self.lam2)) * u0
43+
assert retval.shape == u0.shape
44+
return retval

0 commit comments

Comments
 (0)