Skip to content

Commit d8e4e3a

Browse files
committed
Merge remote-tracking branch 'origin/master' into neuralpint
2 parents 3254981 + 5099de8 commit d8e4e3a

File tree

12 files changed

+300
-233
lines changed

12 files changed

+300
-233
lines changed

pySDC/helpers/spectral_helper.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,8 @@ def get_BC(self, kind, **kwargs):
535535
return self.get_integ_BC_row(**kwargs)
536536
elif kind.lower() == 'dirichlet':
537537
return self.get_Dirichlet_BC_row(**kwargs)
538+
elif kind.lower() == 'neumann':
539+
return self.get_Neumann_BC_row(**kwargs)
538540
else:
539541
return super().get_BC(kind)
540542

@@ -574,6 +576,27 @@ def get_Dirichlet_BC_row(self, x):
574576
else:
575577
raise NotImplementedError(f'Don\'t know how to generate Dirichlet BC\'s at {x=}!')
576578

579+
def get_Neumann_BC_row(self, x):
580+
"""
581+
Get a row for generating Neumann BCs at x with T polynomials.
582+
583+
Args:
584+
x (float): Position of the boundary condition
585+
586+
Returns:
587+
self.xp.ndarray: Row to put into a matrix
588+
"""
589+
n = self.xp.arange(self.N, dtype='D')
590+
nn = n**2
591+
if x == -1:
592+
me = nn
593+
me[1:] *= (-1) ** n[:-1]
594+
return me
595+
elif x == 1:
596+
return nn
597+
else:
598+
raise NotImplementedError(f'Don\'t know how to generate Neumann BC\'s at {x=}!')
599+
577600
def get_Dirichlet_recombination_matrix(self):
578601
'''
579602
Get matrix for Dirichlet recombination, which changes the basis to have sparse boundary conditions.

pySDC/implementations/hooks/log_solution.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ class LogToFile(Hooks):
208208
filename = 'myRun.pySDC'
209209
time_increment = 0
210210
allow_overwriting = False
211+
counter = 0 # number of stored time points in the file
211212

212213
def __init__(self):
213214
super().__init__()
@@ -224,8 +225,9 @@ def pre_run(self, step, level_number):
224225
if os.path.isfile(self.filename) and L.time > 0:
225226
L.prob.setUpFieldsIO()
226227
self.outfile = FieldsIO.fromFile(self.filename)
228+
self.counter = len(self.outfile.times)
227229
self.logger.info(
228-
f'Set up file {self.filename!r} for writing output. This file already contains solutions up to t={self.outfile.times[-1]:.4f}.'
230+
f'Set up file {self.filename!r} for writing output. This file already contains {self.counter} solutions up to t={self.outfile.times[-1]:.4f}.'
229231
)
230232
else:
231233
self.outfile = L.prob.getOutputFile(self.filename)
@@ -236,6 +238,9 @@ def pre_run(self, step, level_number):
236238
self.outfile.addField(time=L.time, field=L.prob.processSolutionForOutput(L.u[0]))
237239
self.logger.info(f'Written initial conditions at t={L.time:4f} to file')
238240

241+
type(self).counter = len(self.outfile.times)
242+
self.logger.info(f'Will write to disk every {self.time_increment:.4e} time units')
243+
239244
def post_step(self, step, level_number):
240245
if level_number > 0:
241246
return None
@@ -252,6 +257,20 @@ def post_step(self, step, level_number):
252257
self.outfile.addField(time=L.time + L.dt, field=L.prob.processSolutionForOutput(L.uend))
253258
self.logger.info(f'Written solution at t={L.time+L.dt:.4f} to file')
254259
self.t_next_log = max([L.time + L.dt, self.t_next_log]) + self.time_increment
260+
type(self).counter = len(self.outfile.times)
261+
262+
def post_run(self, step, level_number):
263+
if level_number > 0:
264+
return None
265+
266+
L = step.levels[level_number]
267+
268+
value_exists = True in [abs(me - (L.time + L.dt)) < np.finfo(float).eps * 1000 for me in self.outfile.times]
269+
if not value_exists:
270+
self.outfile.addField(time=L.time + L.dt, field=L.prob.processSolutionForOutput(L.uend))
271+
self.logger.info(f'Written solution at t={L.time+L.dt:.4f} to file')
272+
self.t_next_log = max([L.time + L.dt, self.t_next_log]) + self.time_increment
273+
type(self).counter = len(self.outfile.times)
255274

256275
@classmethod
257276
def load(cls, index):

pySDC/implementations/problem_classes/RayleighBenard.py

Lines changed: 65 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ def __init__(
5858
BCs=None,
5959
dealiasing=3 / 2,
6060
comm=None,
61-
Lx=8,
61+
Lx=4,
62+
Lz=1,
63+
z0=0,
6264
**kwargs,
6365
):
6466
"""
@@ -73,11 +75,13 @@ def __init__(
7375
dealiasing (float): Dealiasing for evaluating the non-linear part in real space
7476
comm (mpi4py.Intracomm): Space communicator
7577
Lx (float): Horizontal length of the domain
78+
Lz (float): Vertical length of the domain
79+
z0 (float): Position of lower boundary
7680
"""
7781
BCs = {} if BCs is None else BCs
7882
BCs = {
7983
'T_top': 0,
80-
'T_bottom': 2,
84+
'T_bottom': 1,
8185
'v_top': 0,
8286
'v_bottom': 0,
8387
'u_top': 0,
@@ -101,11 +105,16 @@ def __init__(
101105
'dealiasing',
102106
'comm',
103107
'Lx',
108+
'Lz',
109+
'z0',
104110
localVars=locals(),
105111
readOnly=True,
106112
)
107113

108-
bases = [{'base': 'fft', 'N': nx, 'x0': 0, 'x1': self.Lx}, {'base': 'ultraspherical', 'N': nz}]
114+
bases = [
115+
{'base': 'fft', 'N': nx, 'x0': 0, 'x1': self.Lx},
116+
{'base': 'ultraspherical', 'N': nz, 'x0': self.z0, 'x1': self.Lz},
117+
]
109118
components = ['u', 'v', 'T', 'p']
110119
super().__init__(bases, components, comm=comm, **kwargs)
111120

@@ -247,8 +256,8 @@ def u_exact(self, t=0, noise_level=1e-3, seed=99):
247256

248257
# linear temperature gradient
249258
for comp in ['T', 'v', 'u']:
250-
a = (self.BCs[f'{comp}_top'] - self.BCs[f'{comp}_bottom']) / 2
251-
b = (self.BCs[f'{comp}_top'] + self.BCs[f'{comp}_bottom']) / 2
259+
a = (self.BCs[f'{comp}_top'] - self.BCs[f'{comp}_bottom']) / self.Lz
260+
b = self.BCs[f'{comp}_bottom'] - a * self.z0
252261
me[self.index(comp)] = a * self.Z + b
253262

254263
# perturb slightly
@@ -257,7 +266,7 @@ def u_exact(self, t=0, noise_level=1e-3, seed=99):
257266
noise = self.spectral.u_init
258267
noise[iT] = rng.random(size=me[iT].shape)
259268

260-
me[iT] += noise[iT].real * noise_level * (self.Z - 1) * (self.Z + 1)
269+
me[iT] += noise[iT].real * noise_level * (self.Z - self.z0) * (self.Z - self.z0 + self.Lz)
261270

262271
if self.spectral_space:
263272
me_hat = self.spectral.u_init_forward
@@ -370,14 +379,41 @@ def compute_vorticity(self, u):
370379
u_hat = u.copy()
371380
else:
372381
u_hat = self.transform(u)
382+
373383
Dz = self.Dz
374384
Dx = self.Dx
375385
iu, iv = self.index(['u', 'v'])
376386

377387
vorticity_hat = self.spectral.u_init_forward
378-
vorticity_hat[0] = (Dx * u_hat[iv].flatten() + Dz @ u_hat[iu].flatten()).reshape(u[iu].shape)
388+
vorticity_hat[0] = (Dx * u_hat[iv].flatten() + Dz @ u_hat[iu].flatten()).reshape(u_hat[iu].shape)
379389
return self.itransform(vorticity_hat)[0].real
380390

391+
def getOutputFile(self, fileName):
392+
from pySDC.helpers.fieldsIO import Rectilinear
393+
394+
self.setUpFieldsIO()
395+
396+
coords = [me.get_1dgrid() for me in self.spectral.axes]
397+
assert np.allclose([len(me) for me in coords], self.spectral.global_shape[1:])
398+
399+
fOut = Rectilinear(np.float64, fileName=fileName)
400+
fOut.setHeader(nVar=len(self.components) + 1, coords=coords)
401+
fOut.initialize()
402+
return fOut
403+
404+
def processSolutionForOutput(self, u):
405+
vorticity = self.compute_vorticity(u)
406+
407+
if self.spectral_space:
408+
u_real = self.itransform(u).real
409+
else:
410+
u_real = u.real
411+
412+
me = np.empty(shape=(u_real.shape[0] + 1, *vorticity.shape))
413+
me[:-1] = u_real
414+
me[-1] = vorticity
415+
return me
416+
381417
def compute_Nusselt_numbers(self, u):
382418
"""
383419
Compute the various versions of the Nusselt number. This reflects the type of heat transport.
@@ -392,52 +428,46 @@ def compute_Nusselt_numbers(self, u):
392428
dict: Nusselt number averaged over the entire volume and horizontally averaged at the top and bottom.
393429
"""
394430
iv, iT = self.index(['v', 'T'])
395-
396-
DzT_hat = self.spectral.u_init_forward
431+
zAxis = self.spectral.axes[-1]
397432

398433
if self.spectral_space:
399434
u_hat = u.copy()
400435
else:
401436
u_hat = self.transform(u)
402437

403-
DzT_hat[iT] = (self.Dz @ u_hat[iT].flatten()).reshape(DzT_hat[iT].shape)
438+
DzT_hat = (self.Dz @ u_hat[iT].flatten()).reshape(u_hat[iT].shape)
404439

405440
# compute vT with dealiasing
406441
padding = (self.dealiasing, self.dealiasing)
407442
u_pad = self.itransform(u_hat, padding=padding).real
408443
_me = self.xp.zeros_like(u_pad)
409444
_me[0] = u_pad[iv] * u_pad[iT]
410-
vT_hat = self.transform(_me, padding=padding)
445+
vT_hat = self.transform(_me, padding=padding)[0]
411446

412-
nusselt_hat = (vT_hat[0] - DzT_hat[iT]) / self.nx
413-
nusselt_no_v_hat = (-DzT_hat[iT]) / self.nx
447+
if not hasattr(self, '_zInt'):
448+
self._zInt = zAxis.get_integration_matrix()
414449

415-
integral_z = self.xp.sum(nusselt_hat * self.spectral.axes[1].get_BC(kind='integral'), axis=-1).real
416-
integral_V = (
417-
integral_z[0] * self.axes[0].L
418-
) # only the first Fourier mode has non-zero integral with periodic BCs
419-
Nusselt_V = self.comm.bcast(integral_V / self.spectral.V, root=0)
450+
nusselt_hat = (vT_hat / self.kappa - DzT_hat) * self.axes[-1].L
420451

421-
Nusselt_t = self.comm.bcast(
422-
self.xp.sum(nusselt_hat * self.spectral.axes[1].get_BC(kind='Dirichlet', x=1), axis=-1).real[0], root=0
423-
)
424-
Nusselt_b = self.comm.bcast(
425-
self.xp.sum(nusselt_hat * self.spectral.axes[1].get_BC(kind='Dirichlet', x=-1), axis=-1).real[0], root=0
426-
)
427-
Nusselt_no_v_t = self.comm.bcast(
428-
self.xp.sum(nusselt_no_v_hat * self.spectral.axes[1].get_BC(kind='Dirichlet', x=1), axis=-1).real[0], root=0
429-
)
430-
Nusselt_no_v_b = self.comm.bcast(
431-
self.xp.sum(nusselt_no_v_hat * self.spectral.axes[1].get_BC(kind='Dirichlet', x=-1), axis=-1).real[0],
432-
root=0,
433-
)
452+
# get coefficients for evaluation on the boundary
453+
top = zAxis.get_BC(kind='Dirichlet', x=1)
454+
bot = zAxis.get_BC(kind='Dirichlet', x=-1)
455+
456+
integral_V = 0
457+
if self.comm.rank == 0:
458+
459+
integral_z = (self._zInt @ nusselt_hat[0]).real
460+
integral_z[0] = zAxis.get_integration_constant(integral_z, axis=-1)
461+
integral_V = ((top - bot) * integral_z).sum() * self.axes[0].L / self.nx
462+
463+
Nusselt_V = self.comm.bcast(integral_V / self.spectral.V, root=0)
464+
Nusselt_t = self.comm.bcast(self.xp.sum(nusselt_hat.real[0] * top, axis=-1) / self.nx, root=0)
465+
Nusselt_b = self.comm.bcast(self.xp.sum(nusselt_hat.real[0] * bot, axis=-1) / self.nx, root=0)
434466

435467
return {
436468
'V': Nusselt_V,
437469
't': Nusselt_t,
438470
'b': Nusselt_b,
439-
't_no_v': Nusselt_no_v_t,
440-
'b_no_v': Nusselt_no_v_b,
441471
}
442472

443473
def compute_viscous_dissipation(self, u):
@@ -509,8 +539,8 @@ def compute_max_step_size(P, u):
509539
grid_spacing_x = P.X[1, 0] - P.X[0, 0]
510540

511541
cell_wallz = P.xp.zeros(P.nz + 1)
512-
cell_wallz[0] = 1
513-
cell_wallz[-1] = -1
542+
cell_wallz[0] = P.Lz
543+
cell_wallz[-1] = 0
514544
cell_wallz[1:-1] = (P.Z[0, :-1] + P.Z[0, 1:]) / 2
515545
grid_spacing_z = cell_wallz[:-1] - cell_wallz[1:]
516546

0 commit comments

Comments
 (0)