Skip to content

Commit 3de8c94

Browse files
committed
Substantial refactor of spectral helper and 3D Rayleigh-Benard
1 parent 6cd748b commit 3de8c94

File tree

11 files changed

+1437
-744
lines changed

11 files changed

+1437
-744
lines changed

pySDC/helpers/spectral_helper.py

Lines changed: 444 additions & 562 deletions
Large diffs are not rendered by default.

pySDC/implementations/problem_classes/RayleighBenard.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ def eval_f(self, u, *args, **kwargs):
218218
Dx_u_hat = (self._Dx_expanded @ u_hat.flatten()).reshape(u_hat.shape)
219219
Dz_u_hat = (self._Dz_expanded @ u_hat.flatten()).reshape(u_hat.shape)
220220

221-
padding = [self.dealiasing, self.dealiasing]
221+
padding = (self.dealiasing, self.dealiasing)
222222
Dx_u_pad = self.itransform(Dx_u_hat, padding=padding).real
223223
Dz_u_pad = self.itransform(Dz_u_hat, padding=padding).real
224224
u_pad = self.itransform(u_hat, padding=padding).real
@@ -403,7 +403,7 @@ def compute_Nusselt_numbers(self, u):
403403
DzT_hat[iT] = (self.Dz @ u_hat[iT].flatten()).reshape(DzT_hat[iT].shape)
404404

405405
# compute vT with dealiasing
406-
padding = [self.dealiasing, self.dealiasing]
406+
padding = (self.dealiasing, self.dealiasing)
407407
u_pad = self.itransform(u_hat, padding=padding).real
408408
_me = self.xp.zeros_like(u_pad)
409409
_me[0] = u_pad[iv] * u_pad[iT]
Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
import numpy as np
2+
from mpi4py import MPI
3+
4+
from pySDC.implementations.problem_classes.generic_spectral import GenericSpectralLinear
5+
from pySDC.implementations.datatype_classes.mesh import mesh, imex_mesh
6+
from pySDC.core.convergence_controller import ConvergenceController
7+
from pySDC.core.hooks import Hooks
8+
from pySDC.implementations.convergence_controller_classes.check_convergence import CheckConvergence
9+
from pySDC.core.problem import WorkCounter
10+
11+
12+
class RayleighBenard3D(GenericSpectralLinear):
13+
"""
14+
Rayleigh-Benard Convection is a variation of incompressible Navier-Stokes.
15+
16+
The equations we solve are
17+
18+
u_x + v_y + w_z = 0
19+
T_t - kappa (T_xx + T_yy + T_zz) = -uT_x - vT_y - wT_z
20+
u_t - nu (u_xx + u_yy + u_zz) + p_x = -uu_x - vu_y - wu_z
21+
v_t - nu (v_xx + v_yy + v_zz) + p_y = -uv_x - vv_y - wv_z
22+
w_t - nu (w_xx + w_yy + w_zz) + p_z - T = -uw_x - vw_y - ww_z
23+
24+
with u the horizontal velocity, v the vertical velocity (in z-direction), T the temperature, p the pressure, indices
25+
denoting derivatives, kappa=(Rayleigh * Prandtl)**(-1/2) and nu = (Rayleigh / Prandtl)**(-1/2). Everything on the left
26+
hand side, that is the viscous part, the pressure gradient and the buoyancy due to temperature are treated
27+
implicitly, while the non-linear convection part on the right hand side is integrated explicitly.
28+
29+
The domain, vertical boundary conditions and pressure gauge are
30+
31+
Omega = [0, 8) x (-1, 1)
32+
T(z=+1) = 0
33+
T(z=-1) = 2
34+
u(z=+-1) = v(z=+-1) = 0
35+
integral over p = 0
36+
37+
The spectral discretization uses FFT horizontally, implying periodic BCs, and an ultraspherical method vertically to
38+
facilitate the Dirichlet BCs.
39+
40+
Parameters:
41+
Prandtl (float): Prandtl number
42+
Rayleigh (float): Rayleigh number
43+
nx (int): Horizontal resolution
44+
nz (int): Vertical resolution
45+
BCs (dict): Can specify boundary conditions here
46+
dealiasing (float): Dealiasing factor for evaluating the non-linear part
47+
comm (mpi4py.Intracomm): Space communicator
48+
"""
49+
50+
dtype_u = mesh
51+
dtype_f = imex_mesh
52+
53+
def __init__(
54+
self,
55+
Prandtl=1,
56+
Rayleigh=2e6,
57+
nx=256,
58+
ny=256,
59+
nz=64,
60+
BCs=None,
61+
dealiasing=1.5,
62+
comm=None,
63+
Lz=1,
64+
Lx=1,
65+
Ly=1,
66+
useGPU=False,
67+
**kwargs,
68+
):
69+
"""
70+
Constructor. `kwargs` are forwarded to parent class constructor.
71+
72+
Args:
73+
Prandtl (float): Prandtl number
74+
Rayleigh (float): Rayleigh number
75+
nx (int): Resolution in x-direction
76+
nz (int): Resolution in z direction
77+
BCs (dict): Vertical boundary conditions
78+
dealiasing (float): Dealiasing for evaluating the non-linear part in real space
79+
comm (mpi4py.Intracomm): Space communicator
80+
Lx (float): Horizontal length of the domain
81+
"""
82+
# TODO: documentation
83+
BCs = {} if BCs is None else BCs
84+
BCs = {
85+
'T_top': 0,
86+
'T_bottom': Lz,
87+
'w_top': 0,
88+
'w_bottom': 0,
89+
'v_top': 0,
90+
'v_bottom': 0,
91+
'u_top': 0,
92+
'u_bottom': 0,
93+
'p_integral': 0,
94+
**BCs,
95+
}
96+
if comm is None:
97+
try:
98+
from mpi4py import MPI
99+
100+
comm = MPI.COMM_WORLD
101+
except ModuleNotFoundError:
102+
pass
103+
self._makeAttributeAndRegister(
104+
'Prandtl',
105+
'Rayleigh',
106+
'nx',
107+
'ny',
108+
'nz',
109+
'BCs',
110+
'dealiasing',
111+
'comm',
112+
'Lx',
113+
'Ly',
114+
'Lz',
115+
localVars=locals(),
116+
readOnly=True,
117+
)
118+
119+
bases = [
120+
{'base': 'fft', 'N': nx, 'x0': 0, 'x1': self.Lx, 'useFFTW': not useGPU},
121+
{'base': 'fft', 'N': ny, 'x0': 0, 'x1': self.Ly, 'useFFTW': not useGPU},
122+
{'base': 'ultraspherical', 'N': nz, 'x0': 0, 'x1': self.Lz},
123+
]
124+
components = ['u', 'v', 'w', 'T', 'p']
125+
super().__init__(bases, components, comm=comm, useGPU=useGPU, **kwargs)
126+
127+
self.X, self.Y, self.Z = self.get_grid()
128+
self.Kx, self.Ky, self.Kz = self.get_wavenumbers()
129+
130+
# construct 3D matrices
131+
Dzz = self.get_differentiation_matrix(axes=(2,), p=2)
132+
Dz = self.get_differentiation_matrix(axes=(2,))
133+
Dy = self.get_differentiation_matrix(axes=(1,))
134+
Dyy = self.get_differentiation_matrix(axes=(1,), p=2)
135+
Dx = self.get_differentiation_matrix(axes=(0,))
136+
Dxx = self.get_differentiation_matrix(axes=(0,), p=2)
137+
Id = self.get_Id()
138+
139+
S1 = self.get_basis_change_matrix(p_out=0, p_in=1)
140+
S2 = self.get_basis_change_matrix(p_out=0, p_in=2)
141+
142+
U01 = self.get_basis_change_matrix(p_in=0, p_out=1)
143+
U12 = self.get_basis_change_matrix(p_in=1, p_out=2)
144+
U02 = self.get_basis_change_matrix(p_in=0, p_out=2)
145+
146+
self.Dx = Dx
147+
self.Dxx = Dxx
148+
self.Dy = Dy
149+
self.Dyy = Dyy
150+
self.Dz = S1 @ Dz
151+
self.Dzz = S2 @ Dzz
152+
153+
# compute rescaled Rayleigh number to extract viscosity and thermal diffusivity
154+
Ra = Rayleigh / (max([abs(BCs['T_top'] - BCs['T_bottom']), np.finfo(float).eps]) * self.axes[2].L ** 3)
155+
self.kappa = (Ra * Prandtl) ** (-1 / 2.0)
156+
self.nu = (Ra / Prandtl) ** (-1 / 2.0)
157+
158+
# construct operators
159+
L_lhs = {
160+
'p': {'u': U01 @ Dx, 'v': U01 @ Dy, 'w': Dz}, # divergence free constraint
161+
'u': {'p': U02 @ Dx, 'u': -self.nu * (U02 @ (Dxx + Dyy) + Dzz)},
162+
'v': {'p': U02 @ Dy, 'v': -self.nu * (U02 @ (Dxx + Dyy) + Dzz)},
163+
'w': {'p': U12 @ Dz, 'w': -self.nu * (U02 @ (Dxx + Dyy) + Dzz), 'T': -U02 @ Id},
164+
'T': {'T': -self.kappa * (U02 @ (Dxx + Dyy) + Dzz)},
165+
}
166+
self.setup_L(L_lhs)
167+
168+
# mass matrix
169+
M_lhs = {i: {i: U02 @ Id} for i in ['u', 'v', 'w', 'T']}
170+
self.setup_M(M_lhs)
171+
172+
# Prepare going from second (first for divergence free equation) derivative basis back to Chebychev-T
173+
self.base_change = self._setup_operator({**{comp: {comp: S2} for comp in ['u', 'v', 'w', 'T']}, 'p': {'p': S1}})
174+
175+
# BCs
176+
self.add_BC(
177+
component='p', equation='p', axis=2, v=self.BCs['p_integral'], kind='integral', line=-1, scalar=True
178+
)
179+
self.add_BC(component='T', equation='T', axis=2, x=-1, v=self.BCs['T_bottom'], kind='Dirichlet', line=-1)
180+
self.add_BC(component='T', equation='T', axis=2, x=1, v=self.BCs['T_top'], kind='Dirichlet', line=-2)
181+
self.add_BC(component='w', equation='w', axis=2, x=1, v=self.BCs['w_top'], kind='Dirichlet', line=-1)
182+
self.add_BC(component='w', equation='w', axis=2, x=-1, v=self.BCs['w_bottom'], kind='Dirichlet', line=-2)
183+
self.remove_BC(component='w', equation='w', axis=2, x=-1, kind='Dirichlet', line=-2, scalar=True)
184+
for comp in ['u', 'v']:
185+
self.add_BC(
186+
component=comp, equation=comp, axis=2, v=self.BCs[f'{comp}_top'], x=1, kind='Dirichlet', line=-2
187+
)
188+
self.add_BC(
189+
component=comp,
190+
equation=comp,
191+
axis=2,
192+
v=self.BCs[f'{comp}_bottom'],
193+
x=-1,
194+
kind='Dirichlet',
195+
line=-1,
196+
)
197+
198+
# eliminate Nyquist mode if needed
199+
if nx % 2 == 0:
200+
Nyquist_mode_index = self.axes[0].get_Nyquist_mode_index()
201+
for component in self.components:
202+
self.add_BC(
203+
component=component, equation=component, axis=0, kind='Nyquist', line=int(Nyquist_mode_index), v=0
204+
)
205+
if ny % 2 == 0:
206+
Nyquist_mode_index = self.axes[0].get_Nyquist_mode_index()
207+
for component in self.components:
208+
self.add_BC(
209+
component=component, equation=component, axis=1, kind='Nyquist', line=int(Nyquist_mode_index), v=0
210+
)
211+
self.setup_BCs()
212+
213+
self.work_counters['rhs'] = WorkCounter()
214+
215+
def eval_f(self, u, *args, **kwargs):
216+
f = self.f_init
217+
218+
if self.spectral_space:
219+
u_hat = u.copy()
220+
else:
221+
u_hat = self.transform(u)
222+
223+
f_impl_hat = self.u_init_forward
224+
225+
iu, iv, iw, iT, ip = self.index(['u', 'v', 'w', 'T', 'p'])
226+
227+
# evaluate implicit terms
228+
if not hasattr(self, '_L_T_base'):
229+
self._L_T_base = self.base_change @ self.L
230+
f_impl_hat = -(self._L_T_base @ u_hat.flatten()).reshape(u_hat.shape)
231+
232+
if self.spectral_space:
233+
f.impl[:] = f_impl_hat
234+
else:
235+
f.impl[:] = self.itransform(f_impl_hat).real
236+
237+
# -------------------------------------------
238+
# treat convection explicitly with dealiasing
239+
240+
# start by computing derivatives
241+
if not hasattr(self, '_Dx_expanded') or not hasattr(self, '_Dz_expanded'):
242+
Dz = self.Dz
243+
Dy = self.Dy
244+
Dx = self.Dx
245+
246+
self._Dx_expanded = self._setup_operator(
247+
{'u': {'u': Dx}, 'v': {'v': Dx}, 'w': {'w': Dx}, 'T': {'T': Dx}, 'p': {}}
248+
)
249+
self._Dy_expanded = self._setup_operator(
250+
{'u': {'u': Dy}, 'v': {'v': Dy}, 'w': {'w': Dy}, 'T': {'T': Dy}, 'p': {}}
251+
)
252+
self._Dz_expanded = self._setup_operator(
253+
{'u': {'u': Dz}, 'v': {'v': Dz}, 'w': {'w': Dz}, 'T': {'T': Dz}, 'p': {}}
254+
)
255+
Dx_u_hat = (self._Dx_expanded @ u_hat.flatten()).reshape(u_hat.shape)
256+
Dy_u_hat = (self._Dy_expanded @ u_hat.flatten()).reshape(u_hat.shape)
257+
Dz_u_hat = (self._Dz_expanded @ u_hat.flatten()).reshape(u_hat.shape)
258+
259+
padding = (self.dealiasing,) * self.ndim
260+
Dx_u_pad = self.itransform(Dx_u_hat, padding=padding).real
261+
Dy_u_pad = self.itransform(Dy_u_hat, padding=padding).real
262+
Dz_u_pad = self.itransform(Dz_u_hat, padding=padding).real
263+
u_pad = self.itransform(u_hat, padding=padding).real
264+
265+
fexpl_pad = self.xp.zeros_like(u_pad)
266+
fexpl_pad[iu][:] = -(u_pad[iu] * Dx_u_pad[iu] + u_pad[iv] * Dy_u_pad[iu] + u_pad[iw] * Dz_u_pad[iu])
267+
fexpl_pad[iv][:] = -(u_pad[iu] * Dx_u_pad[iv] + u_pad[iv] * Dy_u_pad[iv] + u_pad[iw] * Dz_u_pad[iv])
268+
fexpl_pad[iw][:] = -(u_pad[iu] * Dx_u_pad[iw] + u_pad[iv] * Dy_u_pad[iw] + u_pad[iw] * Dz_u_pad[iw])
269+
fexpl_pad[iT][:] = -(u_pad[iu] * Dx_u_pad[iT] + u_pad[iv] * Dy_u_pad[iT] + u_pad[iw] * Dz_u_pad[iT])
270+
271+
if self.spectral_space:
272+
f.expl[:] = self.transform(fexpl_pad, padding=padding)
273+
else:
274+
f.expl[:] = self.itransform(self.transform(fexpl_pad, padding=padding)).real
275+
276+
self.work_counters['rhs']()
277+
return f
278+
279+
def u_exact(self, t=0, noise_level=1e-3, seed=99):
280+
assert t == 0
281+
assert (
282+
self.BCs['v_top'] == self.BCs['v_bottom']
283+
), 'Initial conditions are only implemented for zero velocity gradient'
284+
285+
me = self.spectral.u_init
286+
iu, iw, iT, ip = self.index(['u', 'w', 'T', 'p'])
287+
288+
# linear temperature gradient
289+
assert self.Lz == 1
290+
for comp in ['T', 'u', 'v', 'w']:
291+
a = self.BCs[f'{comp}_top'] - self.BCs[f'{comp}_bottom']
292+
b = self.BCs[f'{comp}_bottom']
293+
me[self.index(comp)] = a * self.Z + b
294+
295+
# perturb slightly
296+
rng = self.xp.random.default_rng(seed=seed)
297+
298+
noise = self.spectral.u_init
299+
noise[iT] = rng.random(size=me[iT].shape)
300+
301+
me[iT] += noise[iT].real * noise_level * (self.Z - 1) * (self.Z + 1)
302+
303+
if self.spectral_space:
304+
me_hat = self.spectral.u_init_forward
305+
me_hat[:] = self.transform(me)
306+
return me_hat
307+
else:
308+
return me

0 commit comments

Comments
 (0)