Skip to content

Commit 7224da5

Browse files
committed
Merge branch 'master' into neuralpint
2 parents e930b97 + b5ed879 commit 7224da5

File tree

14 files changed

+175
-64
lines changed

14 files changed

+175
-64
lines changed

pySDC/core/level.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
from pySDC.core.sweeper import Sweeper
2+
from pySDC.core.problem import Problem
3+
14
from pySDC.helpers.pysdc_helper import FrozenClass
25

36

@@ -72,8 +75,8 @@ def __init__(self, problem_class, problem_params, sweeper_class, sweeper_params,
7275
self.status = _Status()
7376

7477
# instantiate sweeper, problem and hooks
75-
self.__sweep = sweeper_class(sweeper_params, self)
76-
self.__prob = problem_class(**problem_params)
78+
self.__sweep: Sweeper = sweeper_class(sweeper_params, self)
79+
self.__prob: Problem = problem_class(**problem_params)
7780

7881
# set name
7982
self.level_index = level_index
@@ -119,7 +122,7 @@ def reset_level(self, reset_status=True):
119122
self.tau = [None] * self.sweep.coll.num_nodes
120123

121124
@property
122-
def sweep(self):
125+
def sweep(self) -> Sweeper:
123126
"""
124127
Getter for the sweeper
125128
@@ -129,7 +132,7 @@ def sweep(self):
129132
return self.__sweep
130133

131134
@property
132-
def prob(self):
135+
def prob(self) -> Problem:
133136
"""
134137
Getter for the problem
135138

pySDC/core/step.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import logging
22

3-
from pySDC.core import level as levclass
3+
from pySDC.core.level import Level
44
from pySDC.core.base_transfer import BaseTransfer
55
from pySDC.core.errors import ParameterError
66
from pySDC.helpers.pysdc_helper import FrozenClass
@@ -73,7 +73,7 @@ def __init__(self, description):
7373
# empty attributes
7474
self.__transfer_dict = {}
7575
self.base_transfer = None
76-
self.levels = []
76+
self.levels: list[Level] = []
7777
self.__prev = None
7878
self.__next = None
7979

@@ -149,7 +149,7 @@ def __generate_hierarchy(self, descr):
149149

150150
# generate levels, register and connect if needed
151151
for l in range(len(descr_list)):
152-
L = levclass.Level(
152+
L = Level(
153153
problem_class=descr_list[l]['problem_class'],
154154
problem_params=descr_list[l]['problem_params'],
155155
sweeper_class=descr_list[l]['sweeper_class'],

pySDC/core/sweeper.py

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
import logging
22
import numpy as np
3-
from qmat import QDELTA_GENERATORS
3+
from qmat.qdelta import QDeltaGenerator, QDELTA_GENERATORS
44

55
from pySDC.core.errors import ParameterError
6-
from pySDC.core.level import Level
76
from pySDC.core.collocation import CollBase
87
from pySDC.helpers.pysdc_helper import FrozenClass
98

109

10+
# Organize QDeltaGenerator class in dict[type(QDeltaGenerator),set(str)] to retrieve aliases
11+
QDELTA_GENERATORS_ALIASES = {v: set() for v in set(QDELTA_GENERATORS.values())}
12+
for k, v in QDELTA_GENERATORS.items():
13+
QDELTA_GENERATORS_ALIASES[v].add(k)
14+
15+
1116
# short helper class to add params as attributes
1217
class _Pars(FrozenClass):
1318
def __init__(self, pars):
@@ -82,27 +87,17 @@ def __init__(self, params, level):
8287

8388
self.__level = level
8489
self.parallelizable = False
90+
for name in ["genQI", "genQE"]:
91+
if hasattr(self, name):
92+
delattr(self, name)
8593

86-
def getGenerator(self, qd_type):
87-
coll = self.coll
88-
89-
generator = QDELTA_GENERATORS[qd_type](
90-
# for algebraic types (LU, ...)
91-
Q=coll.generator.Q,
92-
# for MIN in tables, MIN-SR-S ...
93-
nNodes=coll.num_nodes,
94-
nodeType=coll.node_type,
95-
quadType=coll.quad_type,
96-
# for time-stepping types, MIN-SR-NS
97-
nodes=coll.nodes,
98-
tLeft=coll.tleft,
99-
)
100-
101-
return generator
94+
def buildGenerator(self, qdType: str) -> QDeltaGenerator:
95+
return QDELTA_GENERATORS[qdType](qGen=self.coll.generator, tLeft=self.coll.tleft)
10296

10397
def get_Qdelta_implicit(self, qd_type, k=None):
10498
QDmat = np.zeros_like(self.coll.Qmat)
105-
self.genQI = self.getGenerator(qd_type)
99+
if not hasattr(self, "genQI") or qd_type not in QDELTA_GENERATORS_ALIASES[type(self.genQI)]:
100+
self.genQI: QDeltaGenerator = self.buildGenerator(qd_type)
106101
QDmat[1:, 1:] = self.genQI.genCoeffs(k=k)
107102

108103
err_msg = 'Lower triangular matrix expected!'
@@ -114,7 +109,8 @@ def get_Qdelta_implicit(self, qd_type, k=None):
114109
def get_Qdelta_explicit(self, qd_type, k=None):
115110
coll = self.coll
116111
QDmat = np.zeros(coll.Qmat.shape, dtype=float)
117-
self.genQE = self.getGenerator(qd_type)
112+
if not hasattr(self, "genQE") or qd_type not in QDELTA_GENERATORS_ALIASES[type(self.genQE)]:
113+
self.genQE: QDeltaGenerator = self.buildGenerator(qd_type)
118114
QDmat[1:, 1:], QDmat[1:, 0] = self.genQE.genCoeffs(k=k, dTau=True)
119115

120116
err_msg = 'Strictly lower triangular matrix expected!'
@@ -251,6 +247,8 @@ def level(self, L):
251247
Args:
252248
L (pySDC.Level.level): current level
253249
"""
250+
from pySDC.core.level import Level
251+
254252
assert isinstance(L, Level)
255253
self.__level = L
256254

@@ -267,5 +265,9 @@ def updateVariableCoeffs(self, k):
267265
k : int
268266
Index of the sweep (0 for initial sweep, 1 for the first one, ...).
269267
"""
270-
if self.genQI._K_DEPENDENT:
271-
self.QI = self.get_Qdelta_implicit(qd_type=self.genQI.__class__.__name__, k=k)
268+
if hasattr(self, "genQI") and self.genQI.isKDependent():
269+
qdType = type(self.genQI).__name__
270+
self.QI = self.get_Qdelta_implicit(qdType, k=k)
271+
if hasattr(self, "genQE") and self.genQE.isKDependent():
272+
qdType = type(self.genQE).__name__
273+
self.QE = self.get_Qdelta_explicit(qdType, k=k)

pySDC/implementations/controller_classes/controller_MPI.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def __init__(self, controller_params, description, comm):
2828
super().__init__(controller_params, description, useMPI=True)
2929

3030
# create single step per processor
31-
self.S = Step(description)
31+
self.S: Step = Step(description)
3232

3333
# pass communicator for future use
3434
self.comm = comm
@@ -688,8 +688,11 @@ def it_fine(self, comm, num_procs):
688688

689689
for hook in self.hooks:
690690
hook.pre_sweep(step=self.S, level_number=0)
691+
692+
self.S.levels[0].sweep.updateVariableCoeffs(k + 1) # update QDelta coefficients if variable preconditioner
691693
self.S.levels[0].sweep.update_nodes()
692694
self.S.levels[0].sweep.compute_residual(stage='IT_FINE')
695+
693696
for hook in self.hooks:
694697
hook.post_sweep(step=self.S, level_number=0)
695698

@@ -718,6 +721,7 @@ def it_down(self, comm, num_procs):
718721

719722
for hook in self.hooks:
720723
hook.pre_sweep(step=self.S, level_number=l)
724+
721725
self.S.levels[l].sweep.update_nodes()
722726
self.S.levels[l].sweep.compute_residual(stage='IT_DOWN')
723727
for hook in self.hooks:

pySDC/implementations/controller_classes/controller_nonMPI.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import dill
55

66
from pySDC.core.controller import Controller
7-
from pySDC.core import step as stepclass
7+
from pySDC.core.step import Step
88
from pySDC.core.errors import ControllerError, CommunicationError
99
from pySDC.implementations.convergence_controller_classes.basic_restarting import BasicRestarting
1010

@@ -32,7 +32,7 @@ def __init__(self, num_procs, controller_params, description):
3232
# call parent's initialization routine
3333
super().__init__(controller_params, description, useMPI=False)
3434

35-
self.MS = [stepclass.Step(description)]
35+
self.MS: list[Step] = [Step(description)]
3636

3737
# try to initialize via dill.copy (much faster for many time-steps)
3838
try:
@@ -42,7 +42,7 @@ def __init__(self, num_procs, controller_params, description):
4242
except (dill.PicklingError, TypeError, ValueError) as error:
4343
self.logger.warning(f'Need to initialize steps separately due to pickling error: {error}')
4444
for _ in range(num_procs - 1):
45-
self.MS.append(stepclass.Step(description))
45+
self.MS.append(Step(description))
4646

4747
self.base_convergence_controllers += [BasicRestarting.get_implementation(useMPI=False)]
4848
for convergence_controller in self.base_convergence_controllers:
@@ -542,7 +542,7 @@ def it_check(self, local_MS_running):
542542
for C in [self.convergence_controllers[i] for i in self.convergence_controller_order]:
543543
C.reset_buffers_nonMPI(self)
544544

545-
def it_fine(self, local_MS_running):
545+
def it_fine(self, local_MS_running: list[Step]):
546546
"""
547547
Fine sweeps
548548
@@ -567,8 +567,11 @@ def it_fine(self, local_MS_running):
567567
# standard sweep workflow: update nodes, compute residual, log progress
568568
for hook in self.hooks:
569569
hook.pre_sweep(step=S, level_number=0)
570+
571+
S.levels[0].sweep.updateVariableCoeffs(k + 1) # update QDelta coefficients if variable preconditioner
570572
S.levels[0].sweep.update_nodes()
571573
S.levels[0].sweep.compute_residual(stage='IT_FINE')
574+
572575
for hook in self.hooks:
573576
hook.post_sweep(step=S, level_number=0)
574577

pySDC/implementations/convergence_controller_classes/adaptive_collocation.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
from qmat.lagrange import LagrangeApproximation
44
from pySDC.core.convergence_controller import ConvergenceController, Status
5-
from pySDC.core.collocation import CollBase
65

76

87
class AdaptiveCollocation(ConvergenceController):

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/implementations/sweeper_classes/generic_implicit.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,6 @@ def update_nodes(self):
6565
# get number of collocation nodes for easier access
6666
M = self.coll.num_nodes
6767

68-
# update the MIN-SR-FLEX preconditioner
69-
self.updateVariableCoeffs(L.status.sweep)
70-
7168
# gather all terms which are known already (e.g. from the previous iteration)
7269
# this corresponds to u0 + QF(u^k) - QdF(u^k) + tau
7370

pySDC/implementations/sweeper_classes/generic_implicit_MPI.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -209,9 +209,6 @@ def update_nodes(self):
209209
# only if the level has been touched before
210210
assert L.status.unlocked
211211

212-
# update the MIN-SR-FLEX preconditioner
213-
self.updateVariableCoeffs(L.status.sweep)
214-
215212
# gather all terms which are known already (e.g. from the previous iteration)
216213
# this corresponds to u0 + QF(u^k) - QdF(u^k) + tau
217214

pySDC/implementations/sweeper_classes/imex_1st_order.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,6 @@ def update_nodes(self):
7070

7171
self.updateVariableCoeffs(L.status.sweep)
7272

73-
# update the MIN-SR-FLEX preconditioner
74-
self.updateVariableCoeffs(L.status.sweep)
75-
7673
# gather all terms which are known already (e.g. from the previous iteration)
7774
# this corresponds to u0 + QF(u^k) - QIFI(u^k) - QEFE(u^k) + tau
7875

0 commit comments

Comments
 (0)