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

87 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2024-04-29 09:02 +0000

1import numpy as np 

2 

3from pySDC.core.Errors import ParameterError, ProblemError 

4from pySDC.core.Problem import ptype, WorkCounter 

5from pySDC.implementations.datatype_classes.mesh import mesh 

6 

7 

8class DiscontinuousTestODE(ptype): 

9 r""" 

10 This class implements a very simple test case of a ordinary differential equation consisting of one discrete event. The dynamics of 

11 the solution changes when the state function :math:`h(u) := u - 5` changes the sign. The problem is defined by: 

12 

13 if :math:`u - 5 < 0:` 

14 

15 .. math:: 

16 \frac{d u}{dt} = u 

17 

18 else: 

19 

20 .. math:: 

21 \frac{d u}{dt} = \frac{4}{t^*}, 

22 

23 where :math:`t^* = \log(5) \approx 1.6094379`. For :math:`h(u) < 0`, i.e. :math:`t \leq t^*`, the exact solution is 

24 :math:`u(t) = \exp(t)`; for :math:`h(u) \geq 0`, i.e. :math:`t \geq t^*`, the exact solution is :math:`u(t) = \frac{4 t}{t^*} + 1`. 

25 

26 Attributes 

27 ---------- 

28 t_switch_exact : float 

29 Exact event time with :math:`t^* = \log(5)`. 

30 t_switch: float 

31 Time point of the discrete event found by switch estimation. 

32 nswitches: int 

33 Number of switches found by switch estimation. 

34 

35 Attributes 

36 ---------- 

37 work_counters : WorkCounter 

38 Counts different things, here: Number of Newton iterations is counted. 

39 """ 

40 

41 dtype_u = mesh 

42 dtype_f = mesh 

43 

44 def __init__(self, newton_maxiter=100, newton_tol=1e-8, stop_at_nan=True): 

45 """Initialization routine""" 

46 nvars = 1 

47 super().__init__(init=(nvars, None, np.dtype('float64'))) 

48 self._makeAttributeAndRegister('nvars', localVars=locals(), readOnly=True) 

49 self._makeAttributeAndRegister('newton_maxiter', 'newton_tol', 'stop_at_nan', localVars=locals()) 

50 

51 if self.nvars != 1: 

52 raise ParameterError('nvars has to be equal to 1!') 

53 

54 self.t_switch_exact = np.log(5) 

55 self.t_switch = None 

56 self.nswitches = 0 

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

58 

59 def eval_f(self, u, t): 

60 """ 

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

62 

63 Parameters 

64 ---------- 

65 u : dtype_u 

66 Current values of the numerical solution. 

67 t : float 

68 Current time of the numerical solution is computed. 

69 

70 Returns 

71 ------- 

72 f : dtype_f 

73 The right-hand side of the problem. 

74 """ 

75 

76 t_switch = np.inf if self.t_switch is None else self.t_switch 

77 

78 f = self.dtype_f(self.init, val=0.0) 

79 h = u[0] - 5 

80 if h >= 0 or t >= t_switch: 

81 f[:] = 4 / self.t_switch_exact 

82 else: 

83 f[:] = u 

84 return f 

85 

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

87 r""" 

88 Simple Newton solver for :math:`(I-factor\cdot A)\vec{u}=\vec{rhs}`. 

89 

90 Parameters 

91 ---------- 

92 rhs : dtype_f 

93 Right-hand side for the linear system. 

94 dt : float 

95 Abbrev. for the local stepsize (or any other factor required). 

96 u0 : dtype_u 

97 Initial guess for the iterative solver. 

98 t : float 

99 Current time (e.g. for time-dependent BCs). 

100 

101 Returns 

102 ------- 

103 me : dtype_u 

104 The solution as mesh. 

105 """ 

106 

107 t_switch = np.inf if self.t_switch is None else self.t_switch 

108 

109 h = rhs[0] - 5 

110 u = self.dtype_u(u0) 

111 

112 n = 0 

113 res = 99 

114 while n < self.newton_maxiter: 

115 # form function g with g(u) = 0 

116 if h >= 0 or t >= t_switch: 

117 g = u - dt * (4 / self.t_switch_exact) - rhs 

118 else: 

119 g = u - dt * u - rhs 

120 

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

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

123 

124 if res < self.newton_tol: 

125 break 

126 

127 if h >= 0 or t >= t_switch: 

128 dg = 1 

129 else: 

130 dg = 1 - dt 

131 

132 # newton update 

133 u -= 1.0 / dg * g 

134 

135 n += 1 

136 self.work_counters['newton']() 

137 

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

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

140 elif np.isnan(res): 

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

142 

143 if n == self.newton_maxiter: 

144 self.logger.warning('Newton did not converge after %i iterations, error is %s' % (n, res)) 

145 

146 me = self.dtype_u(self.init) 

147 me[:] = u[:] 

148 

149 return me 

150 

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

152 r""" 

153 Routine to compute the exact solution at time :math:`t`. 

154 

155 Parameters 

156 ---------- 

157 t : float 

158 Time of the exact solution. 

159 u_init : dtype_u 

160 Initial conditions for getting the exact solution. 

161 t_init : float 

162 The starting time. 

163 

164 Returns 

165 ------- 

166 me : dtype_u 

167 The exact solution. 

168 """ 

169 

170 if t_init is not None and u_init is not None: 

171 self.logger.warning( 

172 f'{type(self).__name__} uses an analytic exact solution from t=0. If you try to compute the local error, you will get the global error instead!' 

173 ) 

174 

175 me = self.dtype_u(self.init) 

176 if t <= self.t_switch_exact: 

177 me[:] = np.exp(t) 

178 else: 

179 me[:] = (4 * t) / self.t_switch_exact + 1 

180 return me 

181 

182 def get_switching_info(self, u, t): 

183 r""" 

184 Provides information about the state function of the problem. When the state function changes its sign, 

185 typically an event occurs. So the check for an event should be done in the way that the state function 

186 is checked for a sign change. If this is the case, the intermediate value theorem states a root in this 

187 step. 

188 

189 Parameters 

190 ---------- 

191 u : dtype_u 

192 Current values of the numerical solution at time :math:`t`. 

193 t : float 

194 Current time of the numerical solution. 

195 

196 Returns 

197 ------- 

198 switch_detected : bool 

199 Indicates whether a discrete event is found or not. 

200 m_guess : int 

201 The index before the sign changes. 

202 state_function : list 

203 Defines the values of the state function at collocation nodes where it changes the sign. 

204 """ 

205 

206 switch_detected = False 

207 m_guess = -100 

208 

209 for m in range(1, len(u)): 

210 h_prev_node = u[m - 1][0] - 5 

211 h_curr_node = u[m][0] - 5 

212 if h_prev_node < 0 and h_curr_node >= 0: 

213 switch_detected = True 

214 m_guess = m - 1 

215 break 

216 

217 state_function = [u[m][0] - 5 for m in range(len(u))] 

218 return switch_detected, m_guess, state_function 

219 

220 def count_switches(self): 

221 """ 

222 Setter to update the number of switches if one is found. 

223 """ 

224 self.nswitches += 1 

225 

226 

227class ExactDiscontinuousTestODE(DiscontinuousTestODE): 

228 r""" 

229 Dummy ODE problem for testing the ``SwitchEstimator`` class. The problem contains the exact dynamics 

230 of the problem class ``DiscontinuousTestODE``. 

231 """ 

232 

233 def __init__(self, newton_maxiter=100, newton_tol=1e-8): 

234 """Initialization routine""" 

235 super().__init__(newton_maxiter, newton_tol) 

236 

237 def eval_f(self, u, t): 

238 """ 

239 Derivative. 

240 

241 Parameters 

242 ---------- 

243 u : dtype_u 

244 Exact value of u. 

245 t : float 

246 Time :math:`t`. 

247 

248 Returns 

249 ------- 

250 f : dtype_f 

251 Derivative. 

252 """ 

253 

254 f = self.dtype_f(self.init) 

255 

256 t_switch = np.inf if self.t_switch is None else self.t_switch 

257 h = u[0] - 5 

258 if h >= 0 or t >= t_switch: 

259 f[:] = 1 

260 else: 

261 f[:] = np.exp(t) 

262 return f 

263 

264 def solve_system(self, rhs, factor, u0, t): 

265 """ 

266 Just return the exact solution... 

267 

268 Parameters 

269 ---------- 

270 rhs : dtype_f 

271 Right-hand side for the linear system. 

272 factor : float 

273 Abbrev. for the local stepsize (or any other factor required). 

274 u0 : dtype_u 

275 Initial guess for the iterative solver. 

276 t : float 

277 Current time (e.g. for time-dependent BCs). 

278 

279 Returns 

280 ------- 

281 me : dtype_u 

282 The solution as mesh. 

283 """ 

284 

285 return self.u_exact(t)