1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

from __future__ import division, print_function, absolute_import 

import numpy as np 

from .base import OdeSolver, DenseOutput 

from .common import (validate_max_step, validate_tol, select_initial_step, 

norm, warn_extraneous) 

 

 

# Multiply steps computed from asymptotic behaviour of errors by this. 

SAFETY = 0.9 

 

MIN_FACTOR = 0.2 # Minimum allowed decrease in a step size. 

MAX_FACTOR = 10 # Maximum allowed increase in a step size. 

 

 

def rk_step(fun, t, y, f, h, A, B, C, E, K): 

"""Perform a single Runge-Kutta step. 

 

This function computes a prediction of an explicit Runge-Kutta method and 

also estimates the error of a less accurate method. 

 

Notation for Butcher tableau is as in [1]_. 

 

Parameters 

---------- 

fun : callable 

Right-hand side of the system. 

t : float 

Current time. 

y : ndarray, shape (n,) 

Current state. 

f : ndarray, shape (n,) 

Current value of the derivative, i.e. ``fun(x, y)``. 

h : float 

Step to use. 

A : list of ndarray, length n_stages - 1 

Coefficients for combining previous RK stages to compute the next 

stage. For explicit methods the coefficients above the main diagonal 

are zeros, so `A` is stored as a list of arrays of increasing lengths. 

The first stage is always just `f`, thus no coefficients for it 

are required. 

B : ndarray, shape (n_stages,) 

Coefficients for combining RK stages for computing the final 

prediction. 

C : ndarray, shape (n_stages - 1,) 

Coefficients for incrementing time for consecutive RK stages. 

The value for the first stage is always zero, thus it is not stored. 

E : ndarray, shape (n_stages + 1,) 

Coefficients for estimating the error of a less accurate method. They 

are computed as the difference between b's in an extended tableau. 

K : ndarray, shape (n_stages + 1, n) 

Storage array for putting RK stages here. Stages are stored in rows. 

 

Returns 

------- 

y_new : ndarray, shape (n,) 

Solution at t + h computed with a higher accuracy. 

f_new : ndarray, shape (n,) 

Derivative ``fun(t + h, y_new)``. 

error : ndarray, shape (n,) 

Error estimate of a less accurate method. 

 

References 

---------- 

.. [1] E. Hairer, S. P. Norsett G. Wanner, "Solving Ordinary Differential 

Equations I: Nonstiff Problems", Sec. II.4. 

""" 

K[0] = f 

for s, (a, c) in enumerate(zip(A, C)): 

dy = np.dot(K[:s + 1].T, a) * h 

K[s + 1] = fun(t + c * h, y + dy) 

 

y_new = y + h * np.dot(K[:-1].T, B) 

f_new = fun(t + h, y_new) 

 

K[-1] = f_new 

error = np.dot(K.T, E) * h 

 

return y_new, f_new, error 

 

 

class RungeKutta(OdeSolver): 

"""Base class for explicit Runge-Kutta methods.""" 

C = NotImplemented 

A = NotImplemented 

B = NotImplemented 

E = NotImplemented 

P = NotImplemented 

order = NotImplemented 

n_stages = NotImplemented 

 

def __init__(self, fun, t0, y0, t_bound, max_step=np.inf, 

rtol=1e-3, atol=1e-6, vectorized=False, **extraneous): 

warn_extraneous(extraneous) 

super(RungeKutta, self).__init__(fun, t0, y0, t_bound, vectorized, 

support_complex=True) 

self.y_old = None 

self.max_step = validate_max_step(max_step) 

self.rtol, self.atol = validate_tol(rtol, atol, self.n) 

self.f = self.fun(self.t, self.y) 

self.h_abs = select_initial_step( 

self.fun, self.t, self.y, self.f, self.direction, 

self.order, self.rtol, self.atol) 

self.K = np.empty((self.n_stages + 1, self.n), dtype=self.y.dtype) 

 

def _step_impl(self): 

t = self.t 

y = self.y 

 

max_step = self.max_step 

rtol = self.rtol 

atol = self.atol 

 

min_step = 10 * np.abs(np.nextafter(t, self.direction * np.inf) - t) 

 

if self.h_abs > max_step: 

h_abs = max_step 

elif self.h_abs < min_step: 

h_abs = min_step 

else: 

h_abs = self.h_abs 

 

order = self.order 

step_accepted = False 

 

while not step_accepted: 

if h_abs < min_step: 

return False, self.TOO_SMALL_STEP 

 

h = h_abs * self.direction 

t_new = t + h 

 

if self.direction * (t_new - self.t_bound) > 0: 

t_new = self.t_bound 

 

h = t_new - t 

h_abs = np.abs(h) 

 

y_new, f_new, error = rk_step(self.fun, t, y, self.f, h, self.A, 

self.B, self.C, self.E, self.K) 

scale = atol + np.maximum(np.abs(y), np.abs(y_new)) * rtol 

error_norm = norm(error / scale) 

 

if error_norm == 0.0: 

h_abs *= MAX_FACTOR 

step_accepted = True 

elif error_norm < 1: 

h_abs *= min(MAX_FACTOR, 

max(1, SAFETY * error_norm ** (-1 / (order + 1)))) 

step_accepted = True 

else: 

h_abs *= max(MIN_FACTOR, 

SAFETY * error_norm ** (-1 / (order + 1))) 

 

self.y_old = y 

 

self.t = t_new 

self.y = y_new 

 

self.h_abs = h_abs 

self.f = f_new 

 

return True, None 

 

def _dense_output_impl(self): 

Q = self.K.T.dot(self.P) 

return RkDenseOutput(self.t_old, self.t, self.y_old, Q) 

 

 

class RK23(RungeKutta): 

"""Explicit Runge-Kutta method of order 3(2). 

 

This uses the Bogacki-Shampine pair of formulas [1]_. The error is controlled 

assuming accuracy of the second-order method, but steps are taken using the 

third-order accurate formula (local extrapolation is done). A cubic Hermite 

polynomial is used for the dense output. 

 

Can be applied in the complex domain. 

 

Parameters 

---------- 

fun : callable 

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

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

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

shape (n,). Or alternatively it can have shape (n, k), then ``fun`` 

must return array_like with shape (n, k), i.e. each column 

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

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

t0 : float 

Initial time. 

y0 : array_like, shape (n,) 

Initial state. 

t_bound : float 

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

determines the direction of the integration. 

max_step : float, optional 

Maximum allowed step size. Default is np.inf, i.e. the step size is not 

bounded and determined solely by the solver. 

rtol, atol : float and array_like, optional 

Relative and absolute tolerances. The solver keeps the local error 

estimates less than ``atol + rtol * abs(y)``. Here `rtol` controls a 

relative accuracy (number of correct digits). But if a component of `y` 

is approximately below `atol`, the error only needs to fall within 

the same `atol` threshold, and the number of correct digits is not 

guaranteed. If components of y have different scales, it might be 

beneficial to set different `atol` values for different components by 

passing array_like with shape (n,) for `atol`. Default values are 

1e-3 for `rtol` and 1e-6 for `atol`. 

vectorized : bool, optional 

Whether `fun` is implemented in a vectorized fashion. Default is False. 

 

Attributes 

---------- 

n : int 

Number of equations. 

status : string 

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

t_bound : float 

Boundary time. 

direction : float 

Integration direction: +1 or -1. 

t : float 

Current time. 

y : ndarray 

Current state. 

t_old : float 

Previous time. None if no steps were made yet. 

step_size : float 

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

nfev : int 

Number evaluations of the system's right-hand side. 

njev : int 

Number of evaluations of the Jacobian. Is always 0 for this solver as it does not use the Jacobian. 

nlu : int 

Number of LU decompositions. Is always 0 for this solver. 

 

References 

---------- 

.. [1] P. Bogacki, L.F. Shampine, "A 3(2) Pair of Runge-Kutta Formulas", 

Appl. Math. Lett. Vol. 2, No. 4. pp. 321-325, 1989. 

""" 

order = 2 

n_stages = 3 

C = np.array([1/2, 3/4]) 

A = [np.array([1/2]), 

np.array([0, 3/4])] 

B = np.array([2/9, 1/3, 4/9]) 

E = np.array([5/72, -1/12, -1/9, 1/8]) 

P = np.array([[1, -4 / 3, 5 / 9], 

[0, 1, -2/3], 

[0, 4/3, -8/9], 

[0, -1, 1]]) 

 

 

class RK45(RungeKutta): 

"""Explicit Runge-Kutta method of order 5(4). 

 

This uses the Dormand-Prince pair of formulas [1]_. The error is controlled 

assuming accuracy of the fourth-order method accuracy, but steps are taken 

using the fifth-order accurate formula (local extrapolation is done). 

A quartic interpolation polynomial is used for the dense output [2]_. 

 

Can be applied in the complex domain. 

 

Parameters 

---------- 

fun : callable 

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

Here ``t`` is a scalar, and there are two options for the ndarray ``y``: 

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

shape (n,). Alternatively it can have shape (n, k); then ``fun`` 

must return an array_like with shape (n, k), i.e. each column 

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

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

t0 : float 

Initial time. 

y0 : array_like, shape (n,) 

Initial state. 

t_bound : float 

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

determines the direction of the integration. 

max_step : float, optional 

Maximum allowed step size. Default is np.inf, i.e. the step size is not 

bounded and determined solely by the solver. 

rtol, atol : float and array_like, optional 

Relative and absolute tolerances. The solver keeps the local error 

estimates less than ``atol + rtol * abs(y)``. Here `rtol` controls a 

relative accuracy (number of correct digits). But if a component of `y` 

is approximately below `atol`, the error only needs to fall within 

the same `atol` threshold, and the number of correct digits is not 

guaranteed. If components of y have different scales, it might be 

beneficial to set different `atol` values for different components by 

passing array_like with shape (n,) for `atol`. Default values are 

1e-3 for `rtol` and 1e-6 for `atol`. 

vectorized : bool, optional 

Whether `fun` is implemented in a vectorized fashion. Default is False. 

 

Attributes 

---------- 

n : int 

Number of equations. 

status : string 

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

t_bound : float 

Boundary time. 

direction : float 

Integration direction: +1 or -1. 

t : float 

Current time. 

y : ndarray 

Current state. 

t_old : float 

Previous time. None if no steps were made yet. 

step_size : float 

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

nfev : int 

Number evaluations of the system's right-hand side. 

njev : int 

Number of evaluations of the Jacobian. Is always 0 for this solver as it does not use the Jacobian. 

nlu : int 

Number of LU decompositions. Is always 0 for this solver. 

 

References 

---------- 

.. [1] J. R. Dormand, P. J. Prince, "A family of embedded Runge-Kutta 

formulae", Journal of Computational and Applied Mathematics, Vol. 6, 

No. 1, pp. 19-26, 1980. 

.. [2] L. W. Shampine, "Some Practical Runge-Kutta Formulas", Mathematics 

of Computation,, Vol. 46, No. 173, pp. 135-150, 1986. 

""" 

order = 4 

n_stages = 6 

C = np.array([1/5, 3/10, 4/5, 8/9, 1]) 

A = [np.array([1/5]), 

np.array([3/40, 9/40]), 

np.array([44/45, -56/15, 32/9]), 

np.array([19372/6561, -25360/2187, 64448/6561, -212/729]), 

np.array([9017/3168, -355/33, 46732/5247, 49/176, -5103/18656])] 

B = np.array([35/384, 0, 500/1113, 125/192, -2187/6784, 11/84]) 

E = np.array([-71/57600, 0, 71/16695, -71/1920, 17253/339200, -22/525, 

1/40]) 

# Corresponds to the optimum value of c_6 from [2]_. 

P = np.array([ 

[1, -8048581381/2820520608, 8663915743/2820520608, 

-12715105075/11282082432], 

[0, 0, 0, 0], 

[0, 131558114200/32700410799, -68118460800/10900136933, 

87487479700/32700410799], 

[0, -1754552775/470086768, 14199869525/1410260304, 

-10690763975/1880347072], 

[0, 127303824393/49829197408, -318862633887/49829197408, 

701980252875 / 199316789632], 

[0, -282668133/205662961, 2019193451/616988883, -1453857185/822651844], 

[0, 40617522/29380423, -110615467/29380423, 69997945/29380423]]) 

 

 

class RkDenseOutput(DenseOutput): 

def __init__(self, t_old, t, y_old, Q): 

super(RkDenseOutput, self).__init__(t_old, t) 

self.h = t - t_old 

self.Q = Q 

self.order = Q.shape[1] - 1 

self.y_old = y_old 

 

def _call_impl(self, t): 

x = (t - self.t_old) / self.h 

if t.ndim == 0: 

p = np.tile(x, self.order + 1) 

p = np.cumprod(p) 

else: 

p = np.tile(x, (self.order + 1, 1)) 

p = np.cumprod(p, axis=0) 

y = self.h * np.dot(self.Q, p) 

if y.ndim == 2: 

y += self.y_old[:, None] 

else: 

y += self.y_old 

 

return y