Skip to content

Commit b460650

Browse files
Added Nusselt number computation to 3D RBC (#578)
1 parent ed741a6 commit b460650

File tree

3 files changed

+135
-52
lines changed

3 files changed

+135
-52
lines changed

pySDC/implementations/problem_classes/RayleighBenard3D.py

Lines changed: 48 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -298,62 +298,59 @@ def u_exact(self, t=0, noise_level=1e-3, seed=99):
298298
else:
299299
return me
300300

301-
def get_fig(self): # pragma: no cover
301+
def compute_Nusselt_numbers(self, u):
302302
"""
303-
Get a figure suitable to plot the solution of this problem
303+
Compute the various versions of the Nusselt number. This reflects the type of heat transport.
304+
If the Nusselt number is equal to one, it indicates heat transport due to conduction. If it is larger,
305+
advection is present.
306+
Computing the Nusselt number at various places can be used to check the code.
304307
305-
Returns
306-
-------
307-
self.fig : matplotlib.pyplot.figure.Figure
308-
"""
309-
import matplotlib.pyplot as plt
310-
from mpl_toolkits.axes_grid1 import make_axes_locatable
311-
312-
plt.rcParams['figure.constrained_layout.use'] = True
313-
self.fig, axs = plt.subplots(2, 1, sharex=True, sharey=True, figsize=((10, 5)))
314-
self.cax = []
315-
divider = make_axes_locatable(axs[0])
316-
self.cax += [divider.append_axes('right', size='3%', pad=0.03)]
317-
divider2 = make_axes_locatable(axs[1])
318-
self.cax += [divider2.append_axes('right', size='3%', pad=0.03)]
319-
return self.fig
320-
321-
def plot(self, u, t=None, fig=None, quantity='T'): # pragma: no cover
322-
r"""
323-
Plot the solution.
324-
325-
Parameters
326-
----------
327-
u : dtype_u
328-
Solution to be plotted
329-
t : float
330-
Time to display at the top of the figure
331-
fig : matplotlib.pyplot.figure.Figure
332-
Figure with the same structure as a figure generated by `self.get_fig`. If none is supplied, a new figure will be generated.
333-
quantity : (str)
334-
quantity you want to plot
335-
336-
Returns
337-
-------
338-
None
339-
"""
340-
fig = self.get_fig() if fig is None else fig
341-
axs = fig.axes
308+
Args:
309+
u: The solution you want to compute the Nusselt numbers of
342310
343-
imV = axs[1].pcolormesh(self.X, self.Z, self.compute_vorticity(u).real)
311+
Returns:
312+
dict: Nusselt number averaged over the entire volume and horizontally averaged at the top and bottom.
313+
"""
314+
iw, iT = self.index(['w', 'T'])
315+
zAxis = self.spectral.axes[-1]
344316

345317
if self.spectral_space:
346-
u = self.itransform(u)
318+
u_hat = u.copy()
319+
else:
320+
u_hat = self.transform(u)
347321

348-
imT = axs[0].pcolormesh(self.X, self.Z, u[self.index(quantity)].real)
322+
DzT_hat = (self.Dz @ u_hat[iT].flatten()).reshape(u_hat[iT].shape)
349323

350-
for i, label in zip([0, 1], [rf'${quantity}$', 'vorticity']):
351-
axs[i].set_aspect(1)
352-
axs[i].set_title(label)
324+
# compute wT with dealiasing
325+
padding = (self.dealiasing,) * self.ndim
326+
u_pad = self.itransform(u_hat, padding=padding).real
327+
_me = self.xp.zeros_like(u_pad)
328+
_me[0] = u_pad[iw] * u_pad[iT]
329+
wT_hat = self.transform(_me, padding=padding)[0]
353330

354-
if t is not None:
355-
fig.suptitle(f't = {t:.2f}')
356-
axs[1].set_xlabel(r'$x$')
357-
axs[1].set_ylabel(r'$z$')
358-
fig.colorbar(imT, self.cax[0])
359-
fig.colorbar(imV, self.cax[1])
331+
nusselt_hat = wT_hat - DzT_hat
332+
333+
if not hasattr(self, '_zInt'):
334+
self._zInt = zAxis.get_integration_matrix()
335+
336+
# get coefficients for evaluation on the boundary
337+
top = zAxis.get_BC(kind='Dirichlet', x=1)
338+
bot = zAxis.get_BC(kind='Dirichlet', x=-1)
339+
340+
integral_V = 0
341+
if self.comm.rank == 0:
342+
343+
integral_z = (self._zInt @ nusselt_hat[0, 0]).real
344+
integral_z[0] = zAxis.get_integration_constant(integral_z, axis=-1)
345+
integral_V = ((top - bot) * integral_z).sum() * self.axes[0].L * self.axes[1].L / self.nx / self.ny
346+
347+
Nusselt_V = self.comm.bcast(integral_V / self.spectral.V, root=0)
348+
349+
Nusselt_t = self.comm.bcast(self.xp.sum(nusselt_hat.real[0, 0, :] * top, axis=-1) / self.nx / self.ny, root=0)
350+
Nusselt_b = self.comm.bcast(self.xp.sum(nusselt_hat.real[0, 0] * bot / self.nx / self.ny, axis=-1), root=0)
351+
352+
return {
353+
'V': Nusselt_V,
354+
't': Nusselt_t,
355+
'b': Nusselt_b,
356+
}

pySDC/tests/test_helpers/test_spectral_helper_1d_ultraspherical.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,42 @@ def test_integration(N):
7171
assert np.allclose(poly.integ(lbnd=-1).coef[:-1], U_hat)
7272

7373

74+
@pytest.mark.base
75+
@pytest.mark.parametrize('N', [4, 7, 32])
76+
@pytest.mark.parametrize('x0', [-1, 0])
77+
def test_integration_rescaled_domain(N, x0, x1=1):
78+
import numpy as np
79+
from pySDC.helpers.spectral_helper import UltrasphericalHelper
80+
81+
helper = UltrasphericalHelper(N, x0=x0, x1=x1)
82+
83+
x = helper.get_1dgrid()
84+
y = N - 2
85+
u = x**y * (y + 1)
86+
u_hat = helper.transform(u)
87+
88+
S = helper.get_integration_matrix()
89+
U_hat = S @ u_hat
90+
U_hat[0] = helper.get_integration_constant(U_hat, axis=-1)
91+
92+
int_expect = x ** (y + 1)
93+
int_hat_expect = helper.transform(int_expect)
94+
95+
# compare indefinite integral
96+
assert np.allclose(int_hat_expect[1:], U_hat[1:])
97+
if x0 == 0:
98+
assert np.allclose(int_hat_expect[0], U_hat[0]), 'Integration constant is wrong!'
99+
100+
# compare definite integral
101+
top = helper.get_BC(kind='dirichlet', x=1)
102+
bot = helper.get_BC(kind='dirichlet', x=-1)
103+
res = ((top - bot) * U_hat).sum()
104+
if x0 == 0:
105+
assert np.isclose(res, 1)
106+
elif x0 == -1:
107+
assert np.isclose(res, 0 if y % 2 == 1 else 2)
108+
109+
74110
@pytest.mark.base
75111
@pytest.mark.parametrize('N', [6, 33])
76112
@pytest.mark.parametrize('deg', [1, 3])

pySDC/tests/test_problems/test_RayleighBenard3D.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,11 +292,61 @@ def test_heterogeneous_implementation():
292292
assert xp.allclose(*un)
293293

294294

295+
@pytest.mark.mpi4py
296+
@pytest.mark.parametrize('w', [0, 1, 3.14])
297+
def test_Nusselt_number_computation(w, N=4):
298+
from pySDC.implementations.problem_classes.RayleighBenard3D import RayleighBenard3D
299+
300+
prob = RayleighBenard3D(nx=N, ny=N, nz=N, dealiasing=1.0, spectral_space=False)
301+
xp = prob.xp
302+
iw, iT = prob.index(['w', 'T'])
303+
304+
# constant temperature and perturbed velocity
305+
u = prob.u_init
306+
u[iT, ...] = 3 * prob.Z**2 + 1
307+
u[iw] = w * (1 + xp.sin(prob.Y / prob.axes[1].L * 2 * xp.pi))
308+
Nu = prob.compute_Nusselt_numbers(u)
309+
310+
for key, expect in zip(['t', 'b', 'V'], [prob.Lz * (3 + 1) * w - 6, w, w * (1 + 1) - 3]):
311+
assert xp.isclose(Nu[key], expect), f'Expected Nu_{key}={expect}, but got {Nu[key]}'
312+
313+
# zero
314+
u = prob.u_init
315+
Nu = prob.compute_Nusselt_numbers(u)
316+
assert xp.allclose(list(Nu.values()), 0)
317+
318+
# constant gradient
319+
u = prob.u_init
320+
u[iT] = prob.Z**2 + 1
321+
Nu = prob.compute_Nusselt_numbers(u)
322+
323+
for key, expect in zip(['t', 'b', 'V'], [-prob.Lz * 2, 0, -1]):
324+
assert xp.isclose(Nu[key], expect), f'Expected Nu_{key}={expect}, but got {Nu[key]} with T=z**2!'
325+
326+
# gradient plus fluctuations
327+
u = prob.u_init
328+
u[iT] = prob.Z * 1 + (xp.sin(prob.X / prob.axes[0].L * 2 * xp.pi) * xp.sin(prob.Y / prob.axes[1].L * 2 * xp.pi))
329+
Nu = prob.compute_Nusselt_numbers(u)
330+
331+
for key in ['t', 'b', 'V']:
332+
assert xp.isclose(Nu[key], -1), f'Expected Nu_{key}=-1, but got {Nu[key]} with T=z*(1+sin(x)+sin(y))!'
333+
334+
# constant temperature and perturbed velocity
335+
u = prob.u_init
336+
u[iT, ...] = 1
337+
u[iw] = w * (1 + xp.sin(prob.Y / prob.axes[1].L * 2 * xp.pi))
338+
Nu = prob.compute_Nusselt_numbers(u)
339+
340+
for key in ['t', 'b', 'V']:
341+
assert xp.isclose(Nu[key], w), f'Expected Nu_{key}={w}, but got {Nu[key]} with constant T and perturbed w!'
342+
343+
295344
if __name__ == '__main__':
296345
# test_eval_f(2**2, 2**1, 'x', False)
297346
# test_libraries()
298347
# test_Poisson_problems(4, 'u')
299348
# test_Poisson_problem_w()
300349
# test_solver_convergence('bicgstab+ilu', 32, False, True)
301350
# test_banded_matrix(False)
302-
test_heterogeneous_implementation()
351+
# test_heterogeneous_implementation()
352+
test_Nusselt_number_computation(N=4, w=3.14)

0 commit comments

Comments
 (0)