Skip to content

Commit b5ed879

Browse files
Added spectrum computation to 3D RBC (#581)
* Added spectrum computation to 3D RBC * Added documentation
1 parent 42a5fce commit b5ed879

File tree

2 files changed

+126
-14
lines changed

2 files changed

+126
-14
lines changed

pySDC/implementations/problem_classes/RayleighBenard3D.py

Lines changed: 76 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ class RayleighBenard3D(GenericSpectralLinear):
2828
2929
The domain, vertical boundary conditions and pressure gauge are
3030
31-
Omega = [0, 8) x (-1, 1)
31+
Omega = [0, Lx) x [0, Ly] x (0, Lz)
3232
T(z=+1) = 0
33-
T(z=-1) = 2
33+
T(z=-1) = Lz
3434
u(z=+-1) = v(z=+-1) = 0
3535
integral over p = 0
3636
@@ -53,16 +53,16 @@ class RayleighBenard3D(GenericSpectralLinear):
5353
def __init__(
5454
self,
5555
Prandtl=1,
56-
Rayleigh=2e6,
57-
nx=256,
58-
ny=256,
59-
nz=64,
56+
Rayleigh=1e6,
57+
nx=64,
58+
ny=64,
59+
nz=32,
6060
BCs=None,
6161
dealiasing=1.5,
6262
comm=None,
6363
Lz=1,
64-
Lx=1,
65-
Ly=1,
64+
Lx=4,
65+
Ly=4,
6666
useGPU=False,
6767
**kwargs,
6868
):
@@ -79,7 +79,6 @@ def __init__(
7979
comm (mpi4py.Intracomm): Space communicator
8080
Lx (float): Horizontal length of the domain
8181
"""
82-
# TODO: documentation
8382
BCs = {} if BCs is None else BCs
8483
BCs = {
8584
'T_top': 0,
@@ -328,7 +327,7 @@ def compute_Nusselt_numbers(self, u):
328327
_me[0] = u_pad[iw] * u_pad[iT]
329328
wT_hat = self.transform(_me, padding=padding)[0]
330329

331-
nusselt_hat = wT_hat - DzT_hat
330+
nusselt_hat = (wT_hat / self.kappa - DzT_hat) * self.axes[-1].L
332331

333332
if not hasattr(self, '_zInt'):
334333
self._zInt = zAxis.get_integration_matrix()
@@ -354,3 +353,70 @@ def compute_Nusselt_numbers(self, u):
354353
't': Nusselt_t,
355354
'b': Nusselt_b,
356355
}
356+
357+
def get_frequency_spectrum(self, u):
358+
"""
359+
Compute the frequency spectrum of the velocities in x and y direction in the horizontal plane for every point in
360+
z. If the problem is well resolved, the coefficients will decay quickly with the wave number, and the reverse
361+
indicates that the resolution is too low.
362+
363+
The returned spectrum has three dimensions. The first is for component (i.e. u or v), the second is for every
364+
point in z and the third is the energy in every wave number.
365+
366+
Args:
367+
u: The solution you want to compute the spectrum of
368+
369+
Returns:
370+
RayleighBenard3D.xp.ndarray: wave numbers
371+
RayleighBenard3D.xp.ndarray: spectrum
372+
"""
373+
xp = self.xp
374+
indices = slice(0, 2)
375+
376+
# transform the solution to be in frequency space in x and y, but real space in z
377+
if self.spectral_space:
378+
u_hat = self.itransform(u, axes=(-1,))
379+
else:
380+
u_hat = self.transform(
381+
u,
382+
axes=(
383+
-3,
384+
-2,
385+
),
386+
)
387+
u_hat = self.spectral.redistribute(u_hat, axis=2, forward_output=False)
388+
389+
# compute "energy density" as absolute square of the velocity modes
390+
energy = (u_hat[indices] * xp.conjugate(u_hat[indices])).real / (self.axes[0].N ** 2 * self.axes[1].N ** 2)
391+
392+
# prepare wave numbers at which to compute the spectrum
393+
abs_kx = xp.abs(self.Kx[:, :, 0])
394+
abs_ky = xp.abs(self.Ky[:, :, 0])
395+
396+
unique_k = xp.unique(xp.append(xp.unique(abs_kx), xp.unique(abs_ky)))
397+
n_k = len(unique_k)
398+
399+
# compute local spectrum
400+
local_spectrum = self.xp.empty(shape=(2, energy.shape[3], n_k))
401+
for i, k in zip(range(n_k), unique_k):
402+
mask = xp.logical_or(abs_kx == k, abs_ky == k)
403+
local_spectrum[..., i] = xp.sum(energy[indices, mask, :], axis=1)
404+
405+
# assemble global spectrum from local spectra
406+
k_all = self.comm.allgather(unique_k)
407+
unique_k_all = []
408+
for k in k_all:
409+
unique_k_all = xp.unique(xp.append(unique_k_all, xp.unique(k)))
410+
n_k_all = len(unique_k_all)
411+
412+
spectra = self.comm.allgather(local_spectrum)
413+
spectrum = self.xp.zeros(shape=(2, self.axes[2].N, n_k_all))
414+
for ks, _spectrum in zip(k_all, spectra):
415+
ks = list(ks)
416+
unique_k_all = list(unique_k_all)
417+
for k in ks:
418+
index_global = unique_k_all.index(k)
419+
index_local = ks.index(k)
420+
spectrum[..., index_global] += _spectrum[..., index_local]
421+
422+
return xp.array(unique_k_all), spectrum

pySDC/tests/test_problems/test_RayleighBenard3D.py

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ def test_eval_f(nx, nz, direction, spectral_space):
1111
import numpy as np
1212
from pySDC.implementations.problem_classes.RayleighBenard3D import RayleighBenard3D
1313

14-
P = RayleighBenard3D(nx=nx, ny=nx, nz=nz, Rayleigh=1, spectral_space=spectral_space)
14+
P = RayleighBenard3D(nx=nx, ny=nx, nz=nz, Rayleigh=1, spectral_space=spectral_space, Lx=1, Ly=1, Lz=1)
1515
iu, iv, iw, ip, iT = P.index(['u', 'v', 'w', 'p', 'T'])
1616
X, Y, Z = P.X, P.Y, P.Z
1717
cos, sin = np.cos, np.sin
@@ -297,7 +297,7 @@ def test_heterogeneous_implementation():
297297
def test_Nusselt_number_computation(w, N=4):
298298
from pySDC.implementations.problem_classes.RayleighBenard3D import RayleighBenard3D
299299

300-
prob = RayleighBenard3D(nx=N, ny=N, nz=N, dealiasing=1.0, spectral_space=False)
300+
prob = RayleighBenard3D(nx=N, ny=N, nz=N, dealiasing=1.0, spectral_space=False, Rayleigh=1.0, Prandtl=1.0)
301301
xp = prob.xp
302302
iw, iT = prob.index(['w', 'T'])
303303

@@ -341,12 +341,58 @@ def test_Nusselt_number_computation(w, N=4):
341341
assert xp.isclose(Nu[key], w), f'Expected Nu_{key}={w}, but got {Nu[key]} with constant T and perturbed w!'
342342

343343

344+
@pytest.mark.mpi4py
345+
@pytest.mark.mpi(ranks=[1, 2, 5])
346+
def test_spectrum_computation(mpi_ranks):
347+
from pySDC.implementations.problem_classes.RayleighBenard3D import RayleighBenard3D
348+
349+
N = 5
350+
prob = RayleighBenard3D(nx=N, ny=N, nz=2, dealiasing=1.0, spectral_space=False, Rayleigh=1.0)
351+
xp = prob.xp
352+
iu, iv = prob.index(['u', 'v'])
353+
354+
num_k = int(N // 2) + 1
355+
356+
u = prob.u_exact() * 0
357+
u[iu] = 1
358+
ks, spectrum = prob.get_frequency_spectrum(u)
359+
360+
expect_spectrum = xp.zeros((prob.nz, num_k))
361+
expect_spectrum[:, 0] = 1
362+
363+
assert len(ks) == num_k
364+
assert xp.allclose(spectrum[iv], 0)
365+
assert xp.allclose(spectrum[iu], expect_spectrum)
366+
367+
assert N >= 5
368+
u = prob.u_exact() * 0
369+
u[iu] = xp.sin(prob.Y * 2 * xp.pi / prob.axes[1].L)
370+
ks, spectrum = prob.get_frequency_spectrum(u)
371+
assert xp.allclose(spectrum[iu, :, 2:], 0)
372+
assert not xp.allclose(spectrum[iu, :, :2], 0)
373+
374+
assert N >= 5
375+
u = prob.u_exact() * 0
376+
u[iu] = xp.sin(prob.X * 2 * xp.pi / prob.axes[0].L) * xp.sin(prob.Y * 2 * xp.pi / prob.axes[1].L)
377+
ks, spectrum = prob.get_frequency_spectrum(u)
378+
assert xp.allclose(spectrum[iu, :, 2:], 0)
379+
assert xp.allclose(spectrum[iu, :, 0], 0)
380+
assert not xp.allclose(spectrum[iu, :, 1], 0)
381+
382+
assert N >= 5
383+
u = prob.u_exact() * 0
384+
u[iu] = xp.sin(prob.X * 4 * xp.pi / prob.axes[0].L) * xp.sin(prob.Y * 2 * xp.pi / prob.axes[1].L)
385+
ks, spectrum = prob.get_frequency_spectrum(u)
386+
assert not xp.allclose(spectrum[iu, :, 1:], 0)
387+
assert xp.allclose(spectrum[iu, :, 0], 0)
388+
389+
344390
if __name__ == '__main__':
345-
# test_eval_f(2**2, 2**1, 'x', False)
391+
test_eval_f(2**2, 2**1, 'x', False)
346392
# test_libraries()
347393
# test_Poisson_problems(4, 'u')
348394
# test_Poisson_problem_w()
349395
# test_solver_convergence('bicgstab+ilu', 32, False, True)
350396
# test_banded_matrix(False)
351397
# test_heterogeneous_implementation()
352-
test_Nusselt_number_computation(N=4, w=3.14)
398+
# test_Nusselt_number_computation(N=4, w=3.14)

0 commit comments

Comments
 (0)