Coverage for pySDC/projects/DAE/sweepers/rungeKuttaDAE.py: 100%

50 statements  

« prev     ^ index     » next       coverage.py v7.6.9, created at 2024-12-20 14:51 +0000

1from pySDC.projects.DAE.sweepers.fullyImplicitDAE import FullyImplicitDAE 

2from pySDC.implementations.sweeper_classes.Runge_Kutta import ( 

3 RungeKutta, 

4 BackwardEuler, 

5 CrankNicolson, 

6 EDIRK4, 

7 DIRK43_2, 

8) 

9 

10 

11class RungeKuttaDAE(RungeKutta): 

12 r""" 

13 Custom sweeper class to implement Runge-Kutta (RK) methods for general differential-algebraic equations (DAEs) 

14 of the form 

15 

16 .. math:: 

17 0 = F(u, u', t). 

18 

19 RK methods for general DAEs have the form 

20 

21 .. math:: 

22 0 = F(u_0 + \Delta t \sum_{j=1}^M a_{i,j} U_j, U_m), 

23 

24 .. math:: 

25 u_M = u_0 + \Delta t \sum_{j=1}^M b_j U_j. 

26 

27 In pySDC, RK methods are implemented in the way that the coefficient matrix :math:`A` in the Butcher 

28 tableau is a lower triangular matrix so that the stages are updated node-by-node. This class therefore only supports 

29 RK methods with lower triangular matrices in the tableau. 

30 

31 Parameters 

32 ---------- 

33 params : dict 

34 Parameters passed to the sweeper. 

35 

36 Attributes 

37 ---------- 

38 du_init : dtype_f 

39 Stores the initial condition for each step. 

40 

41 Note 

42 ---- 

43 When using a RK sweeper to simulate a problem make sure the DAE problem class has a ``du_exact`` method since RK methods need an initial 

44 condition for :math:`u'(t_0)` as well. 

45 

46 In order to implement a new RK method for DAEs a new tableau can be added in ``pySDC.implementations.sweeper_classes.Runge_Kutta.py``. 

47 For example, a new method called ``newRungeKuttaMethod`` with nodes :math:`c=(c_1, c_2, c_3)`, weights :math:`b=(b_1, b_2, b_3)` and 

48 coefficient matrix 

49 

50 ..math:: 

51 \begin{eqnarray} 

52 A = \begin{pmatrix} 

53 a_{11} & 0 & 0 \\ 

54 a_{21} & a_{22} & 0 \\ 

55 a_{31} & a_{32} & & 0 \\ 

56 \end{pmatrix} 

57 \end{eqnarray} 

58 

59 can be implemented as follows: 

60 

61 >>> class newRungeKuttaMethod(RungeKutta): 

62 >>> nodes = np.array([c1, c2, c3]) 

63 >>> weights = np.array([b1, b2, b3]) 

64 >>> matrix = np.zeros((3, 3)) 

65 >>> matrix[0, 0] = a11 

66 >>> matrix[1, :2] = [a21, a22] 

67 >>> matrix[2, :] = [a31, a32, a33] 

68 >>> ButcherTableauClass = ButcherTableau 

69 

70 The new class ``newRungeKuttaMethodDAE`` can then be used by defining the DAE class inheriting from both, this base class and class containing 

71 the Butcher tableau: 

72 

73 >>> class newRungeKuttaMethodDAE(RungeKuttaDAE, newRungeKuttaMethod): 

74 >>> pass 

75 

76 More details can be found [here](https://github.com/Parallel-in-Time/pySDC/blob/master/pySDC/implementations/sweeper_classes/Runge_Kutta.py). 

77 """ 

78 

79 def __init__(self, params): 

80 super().__init__(params) 

81 self.du_init = None 

82 self.fully_initialized = False 

83 

84 def predict(self): 

85 """ 

86 Predictor to fill values with zeros at nodes before first sweep. 

87 """ 

88 

89 # get current level and problem 

90 lvl = self.level 

91 prob = lvl.prob 

92 

93 if not self.fully_initialized: 

94 self.du_init = prob.du_exact(lvl.time) 

95 self.fully_initialized = True 

96 

97 lvl.f[0] = prob.dtype_f(self.du_init) 

98 for m in range(1, self.coll.num_nodes + 1): 

99 lvl.u[m] = prob.dtype_u(init=prob.init, val=0.0) 

100 lvl.f[m] = prob.dtype_f(init=prob.init, val=0.0) 

101 

102 lvl.status.unlocked = True 

103 lvl.status.updated = True 

104 

105 def integrate(self): 

106 r""" 

107 Returns the solution by integrating its gradient (fundamental theorem of calculus) at each collocation node. 

108 ``level.f`` stores the gradient of solution ``level.u``. 

109 

110 Returns 

111 ------- 

112 me : list of lists 

113 Integral of the gradient at each collocation node. 

114 """ 

115 

116 # get current level and problem 

117 lvl = self.level 

118 prob = lvl.prob 

119 

120 # integrate RHS over all collocation nodes 

121 me = [] 

122 for m in range(1, self.coll.num_nodes + 1): 

123 # new instance of dtype_u, initialize values with 0 

124 me.append(prob.dtype_u(prob.init, val=0.0)) 

125 for j in range(1, self.coll.num_nodes + 1): 

126 me[-1] += lvl.dt * self.coll.Qmat[m, j] * lvl.f[j] 

127 

128 return me 

129 

130 def update_nodes(self): 

131 r""" 

132 Updates the values of solution ``u`` and their gradient stored in ``f``. 

133 """ 

134 

135 # get current level and problem description 

136 lvl = self.level 

137 prob = lvl.prob 

138 

139 # only if the level has been touched before 

140 assert lvl.status.unlocked 

141 assert lvl.status.sweep <= 1, "RK schemes are direct solvers. Please perform only 1 iteration!" 

142 

143 M = self.coll.num_nodes 

144 for m in range(M): 

145 u_approx = prob.dtype_u(lvl.u[0]) 

146 for j in range(1, m + 1): 

147 u_approx += lvl.dt * self.QI[m + 1, j] * lvl.f[j][:] 

148 

149 finit = lvl.f[m].flatten() 

150 lvl.f[m + 1][:] = prob.solve_system( 

151 FullyImplicitDAE.F, 

152 u_approx, 

153 lvl.dt * self.QI[m + 1, m + 1], 

154 finit, 

155 lvl.time + lvl.dt * self.coll.nodes[m + 1], 

156 ) 

157 

158 # Update numerical solution 

159 integral = self.integrate() 

160 for m in range(M): 

161 lvl.u[m + 1][:] = lvl.u[0][:] + integral[m][:] 

162 

163 self.du_init = prob.dtype_f(lvl.f[-1]) 

164 

165 lvl.status.updated = True 

166 

167 return None 

168 

169 

170class BackwardEulerDAE(RungeKuttaDAE, BackwardEuler): 

171 pass 

172 

173 

174class TrapezoidalRuleDAE(RungeKuttaDAE, CrankNicolson): 

175 pass 

176 

177 

178class EDIRK4DAE(RungeKuttaDAE, EDIRK4): 

179 pass 

180 

181 

182class DIRK43_2DAE(RungeKuttaDAE, DIRK43_2): 

183 pass