Skip to content

Commit 773bc26

Browse files
committed
Added serial implementation for ParaDiag for collocation methods
1 parent 10388b6 commit 773bc26

File tree

20 files changed

+2049
-2
lines changed

20 files changed

+2049
-2
lines changed

docs/source/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Tutorial
2525
tutorial/step_6.rst
2626
tutorial/step_7.rst
2727
tutorial/step_8.rst
28+
tutorial/step_9.rst
2829

2930
Playgrounds
3031
-----------
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Full code: `pySDC/tutorial/step_9/A_paradiag_for_linear_problems.py <https://github.com/Parallel-in-Time/pySDC/blob/master/pySDC/tutorial/step_9/A_paradiag_for_linear_problems.py>`_
2+
3+
.. literalinclude:: ../../../pySDC/tutorial/step_9/A_paradiag_for_linear_problems.py
4+
5+
Results:
6+
7+
.. literalinclude:: ../../../data/step_9_A_out.txt
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Full code: `pySDC/tutorial/step_9/B_paradiag_for_nonlinear_problems.py <https://github.com/Parallel-in-Time/pySDC/blob/master/pySDC/tutorial/step_9/B_paradiag_for_nonlinear_problems.py>`_
2+
3+
.. literalinclude:: ../../../pySDC/tutorial/step_9/B_paradiag_for_nonlinear_problems.py
4+
5+
Results:
6+
7+
.. literalinclude:: ../../../data/step_9_B_out.txt
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Full code: `pySDC/tutorial/step_9/C_paradiag_in_pySDC.py <https://github.com/Parallel-in-Time/pySDC/blob/master/pySDC/tutorial/step_9/C_paradiag_in_pySDC.py>`_
2+
3+
.. literalinclude:: ../../../pySDC/tutorial/step_9/C_paradiag_in_pySDC.py
4+
5+
Results:
6+
7+
.. literalinclude:: ../../../data/step_9_C_out.txt

docs/source/tutorial/step_9.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.. include:: /../../pySDC/tutorial/step_9/README.rst

pySDC/core/controller.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from pySDC.core.base_transfer import BaseTransfer
77
from pySDC.helpers.pysdc_helper import FrozenClass
88
from pySDC.implementations.convergence_controller_classes.check_convergence import CheckConvergence
9+
from pySDC.implementations.convergence_controller_classes.store_uold import StoreUOld
910
from pySDC.implementations.hooks.default_hook import DefaultHooks
1011
from pySDC.implementations.hooks.log_timings import CPUTimings
1112

@@ -41,6 +42,7 @@ def __init__(self, controller_params, description, useMPI=None):
4142
controller_params (dict): parameter set for the controller and the steps
4243
"""
4344
self.useMPI = useMPI
45+
self.description = description
4446

4547
# check if we have a hook on this list. If not, use default class.
4648
self.__hooks = []
@@ -341,3 +343,68 @@ def return_stats(self):
341343
for hook in self.hooks:
342344
stats = {**stats, **hook.return_stats()}
343345
return stats
346+
347+
348+
class ParaDiagController(Controller):
349+
350+
def __init__(self, controller_params, description, n_steps, useMPI=None):
351+
"""
352+
Initialization routine for ParaDiag controllers
353+
354+
Args:
355+
num_procs: number of parallel time steps (still serial, though), can be 1
356+
controller_params: parameter set for the controller and the steps
357+
description: all the parameters to set up the rest (levels, problems, transfer, ...)
358+
n_steps (int): Number of parallel steps
359+
alpha (float): alpha parameter for ParaDiag
360+
"""
361+
# TODO: where should I put alpha? When I want to adapt it, maybe it shouldn't be in the controller?
362+
from pySDC.implementations.sweeper_classes.ParaDiagSweepers import QDiagonalization
363+
364+
if QDiagonalization in description['sweeper_class'].__mro__:
365+
description['sweeper_params']['ignore_ic'] = True
366+
description['sweeper_params']['update_f_evals'] = False
367+
else:
368+
logging.getLogger('controller').warning(
369+
f'Warning: Your sweeper class {description["sweeper_class"]} is not derived from {QDiagonalization}. You probably want to use another sweeper class.'
370+
)
371+
372+
if controller_params.get('all_to_done', False):
373+
raise NotImplementedError('ParaDiag only implemented with option `all_to_done=True`')
374+
if 'alpha' not in controller_params.keys():
375+
from pySDC.core.errors import ParameterError
376+
377+
raise ParameterError('Please supply alpha as a parameter to the ParaDiag controller!')
378+
controller_params['average_jacobian'] = controller_params.get('average_jacobian', True)
379+
380+
controller_params['all_to_done'] = True
381+
super().__init__(controller_params=controller_params, description=description, useMPI=useMPI)
382+
self.base_convergence_controllers += [StoreUOld]
383+
384+
self.ParaDiag_block_u0 = None
385+
self.n_steps = n_steps
386+
387+
def FFT_in_time(self):
388+
"""
389+
Compute weighted forward FFT in time. The weighting is determined by the alpha parameter in ParaDiag
390+
391+
Note: The implementation via matrix-vector multiplication may be inefficient and less stable compared to an FFT
392+
with transposes!
393+
"""
394+
if not hasattr(self, '__FFT_matrix'):
395+
from pySDC.helpers.ParaDiagHelper import get_weighted_FFT_matrix
396+
397+
self.__FFT_matrix = get_weighted_FFT_matrix(self.n_steps, self.params.alpha)
398+
399+
self.apply_matrix(self.__FFT_matrix)
400+
401+
def iFFT_in_time(self):
402+
"""
403+
Compute weighted backward FFT in time. The weighting is determined by the alpha parameter in ParaDiag
404+
"""
405+
if not hasattr(self, '__iFFT_matrix'):
406+
from pySDC.helpers.ParaDiagHelper import get_weighted_iFFT_matrix
407+
408+
self.__iFFT_matrix = get_weighted_iFFT_matrix(self.n_steps, self.params.alpha)
409+
410+
self.apply_matrix(self.__iFFT_matrix)

pySDC/core/level.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ def __init__(self, problem_class, problem_params, sweeper_class, sweeper_params,
8282
self.uend = None
8383
self.u = [None] * (self.sweep.coll.num_nodes + 1)
8484
self.uold = [None] * (self.sweep.coll.num_nodes + 1)
85+
self.u_avg = [None] * self.sweep.coll.num_nodes
8586
self.f = [None] * (self.sweep.coll.num_nodes + 1)
8687
self.fold = [None] * (self.sweep.coll.num_nodes + 1)
8788

pySDC/helpers/ParaDiagHelper.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import numpy as np
2+
import scipy.sparse as sp
3+
4+
5+
def get_FFT_matrix(N):
6+
"""
7+
Get matrix for computing FFT of size N. Normalization is like "ortho" in numpy.
8+
Compute inverse FFT by multiplying by the complex conjugate (numpy.conjugate) of this matrix
9+
10+
Args:
11+
N (int): Size of the data to be transformed
12+
13+
Returns:
14+
numpy.ndarray: Dense square matrix to compute forward transform
15+
"""
16+
idx_1d = np.arange(N, dtype=complex)
17+
i1, i2 = np.meshgrid(idx_1d, idx_1d)
18+
19+
return np.exp(-2 * np.pi * 1j * i1 * i2 / N) / np.sqrt(N)
20+
21+
22+
def get_E_matrix(N, alpha=0):
23+
"""
24+
Get NxN matrix with -1 on the lower subdiagonal, -alpha in the top right and 0 elsewhere
25+
26+
Args:
27+
N (int): Size of the matrix
28+
alpha (float): Negative of value in the top right
29+
30+
Returns:
31+
sparse E matrix
32+
"""
33+
E = sp.diags(
34+
[
35+
-1.0,
36+
]
37+
* (N - 1),
38+
offsets=-1,
39+
).tolil()
40+
E[0, -1] = -alpha
41+
return E
42+
43+
44+
def get_J_matrix(N, alpha):
45+
"""
46+
Get matrix for weights in the weighted inverse FFT
47+
48+
Args:
49+
N (int): Size of the matrix
50+
alpha (float): alpha parameter in ParaDiag
51+
52+
Returns:
53+
sparse J matrix
54+
"""
55+
gamma = alpha ** (-np.arange(N) / N)
56+
return sp.diags(gamma)
57+
58+
59+
def get_J_inv_matrix(N, alpha):
60+
"""
61+
Get matrix for weights in the weighted FFT
62+
63+
Args:
64+
N (int): Size of the matrix
65+
alpha (float): alpha parameter in ParaDiag
66+
67+
Returns:
68+
sparse J_inv matrix
69+
"""
70+
gamma = alpha ** (-np.arange(N) / N)
71+
return sp.diags(1 / gamma)
72+
73+
74+
def get_weighted_FFT_matrix(N, alpha):
75+
"""
76+
Get matrix for the weighted FFT
77+
78+
Args:
79+
N (int): Size of the matrix
80+
alpha (float): alpha parameter in ParaDiag
81+
82+
Returns:
83+
Dense weighted FFT matrix
84+
"""
85+
return get_FFT_matrix(N) @ get_J_inv_matrix(N, alpha)
86+
87+
88+
def get_weighted_iFFT_matrix(N, alpha):
89+
"""
90+
Get matrix for the weighted inverse FFT
91+
92+
Args:
93+
N (int): Size of the matrix
94+
alpha (float): alpha parameter in ParaDiag
95+
96+
Returns:
97+
Dense weighted FFT matrix
98+
"""
99+
return get_J_matrix(N, alpha) @ np.conjugate(get_FFT_matrix(N))
100+
101+
102+
def get_H_matrix(N, sweeper_params):
103+
"""
104+
Get sparse matrix for computing the collocation update. Requires not to do a collocation update!
105+
106+
Args:
107+
N (int): Number of collocation nodes
108+
sweeper_params (dict): Parameters for the sweeper
109+
110+
Returns:
111+
Sparse matrix for collocation update
112+
"""
113+
assert sweeper_params['quad_type'] == 'RADAU-RIGHT'
114+
H = sp.eye(N).tolil() * 0
115+
H[:, -1] = 1
116+
return H
117+
118+
119+
def get_G_inv_matrix(l, L, alpha, sweeper_params):
120+
M = sweeper_params['num_nodes']
121+
I_M = sp.eye(M)
122+
E_alpha = get_E_matrix(L, alpha)
123+
H = get_H_matrix(M, sweeper_params)
124+
125+
gamma = alpha ** (-np.arange(L) / L)
126+
diags = np.fft.fft(1 / gamma * E_alpha[:, 0].toarray().flatten(), norm='backward')
127+
G = (diags[l] * H + I_M).tocsc()
128+
if M > 1:
129+
return sp.linalg.inv(G).toarray()
130+
else:
131+
return 1 / G.toarray()

0 commit comments

Comments
 (0)