Coverage for pySDC/implementations/sweeper_classes/Runge_Kutta.py: 92%
348 statements
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-20 14:51 +0000
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-20 14:51 +0000
1import numpy as np
2import logging
3from qmat.qcoeff.butcher import RK_SCHEMES
5from pySDC.core.sweeper import Sweeper, _Pars
6from pySDC.core.errors import ParameterError
7from pySDC.core.level import Level
10class ButcherTableau(object):
11 def __init__(self, weights, nodes, matrix):
12 """
13 Initialization routine to get a quadrature matrix out of a Butcher tableau
15 Args:
16 weights (numpy.ndarray): Butcher tableau weights
17 nodes (numpy.ndarray): Butcher tableau nodes
18 matrix (numpy.ndarray): Butcher tableau entries
19 """
20 self.check_method(weights, nodes, matrix)
22 self.tleft = 0.0
23 self.tright = 1.0
24 self.num_nodes = matrix.shape[0]
25 self.weights = weights
27 self.nodes = np.append([0], nodes)
28 self.Qmat = np.zeros([self.num_nodes + 1, self.num_nodes + 1])
29 self.Qmat[1:, 1:] = matrix
31 self.left_is_node = True
32 self.right_is_node = self.nodes[-1] == self.tright
34 # compute distances between the nodes
35 if self.num_nodes > 1:
36 self.delta_m = self.nodes[1:] - self.nodes[:-1]
37 else:
38 self.delta_m = np.zeros(1)
39 self.delta_m[0] = self.nodes[0] - self.tleft
41 # check if the RK scheme is implicit
42 self.implicit = any(matrix[i, i] != 0 for i in range(self.num_nodes))
44 def check_method(self, weights, nodes, matrix):
45 """
46 Check that the method is entered in the correct format
47 """
48 if type(matrix) != np.ndarray:
49 raise ParameterError('Runge-Kutta matrix needs to be supplied as a numpy array!')
50 elif len(np.unique(matrix.shape)) != 1 or len(matrix.shape) != 2:
51 raise ParameterError('Runge-Kutta matrix needs to be a square 2D numpy array!')
53 if type(nodes) != np.ndarray:
54 raise ParameterError('Nodes need to be supplied as a numpy array!')
55 elif len(nodes.shape) != 1:
56 raise ParameterError(f'Incompatible dimension of nodes! Need 1, got {len(nodes.shape)}')
57 elif len(nodes) != matrix.shape[0]:
58 raise ParameterError(f'Incompatible number of nodes! Need {matrix.shape[0]}, got {len(nodes)}')
60 self.check_weights(weights, nodes, matrix)
62 def check_weights(self, weights, nodes, matrix):
63 """
64 Check that the weights of the method are entered in the correct format
65 """
66 if type(weights) != np.ndarray:
67 raise ParameterError('Weights need to be supplied as a numpy array!')
68 elif len(weights.shape) != 1:
69 raise ParameterError(f'Incompatible dimension of weights! Need 1, got {len(weights.shape)}')
70 elif len(weights) != matrix.shape[0]:
71 raise ParameterError(f'Incompatible number of weights! Need {matrix.shape[0]}, got {len(weights)}')
73 @property
74 def globally_stiffly_accurate(self):
75 return np.allclose(self.Qmat[-1, 1:], self.weights)
78class ButcherTableauEmbedded(ButcherTableau):
80 def check_weights(self, weights, nodes, matrix):
81 """
82 Check that the weights of the method are entered in the correct format
83 """
84 if type(weights) != np.ndarray:
85 raise ParameterError('Weights need to be supplied as a numpy array!')
86 elif len(weights.shape) != 2:
87 raise ParameterError(f'Incompatible dimension of weights! Need 2, got {len(weights.shape)}')
88 elif len(weights[0]) != matrix.shape[0]:
89 raise ParameterError(f'Incompatible number of weights! Need {matrix.shape[0]}, got {len(weights[0])}')
91 @property
92 def globally_stiffly_accurate(self):
93 return np.allclose(self.Qmat[-1, 1:], self.weights[0])
96class RungeKutta(Sweeper):
97 nodes = None
98 weights = None
99 matrix = None
100 ButcherTableauClass = ButcherTableau
102 """
103 Runge-Kutta scheme that fits the interface of a sweeper.
104 Actually, the sweeper idea fits the Runge-Kutta idea when using only lower triangular rules, where solutions
105 at the nodes are successively computed from earlier nodes. However, we only perform a single iteration of this.
107 We have two choices to realise a Runge-Kutta sweeper: We can choose Q = Q_Delta = <Butcher tableau>, but in this
108 implementation, that would lead to a lot of wasted FLOPS from integrating with Q and then with Q_Delta and
109 subtracting the two. For that reason, we built this new sweeper, which does not have a preconditioner.
111 This class only supports lower triangular Butcher tableaux such that the system can be solved with forward
112 substitution. In this way, we don't get the maximum order that we could for the number of stages, but computing the
113 stages is much cheaper. In particular, if the Butcher tableaux is strictly lower triangular, we get an explicit
114 method, which does not require us to solve a system of equations to compute the stages.
116 Please be aware that all fundamental parameters of the Sweeper are ignored. These include
118 - num_nodes
119 - collocation_class
120 - initial_guess
121 - QI
123 All of these variables are either determined by the RK rule, or are not part of an RK scheme.
125 The entries of the Butcher tableau are stored as class attributes.
126 """
128 def __init__(self, params):
129 """
130 Initialization routine for the custom sweeper
132 Args:
133 params: parameters for the sweeper
134 """
135 # set up logger
136 self.logger = logging.getLogger('sweeper')
138 # check if some parameters are set which only apply to actual sweepers
139 for key in ['initial_guess', 'collocation_class', 'num_nodes']:
140 if key in params:
141 self.logger.warning(f'"{key}" will be ignored by Runge-Kutta sweeper')
143 # set parameters to their actual values
144 self.coll = self.get_Butcher_tableau()
145 params['initial_guess'] = 'zero'
146 params['collocation_class'] = type(self.ButcherTableauClass)
147 params['num_nodes'] = self.coll.num_nodes
149 # disable residual computation by default
150 params['skip_residual_computation'] = params.get(
151 'skip_residual_computation', ('IT_CHECK', 'IT_FINE', 'IT_COARSE', 'IT_UP', 'IT_DOWN')
152 )
154 # check if we can skip some usually unnecessary right hand side evaluations
155 params['eval_rhs_at_right_boundary'] = params.get('eval_rhs_at_right_boundary', False)
157 self.params = _Pars(params)
159 # This will be set as soon as the sweeper is instantiated at the level
160 self.__level = None
162 self.parallelizable = False
163 self.QI = self.coll.Qmat
165 @classmethod
166 def get_Q_matrix(cls):
167 return cls.get_Butcher_tableau().Qmat
169 @classmethod
170 def get_Butcher_tableau(cls):
171 return cls.ButcherTableauClass(cls.weights, cls.nodes, cls.matrix)
173 @classmethod
174 def get_update_order(cls):
175 """
176 Get the order of the lower order method for doing adaptivity. Only applies to embedded methods.
177 """
178 raise NotImplementedError(
179 f"There is not an update order for RK scheme \"{cls.__name__}\" implemented. Maybe it is not an embedded scheme?"
180 )
182 def get_full_f(self, f):
183 """
184 Get the full right hand side as a `mesh` from the right hand side
186 Args:
187 f (dtype_f): Right hand side at a single node
189 Returns:
190 mesh: Full right hand side as a mesh
191 """
192 if type(f).__name__ in ['mesh', 'cupy_mesh']:
193 return f
194 elif type(f).__name__ in ['imex_mesh', 'imex_cupy_mesh']:
195 return f.impl + f.expl
196 elif f is None:
197 prob = self.level.prob
198 return self.get_full_f(prob.dtype_f(prob.init, val=0))
199 else:
200 raise NotImplementedError(f'Type \"{type(f)}\" not implemented in Runge-Kutta sweeper')
202 def integrate(self):
203 """
204 Integrates the right-hand side
206 Returns:
207 list of dtype_u: containing the integral as values
208 """
210 # get current level and problem
211 lvl = self.level
212 prob = lvl.prob
214 me = []
216 # integrate RHS over all collocation nodes
217 for m in range(1, self.coll.num_nodes + 1):
218 # new instance of dtype_u, initialize values with 0
219 me.append(prob.dtype_u(prob.init, val=0.0))
220 for j in range(1, self.coll.num_nodes + 1):
221 me[-1] += lvl.dt * self.coll.Qmat[m, j] * self.get_full_f(lvl.f[j])
223 return me
225 def update_nodes(self):
226 """
227 Update the u- and f-values at the collocation nodes
229 Returns:
230 None
231 """
233 # get current level and problem
234 lvl = self.level
235 prob = lvl.prob
237 # only if the level has been touched before
238 assert lvl.status.unlocked
239 assert lvl.status.sweep <= 1, "RK schemes are direct solvers. Please perform only 1 iteration!"
241 # get number of collocation nodes for easier access
242 M = self.coll.num_nodes
244 for m in range(0, M):
245 # build rhs, consisting of the known values from above and new values from previous nodes (at k+1)
246 rhs = prob.dtype_u(lvl.u[0])
247 for j in range(1, m + 1):
248 rhs += lvl.dt * self.QI[m + 1, j] * self.get_full_f(lvl.f[j])
250 # implicit solve with prefactor stemming from the diagonal of Qd, use previous stage as initial guess
251 if self.QI[m + 1, m + 1] != 0:
252 lvl.u[m + 1][:] = prob.solve_system(
253 rhs, lvl.dt * self.QI[m + 1, m + 1], lvl.u[m], lvl.time + lvl.dt * self.coll.nodes[m + 1]
254 )
255 else:
256 lvl.u[m + 1][:] = rhs[:]
258 # update function values (we don't usually need to evaluate the RHS at the solution of the step)
259 lvl.f[m + 1] = prob.eval_f(lvl.u[m + 1], lvl.time + lvl.dt * self.coll.nodes[m + 1])
261 # indicate presence of new values at this level
262 lvl.status.updated = True
264 return None
266 def compute_end_point(self):
267 """
268 In this Runge-Kutta implementation, the solution to the step is always stored in the last node
269 """
270 lvl = self.level
272 if lvl.f[1] is None:
273 lvl.uend = lvl.prob.dtype_u(lvl.u[0])
274 if type(self.coll) == ButcherTableauEmbedded:
275 self.u_secondary = lvl.prob.dtype_u(lvl.u[0])
276 elif self.coll.globally_stiffly_accurate:
277 lvl.uend = lvl.prob.dtype_u(lvl.u[-1])
278 if type(self.coll) == ButcherTableauEmbedded:
279 self.u_secondary = lvl.prob.dtype_u(lvl.u[0])
280 for w2, k in zip(self.coll.weights[1], lvl.f[1:]):
281 self.u_secondary += lvl.dt * w2 * k
282 else:
283 lvl.uend = lvl.prob.dtype_u(lvl.u[0])
284 if type(self.coll) == ButcherTableau:
285 for w, k in zip(self.coll.weights, lvl.f[1:]):
286 lvl.uend += lvl.dt * w * k
287 elif type(self.coll) == ButcherTableauEmbedded:
288 self.u_secondary = lvl.prob.dtype_u(lvl.u[0])
289 for w1, w2, k in zip(self.coll.weights[0], self.coll.weights[1], lvl.f[1:]):
290 lvl.uend += lvl.dt * w1 * k
291 self.u_secondary += lvl.dt * w2 * k
293 @property
294 def level(self):
295 """
296 Returns the current level
298 Returns:
299 pySDC.Level.level: Current level
300 """
301 return self.__level
303 @level.setter
304 def level(self, lvl):
305 """
306 Sets a reference to the current level (done in the initialization of the level)
308 Args:
309 lvl (pySDC.Level.level): Current level
310 """
311 assert isinstance(lvl, Level), f"You tried to set the sweeper's level with an instance of {type(lvl)}!"
312 if lvl.params.restol > 0:
313 lvl.params.restol = -1
314 self.logger.warning(
315 'Overwriting residual tolerance with -1 because RK methods are direct and hence may not compute a residual at all!'
316 )
318 self.__level = lvl
320 def predict(self):
321 """
322 Predictor to fill values at nodes before first sweep
323 """
325 # get current level and problem
326 lvl = self.level
327 prob = lvl.prob
329 for m in range(1, self.coll.num_nodes + 1):
330 lvl.u[m] = prob.dtype_u(init=prob.init, val=0.0)
332 # indicate that this level is now ready for sweeps
333 lvl.status.unlocked = True
334 lvl.status.updated = True
337class RungeKuttaIMEX(RungeKutta):
338 """
339 Implicit-explicit split Runge Kutta base class. Only supports methods that share the nodes and weights.
340 """
342 matrix_explicit = None
343 weights_explicit = None
344 ButcherTableauClass_explicit = ButcherTableau
346 def __init__(self, params):
347 """
348 Initialization routine
350 Args:
351 params: parameters for the sweeper
352 """
353 super().__init__(params)
354 type(self).weights_explicit = self.weights if self.weights_explicit is None else self.weights_explicit
355 self.coll_explicit = self.get_Butcher_tableau_explicit()
356 self.QE = self.coll_explicit.Qmat
358 def predict(self):
359 """
360 Predictor to fill values at nodes before first sweep
361 """
363 # get current level and problem
364 lvl = self.level
365 prob = lvl.prob
367 for m in range(1, self.coll.num_nodes + 1):
368 lvl.u[m] = prob.dtype_u(init=prob.init, val=0.0)
369 lvl.f[m] = prob.dtype_f(init=prob.init, val=0.0)
371 # indicate that this level is now ready for sweeps
372 lvl.status.unlocked = True
373 lvl.status.updated = True
375 @classmethod
376 def get_Butcher_tableau_explicit(cls):
377 return cls.ButcherTableauClass_explicit(cls.weights_explicit, cls.nodes, cls.matrix_explicit)
379 def integrate(self):
380 """
381 Integrates the right-hand side
383 Returns:
384 list of dtype_u: containing the integral as values
385 """
387 # get current level and problem
388 lvl = self.level
389 prob = lvl.prob
391 me = []
393 # integrate RHS over all collocation nodes
394 for m in range(1, self.coll.num_nodes + 1):
395 # new instance of dtype_u, initialize values with 0
396 me.append(prob.dtype_u(prob.init, val=0.0))
397 for j in range(1, self.coll.num_nodes + 1):
398 me[-1] += lvl.dt * (
399 self.coll.Qmat[m, j] * lvl.f[j].impl + self.coll_explicit.Qmat[m, j] * lvl.f[j].expl
400 )
402 return me
404 def update_nodes(self):
405 """
406 Update the u- and f-values at the collocation nodes
408 Returns:
409 None
410 """
412 # get current level and problem
413 lvl = self.level
414 prob = lvl.prob
416 # only if the level has been touched before
417 assert lvl.status.unlocked
418 assert lvl.status.sweep <= 1, "RK schemes are direct solvers. Please perform only 1 iteration!"
420 # get number of collocation nodes for easier access
421 M = self.coll.num_nodes
423 for m in range(0, M):
424 # build rhs, consisting of the known values from above and new values from previous nodes (at k+1)
425 rhs = lvl.u[0]
426 for j in range(1, m + 1):
427 rhs += lvl.dt * (self.QI[m + 1, j] * lvl.f[j].impl + self.QE[m + 1, j] * lvl.f[j].expl)
429 # implicit solve with prefactor stemming from the diagonal of Qd, use previous stage as initial guess
430 if self.QI[m + 1, m + 1] != 0:
431 lvl.u[m + 1][:] = prob.solve_system(
432 rhs, lvl.dt * self.QI[m + 1, m + 1], lvl.u[m], lvl.time + lvl.dt * self.coll.nodes[m + 1]
433 )
434 else:
435 lvl.u[m + 1][:] = rhs[:]
437 # update function values
438 lvl.f[m + 1] = prob.eval_f(lvl.u[m + 1], lvl.time + lvl.dt * self.coll.nodes[m + 1])
440 # indicate presence of new values at this level
441 lvl.status.updated = True
443 return None
445 def compute_end_point(self):
446 """
447 In this Runge-Kutta implementation, the solution to the step is always stored in the last node
448 """
449 lvl = self.level
451 if lvl.f[1] is None:
452 lvl.uend = lvl.prob.dtype_u(lvl.u[0])
453 if type(self.coll) == ButcherTableauEmbedded:
454 self.u_secondary = lvl.prob.dtype_u(lvl.u[0])
455 elif self.coll.globally_stiffly_accurate and self.coll_explicit.globally_stiffly_accurate:
456 lvl.uend = lvl.u[-1]
457 if type(self.coll) == ButcherTableauEmbedded:
458 self.u_secondary = lvl.prob.dtype_u(lvl.u[0])
459 for w2, w2E, k in zip(self.coll.weights[1], self.coll_explicit.weights[1], lvl.f[1:]):
460 self.u_secondary += lvl.dt * (w2 * k.impl + w2E * k.expl)
461 else:
462 lvl.uend = lvl.prob.dtype_u(lvl.u[0])
463 if type(self.coll) == ButcherTableau:
464 for w, wE, k in zip(self.coll.weights, self.coll_explicit.weights, lvl.f[1:]):
465 lvl.uend += lvl.dt * (w * k.impl + wE * k.expl)
466 elif type(self.coll) == ButcherTableauEmbedded:
467 self.u_secondary = lvl.u[0].copy()
468 for w1, w2, w1E, w2E, k in zip(
469 self.coll.weights[0],
470 self.coll.weights[1],
471 self.coll_explicit.weights[0],
472 self.coll_explicit.weights[1],
473 lvl.f[1:],
474 ):
475 lvl.uend += lvl.dt * (w1 * k.impl + w1E * k.expl)
476 self.u_secondary += lvl.dt * (w2 * k.impl + w2E * k.expl)
479class ForwardEuler(RungeKutta):
480 """
481 Forward Euler. Still a classic.
483 Not very stable first order method.
484 """
486 generator = RK_SCHEMES["FE"]()
487 nodes, weights, matrix = generator.genCoeffs()
490class BackwardEuler(RungeKutta):
491 """
492 Backward Euler. A favorite among true connoisseurs of the heat equation.
494 A-stable first order method.
495 """
497 generator = RK_SCHEMES["BE"]()
498 nodes, weights, matrix = generator.genCoeffs()
501class IMEXEuler(RungeKuttaIMEX):
502 nodes = BackwardEuler.nodes
503 weights = BackwardEuler.weights
505 matrix = BackwardEuler.matrix
506 matrix_explicit = ForwardEuler.matrix
509class CrankNicolson(RungeKutta):
510 """
511 Implicit Runge-Kutta method of second order, A-stable.
512 """
514 generator = RK_SCHEMES["CN"]()
515 nodes, weights, matrix = generator.genCoeffs()
518class ExplicitMidpointMethod(RungeKutta):
519 """
520 Explicit Runge-Kutta method of second order.
521 """
523 generator = RK_SCHEMES["RK2"]()
524 nodes, weights, matrix = generator.genCoeffs()
527class ImplicitMidpointMethod(RungeKutta):
528 """
529 Implicit Runge-Kutta method of second order.
530 """
532 generator = RK_SCHEMES["IMP"]()
533 nodes, weights, matrix = generator.genCoeffs()
536class RK4(RungeKutta):
537 """
538 Explicit Runge-Kutta of fourth order: Everybody's darling.
539 """
541 generator = RK_SCHEMES["RK4"]()
542 nodes, weights, matrix = generator.genCoeffs()
545class Heun_Euler(RungeKutta):
546 """
547 Second order explicit embedded Runge-Kutta method.
548 """
550 ButcherTableauClass = ButcherTableauEmbedded
552 generator = RK_SCHEMES["HEUN"]()
553 nodes, _weights, matrix = generator.genCoeffs()
554 weights = np.zeros((2, len(_weights)))
555 weights[0] = _weights
556 weights[1] = matrix[-1]
558 @classmethod
559 def get_update_order(cls):
560 return 2
563class Cash_Karp(RungeKutta):
564 """
565 Fifth order explicit embedded Runge-Kutta. See [here](https://doi.org/10.1145/79505.79507).
566 """
568 generator = RK_SCHEMES["CashKarp"]()
569 nodes, weights, matrix = generator.genCoeffs(embedded=True)
570 ButcherTableauClass = ButcherTableauEmbedded
572 @classmethod
573 def get_update_order(cls):
574 return 5
577class DIRK43(RungeKutta):
578 """
579 Embedded A-stable diagonally implicit RK pair of order 3 and 4.
581 Taken from [here](https://doi.org/10.1007/BF01934920).
582 """
584 generator = RK_SCHEMES["EDIRK43"]()
585 nodes, weights, matrix = generator.genCoeffs(embedded=True)
586 ButcherTableauClass = ButcherTableauEmbedded
588 @classmethod
589 def get_update_order(cls):
590 return 4
593class DIRK43_2(RungeKutta):
594 """
595 L-stable Diagonally Implicit RK method with four stages of order 3.
596 Taken from [here](https://en.wikipedia.org/wiki/List_of_Runge%E2%80%93Kutta_methods).
597 """
599 generator = RK_SCHEMES["DIRK43"]()
600 nodes, weights, matrix = generator.genCoeffs()
603class EDIRK4(RungeKutta):
604 """
605 Stiffly accurate, fourth-order EDIRK with four stages. Taken from
606 [here](https://ntrs.nasa.gov/citations/20160005923), second one in eq. (216).
607 """
609 generator = RK_SCHEMES["EDIRK4"]()
610 nodes, weights, matrix = generator.genCoeffs()
613class ESDIRK53(RungeKutta):
614 """
615 A-stable embedded RK pair of orders 5 and 3, ESDIRK5(3)6L[2]SA.
616 Taken from [here](https://ntrs.nasa.gov/citations/20160005923)
617 """
619 generator = RK_SCHEMES["ESDIRK53"]()
620 nodes, weights, matrix = generator.genCoeffs(embedded=True)
621 ButcherTableauClass = ButcherTableauEmbedded
623 @classmethod
624 def get_update_order(cls):
625 return 4
628class ESDIRK43(RungeKutta):
629 """
630 A-stable embedded RK pair of orders 4 and 3, ESDIRK4(3)6L[2]SA.
631 Taken from [here](https://ntrs.nasa.gov/citations/20160005923)
632 """
634 generator = RK_SCHEMES["ESDIRK43"]()
635 nodes, weights, matrix = generator.genCoeffs(embedded=True)
636 ButcherTableauClass = ButcherTableauEmbedded
638 @classmethod
639 def get_update_order(cls):
640 return 4
643class ARK548L2SAERK(RungeKutta):
644 """
645 Explicit part of the ARK54 scheme.
646 """
648 generator = RK_SCHEMES["ARK548L2SAERK"]()
649 nodes, weights, matrix = generator.genCoeffs(embedded=True)
650 ButcherTableauClass = ButcherTableauEmbedded
652 @classmethod
653 def get_update_order(cls):
654 return 5
657class ARK548L2SAESDIRK(ARK548L2SAERK):
658 """
659 Implicit part of the ARK54 scheme. Be careful with the embedded scheme. It seems that both schemes are order 5 as opposed to 5 and 4 as claimed. This may cause issues when doing adaptive time-stepping.
660 """
662 generator_IMP = RK_SCHEMES["ARK548L2SAESDIRK"]()
663 matrix = generator_IMP.Q
666class ARK54(RungeKuttaIMEX):
667 """
668 Pair of pairs of ARK5(4)8L[2]SA-ERK and ARK5(4)8L[2]SA-ESDIRK from [here](https://doi.org/10.1016/S0168-9274(02)00138-1).
669 """
671 ButcherTableauClass = ButcherTableauEmbedded
672 ButcherTableauClass_explicit = ButcherTableauEmbedded
674 nodes = ARK548L2SAERK.nodes
675 weights = ARK548L2SAERK.weights
677 matrix = ARK548L2SAESDIRK.matrix
678 matrix_explicit = ARK548L2SAERK.matrix
680 @classmethod
681 def get_update_order(cls):
682 return 5
685class ARK548L2SAESDIRK2(RungeKutta):
686 """
687 Stiffly accurate singly diagonally L-stable implicit embedded Runge-Kutta pair of orders 5 and 4 with explicit first stage from [here](https://doi.org/10.1016/j.apnum.2018.10.007).
688 This method is part of the IMEX method ARK548L2SA.
689 """
691 generator = RK_SCHEMES["ARK548L2SAESDIRK2"]()
692 nodes, weights, matrix = generator.genCoeffs(embedded=True)
693 ButcherTableauClass = ButcherTableauEmbedded
695 @classmethod
696 def get_update_order(cls):
697 return 5
700class ARK548L2SAERK2(ARK548L2SAESDIRK2):
701 """
702 Explicit embedded pair of Runge-Kutta methods of orders 5 and 4 from [here](https://doi.org/10.1016/j.apnum.2018.10.007).
703 This method is part of the IMEX method ARK548L2SA.
704 """
706 generator_EXP = RK_SCHEMES["ARK548L2SAERK2"]()
707 matrix = generator_EXP.Q
710class ARK548L2SA(RungeKuttaIMEX):
711 """
712 IMEX Runge-Kutta method of order 5 based on the explicit method ARK548L2SAERK2 and the implicit method
713 ARK548L2SAESDIRK2 from [here](https://doi.org/10.1016/j.apnum.2018.10.007).
715 According to Kennedy and Carpenter (see reference), the two IMEX RK methods of order 5 are the only ones available
716 as of now. And we are not aware of higher order ones. This one is newer then the other one and apparently better.
717 """
719 ButcherTableauClass = ButcherTableauEmbedded
720 ButcherTableauClass_explicit = ButcherTableauEmbedded
722 nodes = ARK548L2SAERK2.nodes
723 weights = ARK548L2SAERK2.weights
725 matrix = ARK548L2SAESDIRK2.matrix
726 matrix_explicit = ARK548L2SAERK2.matrix
728 @classmethod
729 def get_update_order(cls):
730 return 5
733class ARK324L2SAERK(RungeKutta):
734 generator = RK_SCHEMES["ARK324L2SAERK"]()
735 nodes, weights, matrix = generator.genCoeffs(embedded=True)
736 ButcherTableauClass = ButcherTableauEmbedded
738 @classmethod
739 def get_update_order(cls):
740 return 3
743class ARK324L2SAESDIRK(ARK324L2SAERK):
744 generator = RK_SCHEMES["ARK324L2SAESDIRK"]()
745 matrix = generator.Q
748class ARK32(RungeKuttaIMEX):
749 ButcherTableauClass = ButcherTableauEmbedded
750 ButcherTableauClass_explicit = ButcherTableauEmbedded
752 nodes = ARK324L2SAESDIRK.nodes
753 weights = ARK324L2SAESDIRK.weights
755 matrix = ARK324L2SAESDIRK.matrix
756 matrix_explicit = ARK324L2SAERK.matrix
758 @classmethod
759 def get_update_order(cls):
760 return 3
763class ARK2(RungeKuttaIMEX):
764 """
765 Second order two stage singly diagonally implicit globally stiffly accurate IMEX RK method with explicit first stage.
766 Can be used to integrate simple DAEs because explicit and implicit part are both stiffly accurate.
767 """
769 generator_IMP = RK_SCHEMES["ARK222EDIRK"]()
770 generator_EXP = RK_SCHEMES["ARK222ERK"]()
772 nodes, weights, matrix = generator_IMP.genCoeffs()
773 _, weights_explicit, matrix_explicit = generator_EXP.genCoeffs()
776class ARK3(RungeKuttaIMEX):
777 """
778 Third order four stage singly diagonally implicit globally stiffly accurate IMEX RK method with explicit first stage.
779 Can be used to integrate simple DAEs because explicit and implicit part are both stiffly accurate.
780 """
782 generator_IMP = RK_SCHEMES["ARK443ESDIRK"]()
783 generator_EXP = RK_SCHEMES["ARK443ERK"]()
785 nodes, weights, matrix = generator_IMP.genCoeffs()
786 _, weights_explicit, matrix_explicit = generator_EXP.genCoeffs()