Skip to content

Commit a6f4cec

Browse files
committed
Merge remote-tracking branch 'upstream/master' into update_gusto
2 parents 7b1f22c + 0dbc979 commit a6f4cec

File tree

19 files changed

+1227
-45
lines changed

19 files changed

+1227
-45
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: 32 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

@@ -161,6 +162,10 @@ def get_differentiation_matrix(self):
161162
def get_integration_matrix(self):
162163
raise NotImplementedError()
163164

165+
def get_integration_weights(self):
166+
"""Weights for integration across entire domain"""
167+
raise NotImplementedError()
168+
164169
def get_wavenumbers(self):
165170
"""
166171
Get the grid in spectral space
@@ -379,6 +384,16 @@ def get_integration_matrix(self, lbnd=0):
379384
raise NotImplementedError(f'This function allows to integrate only from x=0, you attempted from x={lbnd}.')
380385
return S
381386

387+
def get_integration_weights(self):
388+
"""Weights for integration across entire domain"""
389+
n = self.xp.arange(self.N, dtype=float)
390+
391+
weights = (-1) ** n + 1
392+
weights[2:] /= 1 - (n**2)[2:]
393+
394+
weights /= 2 / self.L
395+
return weights
396+
382397
def get_differentiation_matrix(self, p=1):
383398
'''
384399
Keep in mind that the T2T differentiation matrix is dense.
@@ -808,6 +823,12 @@ def get_integration_matrix(self, p=1):
808823
k[0] = 1j * self.L
809824
return self.linalg.matrix_power(self.sparse_lib.diags(1 / (1j * k)), p)
810825

826+
def get_integration_weights(self):
827+
"""Weights for integration across entire domain"""
828+
weights = self.xp.zeros(self.N)
829+
weights[0] = self.L / self.N
830+
return weights
831+
811832
def get_plan(self, u, forward, *args, **kwargs):
812833
if self.fft_lib.__name__ == 'mpi4py_fft.fftw':
813834
if 'axes' in kwargs.keys():
@@ -1006,7 +1027,6 @@ def __init__(self, comm=None, useGPU=False, debug=False):
10061027
self.BCs = None
10071028

10081029
self.fft_cache = {}
1009-
self.fft_dealias_shape_cache = {}
10101030

10111031
self.logger = logging.getLogger(name='Spectral Discretization')
10121032
if debug:
@@ -1293,11 +1313,12 @@ def setup_BCs(self):
12931313

12941314
diags = self.xp.ones(self.BCs.shape[0])
12951315
diags[self.BC_zero_index] = 0
1296-
self.BC_line_zero_matrix = sp.diags(diags)
1316+
self.BC_line_zero_matrix = sp.diags(diags).tocsc()
12971317

12981318
# prepare BCs in spectral space to easily add to the RHS
12991319
rhs_BCs = self.put_BCs_in_rhs(self.u_init)
1300-
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
13011322

13021323
def check_BCs(self, u):
13031324
"""
@@ -1350,7 +1371,7 @@ def put_BCs_in_rhs_hat(self, rhs_hat):
13501371
Generate a mask where we need to set values in the rhs in spectral space to zero, such that can replace them
13511372
by the boundary conditions. The mask is then cached.
13521373
"""
1353-
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)
13541375

13551376
for axis in range(self.ndim):
13561377
for bc in self.full_BCs:
@@ -1498,7 +1519,7 @@ def get_indices(self, forward_output=True):
14981519
def get_pfft(self, axes=None, padding=None, grid=None):
14991520
if self.ndim == 1 or self.comm is None:
15001521
return None
1501-
from mpi4py_fft import PFFT
1522+
from mpi4py_fft import PFFT, newDistArray
15021523

15031524
axes = tuple(i for i in range(self.ndim)) if axes is None else axes
15041525
padding = list(padding if padding else [1.0 for _ in range(self.ndim)])
@@ -1530,6 +1551,10 @@ def no_transform(u, *args, **kwargs):
15301551
transforms=transforms,
15311552
grid=grid,
15321553
)
1554+
1555+
# do a transform to do the planning
1556+
_u = newDistArray(pfft, forward_output=False)
1557+
pfft.backward(pfft.forward(_u))
15331558
return pfft
15341559

15351560
def get_fft(self, axes=None, direction='object', padding=None, shape=None):
@@ -1885,6 +1910,7 @@ def get_differentiation_matrix(self, axes, **kwargs):
18851910
_D = self.axes[axis].get_differentiation_matrix(**kwargs)
18861911
D = D @ self.expand_matrix_ND(_D, axis)
18871912

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

18901916
def get_integration_matrix(self, axes):
@@ -1948,4 +1974,5 @@ def get_basis_change_matrix(self, axes=None, **kwargs):
19481974
_C = self.axes[axis].get_basis_change_matrix(**kwargs)
19491975
C = C @ self.expand_matrix_ND(_C, axis)
19501976

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

pySDC/implementations/problem_classes/RayleighBenard3D.py

Lines changed: 75 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):
@@ -420,3 +462,20 @@ def get_frequency_spectrum(self, u):
420462
spectrum[..., index_global] += _spectrum[..., index_local]
421463

422464
return xp.array(unique_k_all), spectrum
465+
466+
def get_vertical_profiles(self, u, components):
467+
if self.spectral_space:
468+
u_hat = u.copy()
469+
else:
470+
u_hat = self.transform(u)
471+
472+
_u_hat = self.axes[-1].itransform(u_hat, axes=(-1,))
473+
474+
avgs = {}
475+
for c in components:
476+
i = self.index(c)
477+
avg = self.xp.ascontiguousarray(_u_hat[i, 0, 0, :].real) / self.axes[0].N / self.axes[1].N
478+
self.comm.Bcast(avg, root=0)
479+
avgs[c] = avg
480+
481+
return avgs

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)