|
9 | 9 | class ButcherTableau(object): |
10 | 10 | def __init__(self, weights, nodes, matrix): |
11 | 11 | """ |
12 | | - Initialization routine for an collocation object |
| 12 | + Initialization routine to get a quadrature matrix out of a Butcher tableau |
13 | 13 |
|
14 | 14 | Args: |
15 | 15 | weights (numpy.ndarray): Butcher tableau weights |
@@ -61,6 +61,64 @@ def __init__(self, weights, nodes, matrix): |
61 | 61 | self.implicit = any([matrix[i, i] != 0 for i in range(self.num_nodes - 1)]) |
62 | 62 |
|
63 | 63 |
|
| 64 | +class ButcherTableauEmbedded(object): |
| 65 | + def __init__(self, weights, nodes, matrix): |
| 66 | + """ |
| 67 | + Initialization routine to get a quadrature matrix out of a Butcher tableau for embedded RK methods. |
| 68 | +
|
| 69 | + Be aware that the method that generates the final solution should be in the first row of the weights matrix. |
| 70 | +
|
| 71 | + Args: |
| 72 | + weights (numpy.ndarray): Butcher tableau weights |
| 73 | + nodes (numpy.ndarray): Butcher tableau nodes |
| 74 | + matrix (numpy.ndarray): Butcher tableau entries |
| 75 | + """ |
| 76 | + # check if the arguments have the correct form |
| 77 | + if type(matrix) != np.ndarray: |
| 78 | + raise ParameterError('Runge-Kutta matrix needs to be supplied as a numpy array!') |
| 79 | + elif len(np.unique(matrix.shape)) != 1 or len(matrix.shape) != 2: |
| 80 | + raise ParameterError('Runge-Kutta matrix needs to be a square 2D numpy array!') |
| 81 | + |
| 82 | + if type(weights) != np.ndarray: |
| 83 | + raise ParameterError('Weights need to be supplied as a numpy array!') |
| 84 | + elif len(weights.shape) != 2: |
| 85 | + raise ParameterError(f'Incompatible dimension of weights! Need 2, got {len(weights.shape)}') |
| 86 | + elif len(weights[0]) != matrix.shape[0]: |
| 87 | + raise ParameterError(f'Incompatible number of weights! Need {matrix.shape[0]}, got {len(weights[0])}') |
| 88 | + |
| 89 | + if type(nodes) != np.ndarray: |
| 90 | + raise ParameterError('Nodes need to be supplied as a numpy array!') |
| 91 | + elif len(nodes.shape) != 1: |
| 92 | + raise ParameterError(f'Incompatible dimension of nodes! Need 1, got {len(nodes.shape)}') |
| 93 | + elif len(nodes) != matrix.shape[0]: |
| 94 | + raise ParameterError(f'Incompatible number of nodes! Need {matrix.shape[0]}, got {len(nodes)}') |
| 95 | + |
| 96 | + # Set number of nodes, left and right interval boundaries |
| 97 | + self.num_nodes = matrix.shape[0] + 2 |
| 98 | + self.tleft = 0. |
| 99 | + self.tright = 1. |
| 100 | + |
| 101 | + self.nodes = np.append(np.append([0], nodes), [1, 1]) |
| 102 | + self.weights = weights |
| 103 | + self.Qmat = np.zeros([self.num_nodes + 1, self.num_nodes + 1]) |
| 104 | + self.Qmat[1:-2, 1:-2] = matrix |
| 105 | + self.Qmat[-1, 1:-2] = weights[0] # this is for computing the higher order solution |
| 106 | + self.Qmat[-2, 1:-2] = weights[1] # this is for computing the lower order solution |
| 107 | + |
| 108 | + self.left_is_node = True |
| 109 | + self.right_is_node = self.nodes[-1] == self.tright |
| 110 | + |
| 111 | + # compute distances between the nodes |
| 112 | + if self.num_nodes > 1: |
| 113 | + self.delta_m = self.nodes[1:] - self.nodes[:-1] |
| 114 | + else: |
| 115 | + self.delta_m = np.zeros(1) |
| 116 | + self.delta_m[0] = self.nodes[0] - self.tleft |
| 117 | + |
| 118 | + # check if the RK scheme is implicit |
| 119 | + self.implicit = any([matrix[i, i] != 0 for i in range(self.num_nodes - 2)]) |
| 120 | + |
| 121 | + |
64 | 122 | class RungeKutta(generic_implicit): |
65 | 123 | """ |
66 | 124 | Runge-Kutta scheme that fits the interface of a sweeper. |
@@ -215,3 +273,34 @@ def __init__(self, params): |
215 | 273 | matrix[3, 2] = 1. |
216 | 274 | params['butcher_tableau'] = ButcherTableau(weights, nodes, matrix) |
217 | 275 | super(RK4, self).__init__(params) |
| 276 | + |
| 277 | + |
| 278 | +class Heun_Euler(RungeKutta): |
| 279 | + ''' |
| 280 | + Second order explicit embedded Runge-Kutta |
| 281 | + ''' |
| 282 | + def __init__(self, params): |
| 283 | + nodes = np.array([0, 1]) |
| 284 | + weights = np.array([[0.5, 0.5], [1, 0]]) |
| 285 | + matrix = np.zeros((2, 2)) |
| 286 | + matrix[1, 0] = 1 |
| 287 | + params['butcher_tableau'] = ButcherTableauEmbedded(weights, nodes, matrix) |
| 288 | + super(Heun_Euler, self).__init__(params) |
| 289 | + |
| 290 | + |
| 291 | +class Cash_Karp(RungeKutta): |
| 292 | + ''' |
| 293 | + Fifth order explicit embedded Runge-Kutta |
| 294 | + ''' |
| 295 | + def __init__(self, params): |
| 296 | + nodes = np.array([0, 0.2, 0.3, 0.6, 1., 7. / 8.]) |
| 297 | + weights = np.array([[37. / 378., 0., 250. / 621., 125. / 594., 0., 512. / 1771.], |
| 298 | + [2825. / 27648., 0., 18575. / 48384., 13525. / 55296., 277. / 14336., 1. / 4.]]) |
| 299 | + matrix = np.zeros((6, 6)) |
| 300 | + matrix[1, 0] = 1. / 5. |
| 301 | + matrix[2, :2] = [3. / 40., 9. / 40.] |
| 302 | + matrix[3, :3] = [0.3, -0.9, 1.2] |
| 303 | + matrix[4, :4] = [-11. / 54., 5. / 2., -70. / 27., 35. / 27.] |
| 304 | + matrix[5, :5] = [1631. / 55296., 175. / 512., 575. / 13824., 44275. / 110592., 253. / 4096.] |
| 305 | + params['butcher_tableau'] = ButcherTableauEmbedded(weights, nodes, matrix) |
| 306 | + super(Cash_Karp, self).__init__(params) |
0 commit comments