Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/scipy/integrate/_ivp/base.py: 18%

98 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-12 06:31 +0000

1import numpy as np 

2 

3 

4def check_arguments(fun, y0, support_complex): 

5 """Helper function for checking arguments common to all solvers.""" 

6 y0 = np.asarray(y0) 

7 if np.issubdtype(y0.dtype, np.complexfloating): 

8 if not support_complex: 

9 raise ValueError("`y0` is complex, but the chosen solver does " 

10 "not support integration in a complex domain.") 

11 dtype = complex 

12 else: 

13 dtype = float 

14 y0 = y0.astype(dtype, copy=False) 

15 

16 if y0.ndim != 1: 

17 raise ValueError("`y0` must be 1-dimensional.") 

18 

19 def fun_wrapped(t, y): 

20 return np.asarray(fun(t, y), dtype=dtype) 

21 

22 return fun_wrapped, y0 

23 

24 

25class OdeSolver: 

26 """Base class for ODE solvers. 

27 

28 In order to implement a new solver you need to follow the guidelines: 

29 

30 1. A constructor must accept parameters presented in the base class 

31 (listed below) along with any other parameters specific to a solver. 

32 2. A constructor must accept arbitrary extraneous arguments 

33 ``**extraneous``, but warn that these arguments are irrelevant 

34 using `common.warn_extraneous` function. Do not pass these 

35 arguments to the base class. 

36 3. A solver must implement a private method `_step_impl(self)` which 

37 propagates a solver one step further. It must return tuple 

38 ``(success, message)``, where ``success`` is a boolean indicating 

39 whether a step was successful, and ``message`` is a string 

40 containing description of a failure if a step failed or None 

41 otherwise. 

42 4. A solver must implement a private method `_dense_output_impl(self)`, 

43 which returns a `DenseOutput` object covering the last successful 

44 step. 

45 5. A solver must have attributes listed below in Attributes section. 

46 Note that ``t_old`` and ``step_size`` are updated automatically. 

47 6. Use `fun(self, t, y)` method for the system rhs evaluation, this 

48 way the number of function evaluations (`nfev`) will be tracked 

49 automatically. 

50 7. For convenience, a base class provides `fun_single(self, t, y)` and 

51 `fun_vectorized(self, t, y)` for evaluating the rhs in 

52 non-vectorized and vectorized fashions respectively (regardless of 

53 how `fun` from the constructor is implemented). These calls don't 

54 increment `nfev`. 

55 8. If a solver uses a Jacobian matrix and LU decompositions, it should 

56 track the number of Jacobian evaluations (`njev`) and the number of 

57 LU decompositions (`nlu`). 

58 9. By convention, the function evaluations used to compute a finite 

59 difference approximation of the Jacobian should not be counted in 

60 `nfev`, thus use `fun_single(self, t, y)` or 

61 `fun_vectorized(self, t, y)` when computing a finite difference 

62 approximation of the Jacobian. 

63 

64 Parameters 

65 ---------- 

66 fun : callable 

67 Right-hand side of the system. The calling signature is ``fun(t, y)``. 

68 Here ``t`` is a scalar and there are two options for ndarray ``y``. 

69 It can either have shape (n,), then ``fun`` must return array_like with 

70 shape (n,). Or, alternatively, it can have shape (n, n_points), then 

71 ``fun`` must return array_like with shape (n, n_points) (each column 

72 corresponds to a single column in ``y``). The choice between the two 

73 options is determined by `vectorized` argument (see below). 

74 t0 : float 

75 Initial time. 

76 y0 : array_like, shape (n,) 

77 Initial state. 

78 t_bound : float 

79 Boundary time --- the integration won't continue beyond it. It also 

80 determines the direction of the integration. 

81 vectorized : bool 

82 Whether `fun` is implemented in a vectorized fashion. 

83 support_complex : bool, optional 

84 Whether integration in a complex domain should be supported. 

85 Generally determined by a derived solver class capabilities. 

86 Default is False. 

87 

88 Attributes 

89 ---------- 

90 n : int 

91 Number of equations. 

92 status : string 

93 Current status of the solver: 'running', 'finished' or 'failed'. 

94 t_bound : float 

95 Boundary time. 

96 direction : float 

97 Integration direction: +1 or -1. 

98 t : float 

99 Current time. 

100 y : ndarray 

101 Current state. 

102 t_old : float 

103 Previous time. None if no steps were made yet. 

104 step_size : float 

105 Size of the last successful step. None if no steps were made yet. 

106 nfev : int 

107 Number of the system's rhs evaluations. 

108 njev : int 

109 Number of the Jacobian evaluations. 

110 nlu : int 

111 Number of LU decompositions. 

112 """ 

113 TOO_SMALL_STEP = "Required step size is less than spacing between numbers." 

114 

115 def __init__(self, fun, t0, y0, t_bound, vectorized, 

116 support_complex=False): 

117 self.t_old = None 

118 self.t = t0 

119 self._fun, self.y = check_arguments(fun, y0, support_complex) 

120 self.t_bound = t_bound 

121 self.vectorized = vectorized 

122 

123 if vectorized: 

124 def fun_single(t, y): 

125 return self._fun(t, y[:, None]).ravel() 

126 fun_vectorized = self._fun 

127 else: 

128 fun_single = self._fun 

129 

130 def fun_vectorized(t, y): 

131 f = np.empty_like(y) 

132 for i, yi in enumerate(y.T): 

133 f[:, i] = self._fun(t, yi) 

134 return f 

135 

136 def fun(t, y): 

137 self.nfev += 1 

138 return self.fun_single(t, y) 

139 

140 self.fun = fun 

141 self.fun_single = fun_single 

142 self.fun_vectorized = fun_vectorized 

143 

144 self.direction = np.sign(t_bound - t0) if t_bound != t0 else 1 

145 self.n = self.y.size 

146 self.status = 'running' 

147 

148 self.nfev = 0 

149 self.njev = 0 

150 self.nlu = 0 

151 

152 @property 

153 def step_size(self): 

154 if self.t_old is None: 

155 return None 

156 else: 

157 return np.abs(self.t - self.t_old) 

158 

159 def step(self): 

160 """Perform one integration step. 

161 

162 Returns 

163 ------- 

164 message : string or None 

165 Report from the solver. Typically a reason for a failure if 

166 `self.status` is 'failed' after the step was taken or None 

167 otherwise. 

168 """ 

169 if self.status != 'running': 

170 raise RuntimeError("Attempt to step on a failed or finished " 

171 "solver.") 

172 

173 if self.n == 0 or self.t == self.t_bound: 

174 # Handle corner cases of empty solver or no integration. 

175 self.t_old = self.t 

176 self.t = self.t_bound 

177 message = None 

178 self.status = 'finished' 

179 else: 

180 t = self.t 

181 success, message = self._step_impl() 

182 

183 if not success: 

184 self.status = 'failed' 

185 else: 

186 self.t_old = t 

187 if self.direction * (self.t - self.t_bound) >= 0: 

188 self.status = 'finished' 

189 

190 return message 

191 

192 def dense_output(self): 

193 """Compute a local interpolant over the last successful step. 

194 

195 Returns 

196 ------- 

197 sol : `DenseOutput` 

198 Local interpolant over the last successful step. 

199 """ 

200 if self.t_old is None: 

201 raise RuntimeError("Dense output is available after a successful " 

202 "step was made.") 

203 

204 if self.n == 0 or self.t == self.t_old: 

205 # Handle corner cases of empty solver and no integration. 

206 return ConstantDenseOutput(self.t_old, self.t, self.y) 

207 else: 

208 return self._dense_output_impl() 

209 

210 def _step_impl(self): 

211 raise NotImplementedError 

212 

213 def _dense_output_impl(self): 

214 raise NotImplementedError 

215 

216 

217class DenseOutput: 

218 """Base class for local interpolant over step made by an ODE solver. 

219 

220 It interpolates between `t_min` and `t_max` (see Attributes below). 

221 Evaluation outside this interval is not forbidden, but the accuracy is not 

222 guaranteed. 

223 

224 Attributes 

225 ---------- 

226 t_min, t_max : float 

227 Time range of the interpolation. 

228 """ 

229 def __init__(self, t_old, t): 

230 self.t_old = t_old 

231 self.t = t 

232 self.t_min = min(t, t_old) 

233 self.t_max = max(t, t_old) 

234 

235 def __call__(self, t): 

236 """Evaluate the interpolant. 

237 

238 Parameters 

239 ---------- 

240 t : float or array_like with shape (n_points,) 

241 Points to evaluate the solution at. 

242 

243 Returns 

244 ------- 

245 y : ndarray, shape (n,) or (n, n_points) 

246 Computed values. Shape depends on whether `t` was a scalar or a 

247 1-D array. 

248 """ 

249 t = np.asarray(t) 

250 if t.ndim > 1: 

251 raise ValueError("`t` must be a float or a 1-D array.") 

252 return self._call_impl(t) 

253 

254 def _call_impl(self, t): 

255 raise NotImplementedError 

256 

257 

258class ConstantDenseOutput(DenseOutput): 

259 """Constant value interpolator. 

260 

261 This class used for degenerate integration cases: equal integration limits 

262 or a system with 0 equations. 

263 """ 

264 def __init__(self, t_old, t, value): 

265 super().__init__(t_old, t) 

266 self.value = value 

267 

268 def _call_impl(self, t): 

269 if t.ndim == 0: 

270 return self.value 

271 else: 

272 ret = np.empty((self.value.shape[0], t.shape[0])) 

273 ret[:] = self.value[:, None] 

274 return ret