Skip to content

Commit e63c6fb

Browse files
authored
Merge pull request #163 from brownbaerchen/rk-sweeper
RK sweeper
2 parents 5eacd07 + 7851cc8 commit e63c6fb

File tree

7 files changed

+702
-22
lines changed

7 files changed

+702
-22
lines changed

pySDC/implementations/problem_classes/AdvectionEquation_1D_FD.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,12 +167,13 @@ def solve_system(self, rhs, factor, u0, t):
167167
me[:] = L.solve(rhs)
168168
return me
169169

170-
def u_exact(self, t):
170+
def u_exact(self, t, u_init=None, t_init=None):
171171
"""
172172
Routine to compute the exact solution at time t
173173
174174
Args:
175175
t (float): current time
176+
u_init, t_init: unused parameters for common interface reasons
176177
177178
Returns:
178179
dtype_u: exact solution
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
import numpy as np
2+
import logging
3+
4+
from pySDC.core.Sweeper import _Pars
5+
from pySDC.core.Errors import ParameterError
6+
from pySDC.implementations.sweeper_classes.generic_implicit import generic_implicit
7+
8+
9+
class ButcherTableau(object):
10+
def __init__(self, weights, nodes, matrix):
11+
"""
12+
Initialization routine for an collocation object
13+
14+
Args:
15+
weights (numpy.ndarray): Butcher tableau weights
16+
nodes (numpy.ndarray): Butcher tableau nodes
17+
matrix (numpy.ndarray): Butcher tableau entries
18+
"""
19+
# check if the arguments have the correct form
20+
if type(matrix) != np.ndarray:
21+
raise ParameterError('Runge-Kutta matrix needs to be supplied as a numpy array!')
22+
elif len(np.unique(matrix.shape)) != 1 or len(matrix.shape) != 2:
23+
raise ParameterError('Runge-Kutta matrix needs to be a square 2D numpy array!')
24+
25+
if type(weights) != np.ndarray:
26+
raise ParameterError('Weights need to be supplied as a numpy array!')
27+
elif len(weights.shape) != 1:
28+
raise ParameterError(f'Incompatible dimension of weights! Need 1, got {len(weights.shape)}')
29+
elif len(weights) != matrix.shape[0]:
30+
raise ParameterError(f'Incompatible number of weights! Need {matrix.shape[0]}, got {len(weights)}')
31+
32+
if type(nodes) != np.ndarray:
33+
raise ParameterError('Nodes need to be supplied as a numpy array!')
34+
elif len(nodes.shape) != 1:
35+
raise ParameterError(f'Incompatible dimension of nodes! Need 1, got {len(nodes.shape)}')
36+
elif len(nodes) != matrix.shape[0]:
37+
raise ParameterError(f'Incompatible number of nodes! Need {matrix.shape[0]}, got {len(nodes)}')
38+
39+
# Set number of nodes, left and right interval boundaries
40+
self.num_nodes = matrix.shape[0] + 1
41+
self.tleft = 0.
42+
self.tright = 1.
43+
44+
self.nodes = np.append(np.append([0], nodes), [1])
45+
self.weights = weights
46+
self.Qmat = np.zeros([self.num_nodes + 1, self.num_nodes + 1])
47+
self.Qmat[1:-1, 1:-1] = matrix
48+
self.Qmat[-1, 1:-1] = weights # this is for computing the solution to the step from the previous stages
49+
50+
self.left_is_node = True
51+
self.right_is_node = self.nodes[-1] == self.tright
52+
53+
# compute distances between the nodes
54+
if self.num_nodes > 1:
55+
self.delta_m = self.nodes[1:] - self.nodes[:-1]
56+
else:
57+
self.delta_m = np.zeros(1)
58+
self.delta_m[0] = self.nodes[0] - self.tleft
59+
60+
# check if the RK scheme is implicit
61+
self.implicit = any([matrix[i, i] != 0 for i in range(self.num_nodes - 1)])
62+
63+
64+
class RungeKutta(generic_implicit):
65+
"""
66+
Runge-Kutta scheme that fits the interface of a sweeper.
67+
Actually, the sweeper idea fits the Runge-Kutta idea when using only lower triangular rules, where solutions
68+
at the nodes are succesively computed from earlier nodes. However, we only perform a single iteration of this.
69+
70+
We have two choices to realise a Runge-Kutta sweeper: We can choose Q = Q_Delta = <Butcher tableau>, but in this
71+
implementation, that would lead to a lot of wasted FLOPS from integrating with Q and then with Q_Delta and
72+
subtracting the two. For that reason, we built this new sweeper, which does not have a preconditioner.
73+
74+
This class only supports lower triangular Butcher tableaus such that the system can be solved with forward
75+
subsitution. In this way, we don't get the maximum order that we could for the number of stages, but computing the
76+
stages is much cheaper. In particular, if the Butcher tableaus is strictly lower trianglar, we get an explicit
77+
method, which does not require us to solve a system of equations to compute the stages.
78+
79+
Attribues:
80+
butcher_tableau (ButcherTableau): Butcher tableau for the Runge-Kutta scheme that you want
81+
"""
82+
83+
def __init__(self, params):
84+
"""
85+
Initialization routine for the custom sweeper
86+
87+
Args:
88+
params: parameters for the sweeper
89+
"""
90+
# set up logger
91+
self.logger = logging.getLogger('sweeper')
92+
93+
essential_keys = ['butcher_tableau']
94+
for key in essential_keys:
95+
if key not in params:
96+
msg = 'need %s to instantiate step, only got %s' % (key, str(params.keys()))
97+
self.logger.error(msg)
98+
raise ParameterError(msg)
99+
100+
self.params = _Pars(params)
101+
102+
if 'collocation_class' in params or 'num_nodes' in params:
103+
self.logger.warning('You supplied parameters to setup a collocation problem to the Runge-Kutta sweeper. \
104+
Please be aware that they are ignored since the quadrature matrix is entirely determined by the Butcher tableau.')
105+
self.coll = params['butcher_tableau']
106+
107+
if not self.coll.right_is_node and not self.params.do_coll_update:
108+
self.logger.warning('we need to do a collocation update here, since the right end point is not a node. '
109+
'Changing this!')
110+
self.params.do_coll_update = True
111+
112+
# This will be set as soon as the sweeper is instantiated at the level
113+
self.__level = None
114+
115+
self.parallelizable = False
116+
self.QI = self.coll.Qmat
117+
118+
def update_nodes(self):
119+
"""
120+
Update the u- and f-values at the collocation nodes
121+
122+
Returns:
123+
None
124+
"""
125+
126+
# get current level and problem description
127+
L = self.level
128+
P = L.prob
129+
130+
# only if the level has been touched before
131+
assert L.status.unlocked
132+
assert L.status.sweep <= 1, "RK schemes are direct solvers. Please perform only 1 iteration!"
133+
134+
# get number of collocation nodes for easier access
135+
M = self.coll.num_nodes
136+
137+
for m in range(0, M):
138+
# build rhs, consisting of the known values from above and new values from previous nodes (at k+1)
139+
rhs = L.u[0]
140+
for j in range(1, m + 1):
141+
rhs += L.dt * self.QI[m + 1, j] * L.f[j]
142+
143+
# implicit solve with prefactor stemming from the diagonal of Qd
144+
if self.coll.implicit:
145+
L.u[m + 1] = P.solve_system(rhs, L.dt * self.QI[m + 1, m + 1], L.u[m + 1],
146+
L.time + L.dt * self.coll.nodes[m])
147+
else:
148+
L.u[m + 1] = rhs
149+
# update function values
150+
L.f[m + 1] = P.eval_f(L.u[m + 1], L.time + L.dt * self.coll.nodes[m])
151+
152+
# indicate presence of new values at this level
153+
L.status.updated = True
154+
155+
return None
156+
157+
158+
class RK1(RungeKutta):
159+
def __init__(self, params):
160+
implicit = params.get('implicit', False)
161+
nodes = np.array([0.])
162+
weights = np.array([1.])
163+
if implicit:
164+
matrix = np.array([[1.], ])
165+
else:
166+
matrix = np.array([[0.], ])
167+
params['butcher_tableau'] = ButcherTableau(weights, nodes, matrix)
168+
super(RK1, self).__init__(params)
169+
170+
171+
class CrankNicholson(RungeKutta):
172+
'''
173+
Implicit Runge-Kutta method of second order
174+
'''
175+
def __init__(self, params):
176+
nodes = np.array([0, 1])
177+
weights = np.array([0.5, 0.5])
178+
matrix = np.zeros((2, 2))
179+
matrix[1, 0] = 0.5
180+
matrix[1, 1] = 0.5
181+
params['butcher_tableau'] = ButcherTableau(weights, nodes, matrix)
182+
super(CrankNicholson, self).__init__(params)
183+
184+
185+
class MidpointMethod(RungeKutta):
186+
'''
187+
Runge-Kutta method of second order
188+
'''
189+
def __init__(self, params):
190+
implicit = params.get('implicit', False)
191+
if implicit:
192+
nodes = np.array([0.5])
193+
weights = np.array([1])
194+
matrix = np.zeros((1, 1))
195+
matrix[0, 0] = 1. / 2.
196+
else:
197+
nodes = np.array([0, 0.5])
198+
weights = np.array([0, 1])
199+
matrix = np.zeros((2, 2))
200+
matrix[1, 0] = 0.5
201+
params['butcher_tableau'] = ButcherTableau(weights, nodes, matrix)
202+
super(MidpointMethod, self).__init__(params)
203+
204+
205+
class RK4(RungeKutta):
206+
'''
207+
Explicit Runge-Kutta of fourth order: Everybodies darling.
208+
'''
209+
def __init__(self, params):
210+
nodes = np.array([0, 0.5, 0.5, 1])
211+
weights = np.array([1., 2., 2., 1.]) / 6.
212+
matrix = np.zeros((4, 4))
213+
matrix[1, 0] = 0.5
214+
matrix[2, 1] = 0.5
215+
matrix[3, 2] = 1.
216+
params['butcher_tableau'] = ButcherTableau(weights, nodes, matrix)
217+
super(RK4, self).__init__(params)

pySDC/playgrounds/Runge_Kutta/Runge-Kutta-Methods.ipynb

Lines changed: 144 additions & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)