Coverage for pySDC/implementations/problem_classes/odeScalar.py: 100%

53 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-11-02 12:30 +0000

1#!/usr/bin/env python3 

2# -*- coding: utf-8 -*- 

3""" 

4Implementation of scalar test problem ODEs. 

5 

6 

7Reference : 

8 

9Van der Houwen, P. J., & Sommeijer, B. P. (1991). Iterated Runge–Kutta methods 

10on parallel computers. SIAM journal on scientific and statistical computing, 

1112(5), 1000-1028. 

12""" 

13 

14import numpy as np 

15 

16from pySDC.core.errors import ProblemError 

17from pySDC.core.problem import Problem, WorkCounter 

18from pySDC.implementations.datatype_classes.mesh import mesh 

19 

20 

21class ProtheroRobinson(Problem): 

22 r""" 

23 Implement the Prothero-Robinson problem: 

24 

25 .. math:: 

26 \frac{du}{dt} = -\frac{u-g(t)}{\epsilon} + \frac{dg}{dt}, \quad u(0) = g(0)., 

27 

28 with :math:`\epsilon` a stiffness parameter, that makes the problem more stiff 

29 the smaller it is (usual taken value is :math:`\epsilon=1e^{-3}`). 

30 Exact solution is given by :math:`u(t)=g(t)`, and this implementation uses 

31 :math:`g(t)=\cos(t)`. 

32 

33 Implement also the non-linear form of this problem: 

34 

35 .. math:: 

36 \frac{du}{dt} = -\frac{u^3-g(t)^3}{\epsilon} + \frac{dg}{dt}, \quad u(0) = g(0). 

37 

38 To use an other exact solution, one just have to derivate this class 

39 and overload the `g` and `dg` methods. For instance, 

40 to use :math:`g(t)=e^{-0.2*t}`, define and use the following class: 

41 

42 >>> class MyProtheroRobinson(ProtheroRobinson): 

43 >>> 

44 >>> def g(self, t): 

45 >>> return np.exp(-0.2 * t) 

46 >>> 

47 >>> def dg(self, t): 

48 >>> return (-0.2) * np.exp(-0.2 * t) 

49 

50 Parameters 

51 ---------- 

52 epsilon : float, optional 

53 Stiffness parameter. The default is 1e-3. 

54 nonLinear : bool, optional 

55 Wether or not to use the non-linear form of the problem. The default is False. 

56 newton_maxiter : int, optional 

57 Maximum number of Newton iteration in solve_system. The default is 200. 

58 newton_tol : float, optional 

59 Residuum tolerance for Newton iteration in solve_system. The default is 5e-11. 

60 stop_at_nan : bool, optional 

61 Wheter to stop or not solve_system when getting NAN. The default is True. 

62 

63 References 

64 ---------- 

65 A. Prothero and A. Robinson, On the stability and accuracy of one-step methods for solving 

66 stiff systems of ordinary differential equations, Mathematics of Computation, 28 (1974), 

67 pp. 145–162. 

68 """ 

69 

70 dtype_u = mesh 

71 dtype_f = mesh 

72 

73 def __init__(self, epsilon=1e-3, nonLinear=False, newton_maxiter=200, newton_tol=5e-11, stop_at_nan=True): 

74 """Initialization routine""" 

75 nvars = 1 

76 super().__init__((nvars, None, np.dtype('float64'))) 

77 

78 self.f = self.f_NONLIN if nonLinear else self.f_LIN 

79 self.jac = self.jac_NONLIN if nonLinear else self.jac_LIN 

80 self._makeAttributeAndRegister( 

81 'epsilon', 'nonLinear', 'newton_maxiter', 'newton_tol', 'stop_at_nan', localVars=locals(), readOnly=True 

82 ) 

83 self.work_counters['newton'] = WorkCounter() 

84 self.work_counters['rhs'] = WorkCounter() 

85 

86 # ------------------------------------------------------------------------- 

87 # g function (analytical solution), and its first derivative 

88 # ------------------------------------------------------------------------- 

89 def g(self, t): 

90 r""" 

91 Function :math:`g(t) = \cos(t)`, which is the exact solution. 

92 

93 Parameters 

94 ---------- 

95 t : float 

96 Time. 

97 

98 Returns 

99 ------- 

100 float 

101 The value of :math:`g(t)`. 

102 """ 

103 return np.cos(t) 

104 

105 def dg(self, t): 

106 r""" 

107 First derivative :math:`g'(t) = -\sin(t)` of :math:`g`. 

108 

109 Parameters 

110 ---------- 

111 t : float 

112 Time. 

113 

114 Returns 

115 ------- 

116 float 

117 The value of :math:`g'(t)`. 

118 """ 

119 return -np.sin(t) 

120 

121 # ------------------------------------------------------------------------- 

122 # f(u,t) and Jacobian functions 

123 # ------------------------------------------------------------------------- 

124 def f(self, u, t): 

125 """ 

126 Right-hand side, replaced in ``__init__`` by ``f_LIN`` or ``f_NONLIN``. 

127 

128 Parameters 

129 ---------- 

130 u : dtype_u 

131 Current values of the numerical solution. 

132 t : float 

133 Current time. 

134 

135 Raises 

136 ------ 

137 NotImplementedError 

138 Always. 

139 """ 

140 raise NotImplementedError() 

141 

142 def f_LIN(self, u, t): 

143 r""" 

144 Right-hand side :math:`-(u - g(t)) / \epsilon + g'(t)` of the linear form. 

145 

146 Parameters 

147 ---------- 

148 u : dtype_u 

149 Current values of the numerical solution. 

150 t : float 

151 Current time. 

152 

153 Returns 

154 ------- 

155 dtype_u 

156 The right-hand side. 

157 """ 

158 return -self.epsilon ** (-1) * (u - self.g(t)) + self.dg(t) 

159 

160 def f_NONLIN(self, u, t): 

161 r""" 

162 Right-hand side :math:`-(u^3 - g(t)^3) / \epsilon + g'(t)` of the nonlinear form. 

163 

164 Parameters 

165 ---------- 

166 u : dtype_u 

167 Current values of the numerical solution. 

168 t : float 

169 Current time. 

170 

171 Returns 

172 ------- 

173 dtype_u 

174 The right-hand side. 

175 """ 

176 return -self.epsilon ** (-1) * (u**3 - self.g(t) ** 3) + self.dg(t) 

177 

178 def jac(self, u, t): 

179 """ 

180 Jacobian of the right-hand side, replaced in ``__init__`` by ``jac_LIN`` or ``jac_NONLIN``. 

181 

182 Parameters 

183 ---------- 

184 u : dtype_u 

185 Current values of the numerical solution. 

186 t : float 

187 Current time. 

188 

189 Raises 

190 ------ 

191 NotImplementedError 

192 Always. 

193 """ 

194 raise NotImplementedError() 

195 

196 def jac_LIN(self, u, t): 

197 r""" 

198 Jacobian :math:`-1/\epsilon` of the right-hand side of the linear form. 

199 

200 Parameters 

201 ---------- 

202 u : dtype_u 

203 Current values of the numerical solution. 

204 t : float 

205 Current time. 

206 

207 Returns 

208 ------- 

209 float 

210 The Jacobian. 

211 """ 

212 return -self.epsilon ** (-1) 

213 

214 def jac_NONLIN(self, u, t): 

215 r""" 

216 Jacobian :math:`-3 u^2 / \epsilon` of the right-hand side of the nonlinear form. 

217 

218 Parameters 

219 ---------- 

220 u : dtype_u 

221 Current values of the numerical solution. 

222 t : float 

223 Current time. 

224 

225 Returns 

226 ------- 

227 dtype_u 

228 The Jacobian. 

229 """ 

230 return -self.epsilon ** (-1) * 3 * u**2 

231 

232 # ------------------------------------------------------------------------- 

233 # pySDC required methods 

234 # ------------------------------------------------------------------------- 

235 def u_exact(self, t, u_init=None, t_init=None): 

236 r""" 

237 Routine to return initial conditions or exact solution. 

238 

239 Parameters 

240 ---------- 

241 t : float 

242 Time at which the exact solution is computed. 

243 u_init : dtype_u 

244 Initial conditions for getting the exact solution. 

245 t_init : float 

246 The starting time. 

247 

248 Returns 

249 ------- 

250 u : dtype_u 

251 The exact solution. 

252 """ 

253 u = self.dtype_u(self.init) 

254 u[:] = self.g(t) 

255 return u 

256 

257 def eval_f(self, u, t): 

258 """ 

259 Routine to evaluate the right-hand side of the problem. 

260 

261 Parameters 

262 ---------- 

263 u : dtype_u 

264 Current values of the numerical solution. 

265 t : float 

266 Current time of the numerical solution is computed (not used here). 

267 

268 Returns 

269 ------- 

270 f : dtype_f 

271 The right-hand side of the problem (one component). 

272 """ 

273 

274 f = self.dtype_f(self.init) 

275 f[:] = self.f(u, t) 

276 self.work_counters['rhs']() 

277 return f 

278 

279 def solve_system(self, rhs, dt, u0, t): 

280 """ 

281 Simple Newton solver for the nonlinear equation 

282 

283 Parameters 

284 ---------- 

285 rhs : dtype_f 

286 Right-hand side for the nonlinear system. 

287 dt : float 

288 Abbrev. for the node-to-node stepsize (or any other factor required). 

289 u0 : dtype_u 

290 Initial guess for the iterative solver. 

291 t : float 

292 Time of the updated solution (e.g. for time-dependent BCs). 

293 

294 Returns 

295 ------- 

296 u : dtype_u 

297 The solution as mesh. 

298 """ 

299 # create new mesh object from u0 and set initial values for iteration 

300 u = self.dtype_u(u0) 

301 

302 # start newton iteration 

303 n, res = 0, np.inf 

304 while n < self.newton_maxiter: 

305 # form the function g with g(u) = 0 

306 g = u - dt * self.f(u, t) - rhs 

307 

308 # if g is close to 0, then we are done 

309 res = np.linalg.norm(g, np.inf) 

310 if res < self.newton_tol or np.isnan(res): 

311 break 

312 

313 # assemble dg/du 

314 dg = 1 - dt * self.jac(u, t) 

315 

316 # newton update: u1 = u0 - g/dg 

317 u -= dg ** (-1) * g 

318 

319 # increase iteration count and work counter 

320 n += 1 

321 self.work_counters['newton']() 

322 

323 if np.isnan(res) and self.stop_at_nan: 

324 raise ProblemError('Newton got nan after %i iterations, aborting...' % n) 

325 elif np.isnan(res): # pragma: no cover 

326 self.logger.warning('Newton got nan after %i iterations...' % n) 

327 

328 if n == self.newton_maxiter: 

329 raise ProblemError('Newton did not converge after %i iterations, error is %s' % (n, res)) 

330 

331 return u