Skip to content

Commit 42ddf1a

Browse files
Added Nusselt number computation based on thermal and kinetic (#592)
dissipation
1 parent 8b5e6b5 commit 42ddf1a

File tree

6 files changed

+129
-39
lines changed

6 files changed

+129
-39
lines changed

pySDC/helpers/fieldsIO.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,8 @@ def fromFile(cls, fileName):
154154
fieldsIO : :class:`FieldsIO`
155155
The specialized `FieldsIO` adapted to the file.
156156
"""
157-
assert os.path.isfile(fileName), f"not a file ({fileName})"
157+
if not os.path.isfile(fileName):
158+
raise FileNotFoundError(f"not a file ({fileName})")
158159
with open(fileName, "rb") as f:
159160
STRUCT, DTYPE = np.fromfile(f, dtype=H_DTYPE, count=2)
160161
fieldsIO: FieldsIO = cls.STRUCTS[STRUCT](DTYPES[DTYPE], fileName)

pySDC/helpers/plot_helper.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,15 @@ def figsize_by_journal(journal, scale, ratio): # pragma: no cover
4545
'Springer_proceedings': 347.12354,
4646
'JSC_thesis': 434.26027,
4747
'TUHH_thesis': 426.79135,
48+
'Nature_CS': 372.0,
4849
}
4950
# store text height in points here, get this from LaTeX using \the\textheight
5051
textheights = {
5152
'JSC_beamer': 214.43411,
5253
'JSC_thesis': 635.5,
5354
'TUHH_thesis': 631.65118,
5455
'Springer_proceedings': 549.13828,
56+
'Nature_CS': 552.69478,
5557
}
5658
assert (
5759
journal in textwidths.keys()

pySDC/helpers/spectral_helper.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ def __init__(self, N, x0=None, x1=None, useGPU=False, useFFTW=False):
9797

9898
if useGPU:
9999
self.setup_GPU()
100+
self.logger.debug('Set up for GPU')
100101
else:
101102
self.setup_CPU(useFFTW=useFFTW)
102103

@@ -1026,7 +1027,6 @@ def __init__(self, comm=None, useGPU=False, debug=False):
10261027
self.BCs = None
10271028

10281029
self.fft_cache = {}
1029-
self.fft_dealias_shape_cache = {}
10301030

10311031
self.logger = logging.getLogger(name='Spectral Discretization')
10321032
if debug:
@@ -1313,11 +1313,12 @@ def setup_BCs(self):
13131313

13141314
diags = self.xp.ones(self.BCs.shape[0])
13151315
diags[self.BC_zero_index] = 0
1316-
self.BC_line_zero_matrix = sp.diags(diags)
1316+
self.BC_line_zero_matrix = sp.diags(diags).tocsc()
13171317

13181318
# prepare BCs in spectral space to easily add to the RHS
13191319
rhs_BCs = self.put_BCs_in_rhs(self.u_init)
1320-
self.rhs_BCs_hat = self.transform(rhs_BCs)
1320+
self.rhs_BCs_hat = self.transform(rhs_BCs).view(self.xp.ndarray)
1321+
del self.BC_rhs_mask
13211322

13221323
def check_BCs(self, u):
13231324
"""
@@ -1370,7 +1371,7 @@ def put_BCs_in_rhs_hat(self, rhs_hat):
13701371
Generate a mask where we need to set values in the rhs in spectral space to zero, such that can replace them
13711372
by the boundary conditions. The mask is then cached.
13721373
"""
1373-
self._rhs_hat_zero_mask = self.newDistArray().astype(bool)
1374+
self._rhs_hat_zero_mask = self.newDistArray(forward_output=True).astype(bool).view(self.xp.ndarray)
13741375

13751376
for axis in range(self.ndim):
13761377
for bc in self.full_BCs:
@@ -1518,7 +1519,7 @@ def get_indices(self, forward_output=True):
15181519
def get_pfft(self, axes=None, padding=None, grid=None):
15191520
if self.ndim == 1 or self.comm is None:
15201521
return None
1521-
from mpi4py_fft import PFFT
1522+
from mpi4py_fft import PFFT, newDistArray
15221523

15231524
axes = tuple(i for i in range(self.ndim)) if axes is None else axes
15241525
padding = list(padding if padding else [1.0 for _ in range(self.ndim)])
@@ -1550,6 +1551,10 @@ def no_transform(u, *args, **kwargs):
15501551
transforms=transforms,
15511552
grid=grid,
15521553
)
1554+
1555+
# do a transform to do the planning
1556+
_u = newDistArray(pfft, forward_output=False)
1557+
pfft.backward(pfft.forward(_u))
15531558
return pfft
15541559

15551560
def get_fft(self, axes=None, direction='object', padding=None, shape=None):
@@ -1905,6 +1910,7 @@ def get_differentiation_matrix(self, axes, **kwargs):
19051910
_D = self.axes[axis].get_differentiation_matrix(**kwargs)
19061911
D = D @ self.expand_matrix_ND(_D, axis)
19071912

1913+
self.logger.debug(f'Set up differentiation matrix along axes {axes} with kwargs {kwargs}')
19081914
return D
19091915

19101916
def get_integration_matrix(self, axes):
@@ -1968,4 +1974,5 @@ def get_basis_change_matrix(self, axes=None, **kwargs):
19681974
_C = self.axes[axis].get_basis_change_matrix(**kwargs)
19691975
C = C @ self.expand_matrix_ND(_C, axis)
19701976

1977+
self.logger.debug(f'Set up basis change matrix along axes {axes} with kwargs {kwargs}')
19711978
return C

pySDC/implementations/problem_classes/RayleighBenard3D.py

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,10 @@ def __init__(
141141
U01 = self.get_basis_change_matrix(p_in=0, p_out=1)
142142
U12 = self.get_basis_change_matrix(p_in=1, p_out=2)
143143
U02 = self.get_basis_change_matrix(p_in=0, p_out=2)
144+
self.eliminate_zeros(S1)
145+
self.eliminate_zeros(S2)
146+
self.eliminate_zeros(Dz)
147+
self.eliminate_zeros(Dzz)
144148

145149
self.Dx = Dx
146150
self.Dxx = Dxx
@@ -158,13 +162,12 @@ def __init__(
158162

159163
# construct operators
160164
_D = U02 @ (Dxx + Dyy) + Dzz
161-
L_lhs = {
162-
'p': {'u': U01 @ Dx, 'v': U01 @ Dy, 'w': Dz}, # divergence free constraint
163-
'u': {'p': U02 @ Dx, 'u': -self.nu * _D},
164-
'v': {'p': U02 @ Dy, 'v': -self.nu * _D},
165-
'w': {'p': U12 @ Dz, 'w': -self.nu * _D, 'T': -U02 @ Id},
166-
'T': {'T': -self.kappa * _D},
167-
}
165+
L_lhs = {}
166+
L_lhs['p'] = {'u': U01 @ Dx, 'v': U01 @ Dy, 'w': Dz} # divergence free constraint
167+
L_lhs['u'] = {'p': U02 @ Dx, 'u': -self.nu * _D}
168+
L_lhs['v'] = {'p': U02 @ Dy, 'v': -self.nu * _D}
169+
L_lhs['w'] = {'p': U12 @ Dz, 'w': -self.nu * _D, 'T': -U02 @ Id}
170+
L_lhs['T'] = {'T': -self.kappa * _D}
168171
self.setup_L(L_lhs)
169172

170173
# mass matrix
@@ -308,16 +311,30 @@ def compute_Nusselt_numbers(self, u):
308311
u: The solution you want to compute the Nusselt numbers of
309312
310313
Returns:
311-
dict: Nusselt number averaged over the entire volume and horizontally averaged at the top and bottom.
314+
dict: Nusselt number averaged over the entire volume and horizontally averaged at the top and bottom as well
315+
as computed from thermal and kinetic dissipation.
312316
"""
313-
iw, iT = self.index(['w', 'T'])
317+
iu, iv, iw, iT = self.index(['u', 'v', 'w', 'T'])
314318
zAxis = self.spectral.axes[-1]
315319

316320
if self.spectral_space:
317321
u_hat = u.copy()
318322
else:
319323
u_hat = self.transform(u)
320324

325+
# evaluate derivatives in x, y, and z for u, v, w, and T
326+
derivatives = []
327+
derivative_indices = [iu, iv, iw, iT]
328+
u_hat_flat = [u_hat[i].flatten() for i in derivative_indices]
329+
_D_u_hat = self.u_init_forward
330+
for D in [self.Dx, self.Dy, self.Dz]:
331+
_D_u_hat *= 0
332+
for i in derivative_indices:
333+
self.xp.copyto(_D_u_hat[i], (D @ u_hat_flat[i]).reshape(_D_u_hat[i].shape))
334+
derivatives.append(
335+
self.itransform(_D_u_hat).real
336+
) # derivatives[0] contains x derivatives, [2] is y and [3] is z
337+
321338
DzT_hat = (self.Dz @ u_hat[iT].flatten()).reshape(u_hat[iT].shape)
322339

323340
# compute wT with dealiasing
@@ -327,31 +344,56 @@ def compute_Nusselt_numbers(self, u):
327344
_me[0] = u_pad[iw] * u_pad[iT]
328345
wT_hat = self.transform(_me, padding=padding)[0]
329346

347+
# compute Nusselt number
330348
nusselt_hat = (wT_hat / self.kappa - DzT_hat) * self.axes[-1].L
331349

332-
if not hasattr(self, '_zInt'):
333-
self._zInt = zAxis.get_integration_matrix()
350+
# compute thermal dissipation
351+
thermal_dissipation = self.u_init_physical
352+
thermal_dissipation[0, ...] = (
353+
self.kappa * (derivatives[0][iT].real + derivatives[1][iT].real + derivatives[2][iT].real) ** 2
354+
)
355+
thermal_dissipation_hat = self.transform(thermal_dissipation)[0]
356+
357+
# compute kinetic energy dissipation
358+
kinetic_energy_dissipation = self.u_init_physical
359+
for i in [iu, iv, iw]:
360+
kinetic_energy_dissipation[0, ...] += (
361+
self.nu * (derivatives[0][i].real + derivatives[1][i].real + derivatives[2][i].real) ** 2
362+
)
363+
kinetic_energy_dissipation_hat = self.transform(kinetic_energy_dissipation)[0]
334364

335-
# get coefficients for evaluation on the boundary
365+
# get coefficients for evaluation on the boundary and vertical integration
366+
zInt = zAxis.get_integration_weights()
336367
top = zAxis.get_BC(kind='Dirichlet', x=1)
337368
bot = zAxis.get_BC(kind='Dirichlet', x=-1)
338369

370+
# compute volume averages
339371
integral_V = 0
372+
integral_V_th = 0
373+
integral_V_kin = 0
340374
if self.comm.rank == 0:
341375

342-
integral_z = (self._zInt @ nusselt_hat[0, 0]).real
343-
integral_z[0] = zAxis.get_integration_constant(integral_z, axis=-1)
344-
integral_V = ((top - bot) * integral_z).sum() * self.axes[0].L * self.axes[1].L / self.nx / self.ny
376+
integral_V = (zInt @ nusselt_hat[0, 0]).real * self.axes[0].L * self.axes[1].L / self.nx / self.ny
377+
integral_V_th = (
378+
(zInt @ thermal_dissipation_hat[0, 0]).real * self.axes[0].L * self.axes[1].L / self.nx / self.ny
379+
)
380+
integral_V_kin = (
381+
(zInt @ kinetic_energy_dissipation_hat[0, 0]).real * self.axes[0].L * self.axes[1].L / self.nx / self.ny
382+
)
345383

384+
# communicate result across all tasks
346385
Nusselt_V = self.comm.bcast(integral_V / self.spectral.V, root=0)
347-
348386
Nusselt_t = self.comm.bcast(self.xp.sum(nusselt_hat.real[0, 0, :] * top, axis=-1) / self.nx / self.ny, root=0)
349387
Nusselt_b = self.comm.bcast(self.xp.sum(nusselt_hat.real[0, 0] * bot / self.nx / self.ny, axis=-1), root=0)
388+
Nusselt_thermal = self.comm.bcast(1 / self.kappa * integral_V_th / self.spectral.V, root=0)
389+
Nusselt_kinetic = self.comm.bcast(1 + 1 / self.kappa * integral_V_kin / self.spectral.V, root=0)
350390

351391
return {
352392
'V': Nusselt_V,
353393
't': Nusselt_t,
354394
'b': Nusselt_b,
395+
'thermal': Nusselt_thermal,
396+
'kinetic': Nusselt_kinetic,
355397
}
356398

357399
def get_frequency_spectrum(self, u):

pySDC/implementations/problem_classes/generic_spectral.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,21 +132,29 @@ def __init__(
132132
if self.heterogeneous:
133133
self.__heterogeneous_setup = False
134134

135+
self.logger.debug('Finished GenericSpectralLinear __init__')
136+
135137
def heterogeneous_setup(self):
136138
if self.heterogeneous and not self.__heterogeneous_setup:
139+
137140
CPU_only = ['BC_line_zero_matrix', 'BCs']
138141
both = ['Pl', 'Pr', 'L', 'M']
139142

143+
self.logger.debug(f'Starting heterogeneous setup. Moving {CPU_only} and copying {both} to CPU')
144+
140145
if self.useGPU:
141146
for key in CPU_only:
147+
self.logger.debug(f'Moving {key} to CPU')
142148
setattr(self.spectral, key, getattr(self.spectral, key).get())
143149

144150
for key in both:
151+
self.logger.debug(f'Copying {key} to CPU')
145152
setattr(self, f'{key}_CPU', getattr(self, key).get())
146153
else:
147154
for key in both:
148155
setattr(self, f'{key}_CPU', getattr(self, key))
149156

157+
self.logger.debug('Done with heterogeneous setup')
150158
self.__heterogeneous_setup = True
151159

152160
def __getattr__(self, name):
@@ -195,6 +203,7 @@ def setup_L(self, LHS):
195203
LHS (dict): Dictionary containing the equations.
196204
"""
197205
self.L = self._setup_operator(LHS)
206+
self.logger.debug('Set up L matrix')
198207

199208
def setup_M(self, LHS):
200209
'''
@@ -203,6 +212,7 @@ def setup_M(self, LHS):
203212
diff_index = list(LHS.keys())
204213
self.diff_mask = [me in diff_index for me in self.components]
205214
self.M = self._setup_operator(LHS)
215+
self.logger.debug('Set up M matrix')
206216

207217
def setup_preconditioner(self, Dirichlet_recombination=True, left_preconditioner=True):
208218
"""
@@ -213,8 +223,14 @@ def setup_preconditioner(self, Dirichlet_recombination=True, left_preconditioner
213223
left_preconditioner (bool): If True, it will interleave the variables and reverse the Kronecker product
214224
"""
215225
N = np.prod(self.init[0][1:])
226+
if self.useGPU:
227+
from cupy_backends.cuda.libs.cusparse import CuSparseError as MemError
228+
else:
229+
MemError = MemoryError
216230

217231
if left_preconditioner:
232+
self.logger.debug(f'Setting up left preconditioner with {N} local degrees of freedom')
233+
218234
# reverse Kronecker product
219235
if self.spectral.useGPU:
220236
import scipy.sparse as sp
@@ -228,18 +244,27 @@ def setup_preconditioner(self, Dirichlet_recombination=True, left_preconditioner
228244
R[i * self.ncomponents + j, j * N + i] = 1
229245

230246
self.Pl = self.spectral.sparse_lib.csc_matrix(R, dtype=complex)
247+
248+
self.logger.debug('Finished setup of left preconditioner')
231249
else:
232250
Id = self.spectral.sparse_lib.eye(N)
233251
Pl_lhs = {comp: {comp: Id} for comp in self.components}
234252
self.Pl = self._setup_operator(Pl_lhs)
235253

236254
if Dirichlet_recombination and type(self.axes[-1]).__name__ in ['ChebychevHelper', 'UltrasphericalHelper']:
255+
self.logger.debug('Using Dirichlet recombination as right preconditioner')
237256
_Pr = self.spectral.get_Dirichlet_recombination_matrix(axis=-1)
238257
else:
239258
_Pr = self.spectral.sparse_lib.eye(N)
240259

241260
Pr_lhs = {comp: {comp: _Pr} for comp in self.components}
242-
self.Pr = self._setup_operator(Pr_lhs) @ self.Pl.T
261+
operator = self._setup_operator(Pr_lhs)
262+
263+
try:
264+
self.Pr = operator @ self.Pl.T
265+
except MemError:
266+
self.logger.debug('Setting up right preconditioner on CPU due to memory error')
267+
self.Pr = self.spectral.sparse_lib.csc_matrix(operator.get() @ self.Pl.T.get())
243268

244269
def solve_system(self, rhs, dt, u0=None, *args, skip_itransform=False, **kwargs):
245270
"""
@@ -330,6 +355,7 @@ def solve_system(self, rhs, dt, u0=None, *args, skip_itransform=False, **kwargs)
330355
import scipy.sparse as sp
331356

332357
cpu_decomp = sp.linalg.splu(A)
358+
333359
if self.useGPU:
334360
from cupyx.scipy.sparse.linalg import SuperLU
335361

0 commit comments

Comments
 (0)