Coverage for /usr/lib/python3/dist-packages/mpmath/calculus/inverselaplace.py: 15%

170 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-14 15:55 +0200

1# contributed to mpmath by Kristopher L. Kuhlman, February 2017 

2 

3class InverseLaplaceTransform(object): 

4 r""" 

5 Inverse Laplace transform methods are implemented using this 

6 class, in order to simplify the code and provide a common 

7 infrastructure. 

8 

9 Implement a custom inverse Laplace transform algorithm by 

10 subclassing :class:`InverseLaplaceTransform` and implementing the 

11 appropriate methods. The subclass can then be used by 

12 :func:`~mpmath.invertlaplace` by passing it as the *method* 

13 argument. 

14 """ 

15 

16 def __init__(self,ctx): 

17 self.ctx = ctx 

18 

19 def calc_laplace_parameter(self,t,**kwargs): 

20 r""" 

21 Determine the vector of Laplace parameter values needed for an 

22 algorithm, this will depend on the choice of algorithm (de 

23 Hoog is default), the algorithm-specific parameters passed (or 

24 default ones), and desired time. 

25 """ 

26 raise NotImplementedError 

27 

28 def calc_time_domain_solution(self,fp): 

29 r""" 

30 Compute the time domain solution, after computing the 

31 Laplace-space function evaluations at the abscissa required 

32 for the algorithm. Abscissa computed for one algorithm are 

33 typically not useful for another algorithm. 

34 """ 

35 raise NotImplementedError 

36 

37class FixedTalbot(InverseLaplaceTransform): 

38 

39 def calc_laplace_parameter(self,t,**kwargs): 

40 r"""The "fixed" Talbot method deforms the Bromwich contour towards 

41 `-\infty` in the shape of a parabola. Traditionally the Talbot 

42 algorithm has adjustable parameters, but the "fixed" version 

43 does not. The `r` parameter could be passed in as a parameter, 

44 if you want to override the default given by (Abate & Valko, 

45 2004). 

46 

47 The Laplace parameter is sampled along a parabola opening 

48 along the negative imaginary axis, with the base of the 

49 parabola along the real axis at 

50 `p=\frac{r}{t_\mathrm{max}}`. As the number of terms used in 

51 the approximation (degree) grows, the abscissa required for 

52 function evaluation tend towards `-\infty`, requiring high 

53 precision to prevent overflow. If any poles, branch cuts or 

54 other singularities exist such that the deformed Bromwich 

55 contour lies to the left of the singularity, the method will 

56 fail. 

57 

58 **Optional arguments** 

59 

60 :class:`~mpmath.calculus.inverselaplace.FixedTalbot.calc_laplace_parameter` 

61 recognizes the following keywords 

62 

63 *tmax* 

64 maximum time associated with vector of times 

65 (typically just the time requested) 

66 *degree* 

67 integer order of approximation (M = number of terms) 

68 *r* 

69 abscissa for `p_0` (otherwise computed using rule 

70 of thumb `2M/5`) 

71 

72 The working precision will be increased according to a rule of 

73 thumb. If 'degree' is not specified, the working precision and 

74 degree are chosen to hopefully achieve the dps of the calling 

75 context. If 'degree' is specified, the working precision is 

76 chosen to achieve maximum resulting precision for the 

77 specified degree. 

78 

79 .. math :: 

80 

81 p_0=\frac{r}{t} 

82 

83 .. math :: 

84 

85 p_i=\frac{i r \pi}{Mt_\mathrm{max}}\left[\cot\left( 

86 \frac{i\pi}{M}\right) + j \right] \qquad 1\le i <M 

87 

88 where `j=\sqrt{-1}`, `r=2M/5`, and `t_\mathrm{max}` is the 

89 maximum specified time. 

90 

91 """ 

92 

93 # required 

94 # ------------------------------ 

95 # time of desired approximation 

96 self.t = self.ctx.convert(t) 

97 

98 # optional 

99 # ------------------------------ 

100 # maximum time desired (used for scaling) default is requested 

101 # time. 

102 self.tmax = self.ctx.convert(kwargs.get('tmax',self.t)) 

103 

104 # empirical relationships used here based on a linear fit of 

105 # requested and delivered dps for exponentially decaying time 

106 # functions for requested dps up to 512. 

107 

108 if 'degree' in kwargs: 

109 self.degree = kwargs['degree'] 

110 self.dps_goal = self.degree 

111 else: 

112 self.dps_goal = int(1.72*self.ctx.dps) 

113 self.degree = max(12,int(1.38*self.dps_goal)) 

114 

115 M = self.degree 

116 

117 # this is adjusting the dps of the calling context hopefully 

118 # the caller doesn't monkey around with it between calling 

119 # this routine and calc_time_domain_solution() 

120 self.dps_orig = self.ctx.dps 

121 self.ctx.dps = self.dps_goal 

122 

123 # Abate & Valko rule of thumb for r parameter 

124 self.r = kwargs.get('r',self.ctx.fraction(2,5)*M) 

125 

126 self.theta = self.ctx.linspace(0.0, self.ctx.pi, M+1) 

127 

128 self.cot_theta = self.ctx.matrix(M,1) 

129 self.cot_theta[0] = 0 # not used 

130 

131 # all but time-dependent part of p 

132 self.delta = self.ctx.matrix(M,1) 

133 self.delta[0] = self.r 

134 

135 for i in range(1,M): 

136 self.cot_theta[i] = self.ctx.cot(self.theta[i]) 

137 self.delta[i] = self.r*self.theta[i]*(self.cot_theta[i] + 1j) 

138 

139 self.p = self.ctx.matrix(M,1) 

140 self.p = self.delta/self.tmax 

141 

142 # NB: p is complex (mpc) 

143 

144 def calc_time_domain_solution(self,fp,t,manual_prec=False): 

145 r"""The fixed Talbot time-domain solution is computed from the 

146 Laplace-space function evaluations using 

147 

148 .. math :: 

149 

150 f(t,M)=\frac{2}{5t}\sum_{k=0}^{M-1}\Re \left[ 

151 \gamma_k \bar{f}(p_k)\right] 

152 

153 where 

154 

155 .. math :: 

156 

157 \gamma_0 = \frac{1}{2}e^{r}\bar{f}(p_0) 

158 

159 .. math :: 

160 

161 \gamma_k = e^{tp_k}\left\lbrace 1 + \frac{jk\pi}{M}\left[1 + 

162 \cot \left( \frac{k \pi}{M} \right)^2 \right] - j\cot\left( 

163 \frac{k \pi}{M}\right)\right \rbrace \qquad 1\le k<M. 

164 

165 Again, `j=\sqrt{-1}`. 

166 

167 Before calling this function, call 

168 :class:`~mpmath.calculus.inverselaplace.FixedTalbot.calc_laplace_parameter` 

169 to set the parameters and compute the required coefficients. 

170 

171 **References** 

172 

173 1. Abate, J., P. Valko (2004). Multi-precision Laplace 

174 transform inversion. *International Journal for Numerical 

175 Methods in Engineering* 60:979-993, 

176 http://dx.doi.org/10.1002/nme.995 

177 2. Talbot, A. (1979). The accurate numerical inversion of 

178 Laplace transforms. *IMA Journal of Applied Mathematics* 

179 23(1):97, http://dx.doi.org/10.1093/imamat/23.1.97 

180 """ 

181 

182 # required 

183 # ------------------------------ 

184 self.t = self.ctx.convert(t) 

185 

186 # assume fp was computed from p matrix returned from 

187 # calc_laplace_parameter(), so is already a list or matrix of 

188 # mpmath 'mpc' types 

189 

190 # these were computed in previous call to 

191 # calc_laplace_parameter() 

192 theta = self.theta 

193 delta = self.delta 

194 M = self.degree 

195 p = self.p 

196 r = self.r 

197 

198 ans = self.ctx.matrix(M,1) 

199 ans[0] = self.ctx.exp(delta[0])*fp[0]/2 

200 

201 for i in range(1,M): 

202 ans[i] = self.ctx.exp(delta[i])*fp[i]*( 

203 1 + 1j*theta[i]*(1 + self.cot_theta[i]**2) - 

204 1j*self.cot_theta[i]) 

205 

206 result = self.ctx.fraction(2,5)*self.ctx.fsum(ans)/self.t 

207 

208 # setting dps back to value when calc_laplace_parameter was 

209 # called, unless flag is set. 

210 if not manual_prec: 

211 self.ctx.dps = self.dps_orig 

212 

213 return result.real 

214 

215# **************************************** 

216 

217class Stehfest(InverseLaplaceTransform): 

218 

219 def calc_laplace_parameter(self,t,**kwargs): 

220 r""" 

221 The Gaver-Stehfest method is a discrete approximation of the 

222 Widder-Post inversion algorithm, rather than a direct 

223 approximation of the Bromwich contour integral. 

224 

225 The method abscissa along the real axis, and therefore has 

226 issues inverting oscillatory functions (which have poles in 

227 pairs away from the real axis). 

228 

229 The working precision will be increased according to a rule of 

230 thumb. If 'degree' is not specified, the working precision and 

231 degree are chosen to hopefully achieve the dps of the calling 

232 context. If 'degree' is specified, the working precision is 

233 chosen to achieve maximum resulting precision for the 

234 specified degree. 

235 

236 .. math :: 

237 

238 p_k = \frac{k \log 2}{t} \qquad 1 \le k \le M 

239 """ 

240 

241 # required 

242 # ------------------------------ 

243 # time of desired approximation 

244 self.t = self.ctx.convert(t) 

245 

246 # optional 

247 # ------------------------------ 

248 

249 # empirical relationships used here based on a linear fit of 

250 # requested and delivered dps for exponentially decaying time 

251 # functions for requested dps up to 512. 

252 

253 if 'degree' in kwargs: 

254 self.degree = kwargs['degree'] 

255 self.dps_goal = int(1.38*self.degree) 

256 else: 

257 self.dps_goal = int(2.93*self.ctx.dps) 

258 self.degree = max(16,self.dps_goal) 

259 

260 # _coeff routine requires even degree 

261 if self.degree%2 > 0: 

262 self.degree += 1 

263 

264 M = self.degree 

265 

266 # this is adjusting the dps of the calling context 

267 # hopefully the caller doesn't monkey around with it 

268 # between calling this routine and calc_time_domain_solution() 

269 self.dps_orig = self.ctx.dps 

270 self.ctx.dps = self.dps_goal 

271 

272 self.V = self._coeff() 

273 self.p = self.ctx.matrix(self.ctx.arange(1,M+1))*self.ctx.ln2/self.t 

274 

275 # NB: p is real (mpf) 

276 

277 def _coeff(self): 

278 r"""Salzer summation weights (aka, "Stehfest coefficients") 

279 only depend on the approximation order (M) and the precision""" 

280 

281 M = self.degree 

282 M2 = int(M/2) # checked earlier that M is even 

283 

284 V = self.ctx.matrix(M,1) 

285 

286 # Salzer summation weights 

287 # get very large in magnitude and oscillate in sign, 

288 # if the precision is not high enough, there will be 

289 # catastrophic cancellation 

290 for k in range(1,M+1): 

291 z = self.ctx.matrix(min(k,M2)+1,1) 

292 for j in range(int((k+1)/2),min(k,M2)+1): 

293 z[j] = (self.ctx.power(j,M2)*self.ctx.fac(2*j)/ 

294 (self.ctx.fac(M2-j)*self.ctx.fac(j)* 

295 self.ctx.fac(j-1)*self.ctx.fac(k-j)* 

296 self.ctx.fac(2*j-k))) 

297 V[k-1] = self.ctx.power(-1,k+M2)*self.ctx.fsum(z) 

298 

299 return V 

300 

301 def calc_time_domain_solution(self,fp,t,manual_prec=False): 

302 r"""Compute time-domain Stehfest algorithm solution. 

303 

304 .. math :: 

305 

306 f(t,M) = \frac{\log 2}{t} \sum_{k=1}^{M} V_k \bar{f}\left( 

307 p_k \right) 

308 

309 where 

310 

311 .. math :: 

312 

313 V_k = (-1)^{k + N/2} \sum^{\min(k,N/2)}_{i=\lfloor(k+1)/2 \rfloor} 

314 \frac{i^{\frac{N}{2}}(2i)!}{\left(\frac{N}{2}-i \right)! \, i! \, 

315 \left(i-1 \right)! \, \left(k-i\right)! \, \left(2i-k \right)!} 

316 

317 As the degree increases, the abscissa (`p_k`) only increase 

318 linearly towards `\infty`, but the Stehfest coefficients 

319 (`V_k`) alternate in sign and increase rapidly in sign, 

320 requiring high precision to prevent overflow or loss of 

321 significance when evaluating the sum. 

322 

323 **References** 

324 

325 1. Widder, D. (1941). *The Laplace Transform*. Princeton. 

326 2. Stehfest, H. (1970). Algorithm 368: numerical inversion of 

327 Laplace transforms. *Communications of the ACM* 13(1):47-49, 

328 http://dx.doi.org/10.1145/361953.361969 

329 

330 """ 

331 

332 # required 

333 self.t = self.ctx.convert(t) 

334 

335 # assume fp was computed from p matrix returned from 

336 # calc_laplace_parameter(), so is already 

337 # a list or matrix of mpmath 'mpf' types 

338 

339 result = self.ctx.fdot(self.V,fp)*self.ctx.ln2/self.t 

340 

341 # setting dps back to value when calc_laplace_parameter was called 

342 if not manual_prec: 

343 self.ctx.dps = self.dps_orig 

344 

345 # ignore any small imaginary part 

346 return result.real 

347 

348# **************************************** 

349 

350class deHoog(InverseLaplaceTransform): 

351 

352 def calc_laplace_parameter(self,t,**kwargs): 

353 r"""the de Hoog, Knight & Stokes algorithm is an 

354 accelerated form of the Fourier series numerical 

355 inverse Laplace transform algorithms. 

356 

357 .. math :: 

358 

359 p_k = \gamma + \frac{jk}{T} \qquad 0 \le k < 2M+1 

360 

361 where 

362 

363 .. math :: 

364 

365 \gamma = \alpha - \frac{\log \mathrm{tol}}{2T}, 

366 

367 `j=\sqrt{-1}`, `T = 2t_\mathrm{max}` is a scaled time, 

368 `\alpha=10^{-\mathrm{dps\_goal}}` is the real part of the 

369 rightmost pole or singularity, which is chosen based on the 

370 desired accuracy (assuming the rightmost singularity is 0), 

371 and `\mathrm{tol}=10\alpha` is the desired tolerance, which is 

372 chosen in relation to `\alpha`.` 

373 

374 When increasing the degree, the abscissa increase towards 

375 `j\infty`, but more slowly than the fixed Talbot 

376 algorithm. The de Hoog et al. algorithm typically does better 

377 with oscillatory functions of time, and less well-behaved 

378 functions. The method tends to be slower than the Talbot and 

379 Stehfest algorithsm, especially so at very high precision 

380 (e.g., `>500` digits precision). 

381 

382 """ 

383 

384 # required 

385 # ------------------------------ 

386 self.t = self.ctx.convert(t) 

387 

388 # optional 

389 # ------------------------------ 

390 self.tmax = kwargs.get('tmax',self.t) 

391 

392 # empirical relationships used here based on a linear fit of 

393 # requested and delivered dps for exponentially decaying time 

394 # functions for requested dps up to 512. 

395 

396 if 'degree' in kwargs: 

397 self.degree = kwargs['degree'] 

398 self.dps_goal = int(1.38*self.degree) 

399 else: 

400 self.dps_goal = int(self.ctx.dps*1.36) 

401 self.degree = max(10,self.dps_goal) 

402 

403 # 2*M+1 terms in approximation 

404 M = self.degree 

405 

406 # adjust alpha component of abscissa of convergence for higher 

407 # precision 

408 tmp = self.ctx.power(10.0,-self.dps_goal) 

409 self.alpha = self.ctx.convert(kwargs.get('alpha',tmp)) 

410 

411 # desired tolerance (here simply related to alpha) 

412 self.tol = self.ctx.convert(kwargs.get('tol',self.alpha*10.0)) 

413 self.np = 2*self.degree+1 # number of terms in approximation 

414 

415 # this is adjusting the dps of the calling context 

416 # hopefully the caller doesn't monkey around with it 

417 # between calling this routine and calc_time_domain_solution() 

418 self.dps_orig = self.ctx.dps 

419 self.ctx.dps = self.dps_goal 

420 

421 # scaling factor (likely tun-able, but 2 is typical) 

422 self.scale = kwargs.get('scale',2) 

423 self.T = self.ctx.convert(kwargs.get('T',self.scale*self.tmax)) 

424 

425 self.p = self.ctx.matrix(2*M+1,1) 

426 self.gamma = self.alpha - self.ctx.log(self.tol)/(self.scale*self.T) 

427 self.p = (self.gamma + self.ctx.pi* 

428 self.ctx.matrix(self.ctx.arange(self.np))/self.T*1j) 

429 

430 # NB: p is complex (mpc) 

431 

432 def calc_time_domain_solution(self,fp,t,manual_prec=False): 

433 r"""Calculate time-domain solution for 

434 de Hoog, Knight & Stokes algorithm. 

435 

436 The un-accelerated Fourier series approach is: 

437 

438 .. math :: 

439 

440 f(t,2M+1) = \frac{e^{\gamma t}}{T} \sum_{k=0}^{2M}{}^{'} 

441 \Re\left[\bar{f}\left( p_k \right) 

442 e^{i\pi t/T} \right], 

443 

444 where the prime on the summation indicates the first term is halved. 

445 

446 This simplistic approach requires so many function evaluations 

447 that it is not practical. Non-linear acceleration is 

448 accomplished via Pade-approximation and an analytic expression 

449 for the remainder of the continued fraction. See the original 

450 paper (reference 2 below) a detailed description of the 

451 numerical approach. 

452 

453 **References** 

454 

455 1. Davies, B. (2005). *Integral Transforms and their 

456 Applications*, Third Edition. Springer. 

457 2. de Hoog, F., J. Knight, A. Stokes (1982). An improved 

458 method for numerical inversion of Laplace transforms. *SIAM 

459 Journal of Scientific and Statistical Computing* 3:357-366, 

460 http://dx.doi.org/10.1137/0903022 

461 

462 """ 

463 

464 M = self.degree 

465 np = self.np 

466 T = self.T 

467 

468 self.t = self.ctx.convert(t) 

469 

470 # would it be useful to try re-using 

471 # space between e&q and A&B? 

472 e = self.ctx.zeros(np,M+1) 

473 q = self.ctx.matrix(2*M,M) 

474 d = self.ctx.matrix(np,1) 

475 A = self.ctx.zeros(np+1,1) 

476 B = self.ctx.ones(np+1,1) 

477 

478 # initialize Q-D table 

479 e[:,0] = 0.0 + 0j 

480 q[0,0] = fp[1]/(fp[0]/2) 

481 for i in range(1,2*M): 

482 q[i,0] = fp[i+1]/fp[i] 

483 

484 # rhombus rule for filling triangular Q-D table (e & q) 

485 for r in range(1,M+1): 

486 # start with e, column 1, 0:2*M-2 

487 mr = 2*(M-r) + 1 

488 e[0:mr,r] = q[1:mr+1,r-1] - q[0:mr,r-1] + e[1:mr+1,r-1] 

489 if not r == M: 

490 rq = r+1 

491 mr = 2*(M-rq)+1 + 2 

492 for i in range(mr): 

493 q[i,rq-1] = q[i+1,rq-2]*e[i+1,rq-1]/e[i,rq-1] 

494 

495 # build up continued fraction coefficients (d) 

496 d[0] = fp[0]/2 

497 for r in range(1,M+1): 

498 d[2*r-1] = -q[0,r-1] # even terms 

499 d[2*r] = -e[0,r] # odd terms 

500 

501 # seed A and B for recurrence 

502 A[0] = 0.0 + 0.0j 

503 A[1] = d[0] 

504 B[0:2] = 1.0 + 0.0j 

505 

506 # base of the power series 

507 z = self.ctx.expjpi(self.t/T) # i*pi is already in fcn 

508 

509 # coefficients of Pade approximation (A & B) 

510 # using recurrence for all but last term 

511 for i in range(1,2*M): 

512 A[i+1] = A[i] + d[i]*A[i-1]*z 

513 B[i+1] = B[i] + d[i]*B[i-1]*z 

514 

515 # "improved remainder" to continued fraction 

516 brem = (1 + (d[2*M-1] - d[2*M])*z)/2 

517 # powm1(x,y) computes x^y - 1 more accurately near zero 

518 rem = brem*self.ctx.powm1(1 + d[2*M]*z/brem, 

519 self.ctx.fraction(1,2)) 

520 

521 # last term of recurrence using new remainder 

522 A[np] = A[2*M] + rem*A[2*M-1] 

523 B[np] = B[2*M] + rem*B[2*M-1] 

524 

525 # diagonal Pade approximation 

526 # F=A/B represents accelerated trapezoid rule 

527 result = self.ctx.exp(self.gamma*self.t)/T*(A[np]/B[np]).real 

528 

529 # setting dps back to value when calc_laplace_parameter was called 

530 if not manual_prec: 

531 self.ctx.dps = self.dps_orig 

532 

533 return result 

534 

535# **************************************** 

536 

537class LaplaceTransformInversionMethods(object): 

538 def __init__(ctx, *args, **kwargs): 

539 ctx._fixed_talbot = FixedTalbot(ctx) 

540 ctx._stehfest = Stehfest(ctx) 

541 ctx._de_hoog = deHoog(ctx) 

542 

543 def invertlaplace(ctx, f, t, **kwargs): 

544 r"""Computes the numerical inverse Laplace transform for a 

545 Laplace-space function at a given time. The function being 

546 evaluated is assumed to be a real-valued function of time. 

547 

548 The user must supply a Laplace-space function `\bar{f}(p)`, 

549 and a desired time at which to estimate the time-domain 

550 solution `f(t)`. 

551 

552 A few basic examples of Laplace-space functions with known 

553 inverses (see references [1,2]) : 

554 

555 .. math :: 

556 

557 \mathcal{L}\left\lbrace f(t) \right\rbrace=\bar{f}(p) 

558 

559 .. math :: 

560 

561 \mathcal{L}^{-1}\left\lbrace \bar{f}(p) \right\rbrace = f(t) 

562 

563 .. math :: 

564 

565 \bar{f}(p) = \frac{1}{(p+1)^2} 

566 

567 .. math :: 

568 

569 f(t) = t e^{-t} 

570 

571 >>> from mpmath import * 

572 >>> mp.dps = 15; mp.pretty = True 

573 >>> tt = [0.001, 0.01, 0.1, 1, 10] 

574 >>> fp = lambda p: 1/(p+1)**2 

575 >>> ft = lambda t: t*exp(-t) 

576 >>> ft(tt[0]),ft(tt[0])-invertlaplace(fp,tt[0],method='talbot') 

577 (0.000999000499833375, 8.57923043561212e-20) 

578 >>> ft(tt[1]),ft(tt[1])-invertlaplace(fp,tt[1],method='talbot') 

579 (0.00990049833749168, 3.27007646698047e-19) 

580 >>> ft(tt[2]),ft(tt[2])-invertlaplace(fp,tt[2],method='talbot') 

581 (0.090483741803596, -1.75215800052168e-18) 

582 >>> ft(tt[3]),ft(tt[3])-invertlaplace(fp,tt[3],method='talbot') 

583 (0.367879441171442, 1.2428864009344e-17) 

584 >>> ft(tt[4]),ft(tt[4])-invertlaplace(fp,tt[4],method='talbot') 

585 (0.000453999297624849, 4.04513489306658e-20) 

586 

587 The methods also work for higher precision: 

588 

589 >>> mp.dps = 100; mp.pretty = True 

590 >>> nstr(ft(tt[0]),15),nstr(ft(tt[0])-invertlaplace(fp,tt[0],method='talbot'),15) 

591 ('0.000999000499833375', '-4.96868310693356e-105') 

592 >>> nstr(ft(tt[1]),15),nstr(ft(tt[1])-invertlaplace(fp,tt[1],method='talbot'),15) 

593 ('0.00990049833749168', '1.23032291513122e-104') 

594 

595 .. math :: 

596 

597 \bar{f}(p) = \frac{1}{p^2+1} 

598 

599 .. math :: 

600 

601 f(t) = \mathrm{J}_0(t) 

602 

603 >>> mp.dps = 15; mp.pretty = True 

604 >>> fp = lambda p: 1/sqrt(p*p + 1) 

605 >>> ft = lambda t: besselj(0,t) 

606 >>> ft(tt[0]),ft(tt[0])-invertlaplace(fp,tt[0]) 

607 (0.999999750000016, -6.09717765032273e-18) 

608 >>> ft(tt[1]),ft(tt[1])-invertlaplace(fp,tt[1]) 

609 (0.99997500015625, -5.61756281076169e-17) 

610 

611 .. math :: 

612 

613 \bar{f}(p) = \frac{\log p}{p} 

614 

615 .. math :: 

616 

617 f(t) = -\gamma -\log t 

618 

619 >>> mp.dps = 15; mp.pretty = True 

620 >>> fp = lambda p: log(p)/p 

621 >>> ft = lambda t: -euler-log(t) 

622 >>> ft(tt[0]),ft(tt[0])-invertlaplace(fp,tt[0],method='stehfest') 

623 (6.3305396140806, -1.92126634837863e-16) 

624 >>> ft(tt[1]),ft(tt[1])-invertlaplace(fp,tt[1],method='stehfest') 

625 (4.02795452108656, -4.81486093200704e-16) 

626 

627 **Options** 

628 

629 :func:`~mpmath.invertlaplace` recognizes the following optional keywords 

630 valid for all methods: 

631 

632 *method* 

633 Chooses numerical inverse Laplace transform algorithm 

634 (described below). 

635 *degree* 

636 Number of terms used in the approximation 

637 

638 **Algorithms** 

639 

640 Mpmath implements three numerical inverse Laplace transform 

641 algorithms, attributed to: Talbot, Stehfest, and de Hoog, 

642 Knight and Stokes. These can be selected by using 

643 *method='talbot'*, *method='stehfest'*, or *method='dehoog'* 

644 or by passing the classes *method=FixedTalbot*, 

645 *method=Stehfest*, or *method=deHoog*. The functions 

646 :func:`~mpmath.invlaptalbot`, :func:`~mpmath.invlapstehfest`, 

647 and :func:`~mpmath.invlapdehoog` are also available as 

648 shortcuts. 

649 

650 All three algorithms implement a heuristic balance between the 

651 requested precision and the precision used internally for the 

652 calculations. This has been tuned for a typical exponentially 

653 decaying function and precision up to few hundred decimal 

654 digits. 

655 

656 The Laplace transform converts the variable time (i.e., along 

657 a line) into a parameter given by the right half of the 

658 complex `p`-plane. Singularities, poles, and branch cuts in 

659 the complex `p`-plane contain all the information regarding 

660 the time behavior of the corresponding function. Any numerical 

661 method must therefore sample `p`-plane "close enough" to the 

662 singularities to accurately characterize them, while not 

663 getting too close to have catastrophic cancellation, overflow, 

664 or underflow issues. Most significantly, if one or more of the 

665 singularities in the `p`-plane is not on the left side of the 

666 Bromwich contour, its effects will be left out of the computed 

667 solution, and the answer will be completely wrong. 

668 

669 *Talbot* 

670 

671 The fixed Talbot method is high accuracy and fast, but the 

672 method can catastrophically fail for certain classes of time-domain 

673 behavior, including a Heaviside step function for positive 

674 time (e.g., `H(t-2)`), or some oscillatory behaviors. The 

675 Talbot method usually has adjustable parameters, but the 

676 "fixed" variety implemented here does not. This method 

677 deforms the Bromwich integral contour in the shape of a 

678 parabola towards `-\infty`, which leads to problems 

679 when the solution has a decaying exponential in it (e.g., a 

680 Heaviside step function is equivalent to multiplying by a 

681 decaying exponential in Laplace space). 

682 

683 *Stehfest* 

684 

685 The Stehfest algorithm only uses abscissa along the real axis 

686 of the complex `p`-plane to estimate the time-domain 

687 function. Oscillatory time-domain functions have poles away 

688 from the real axis, so this method does not work well with 

689 oscillatory functions, especially high-frequency ones. This 

690 method also depends on summation of terms in a series that 

691 grows very large, and will have catastrophic cancellation 

692 during summation if the working precision is too low. 

693 

694 *de Hoog et al.* 

695 

696 The de Hoog, Knight, and Stokes method is essentially a 

697 Fourier-series quadrature-type approximation to the Bromwich 

698 contour integral, with non-linear series acceleration and an 

699 analytical expression for the remainder term. This method is 

700 typically the most robust and is therefore the default 

701 method. This method also involves the greatest amount of 

702 overhead, so it is typically the slowest of the three methods 

703 at high precision. 

704 

705 **Singularities** 

706 

707 All numerical inverse Laplace transform methods have problems 

708 at large time when the Laplace-space function has poles, 

709 singularities, or branch cuts to the right of the origin in 

710 the complex plane. For simple poles in `\bar{f}(p)` at the 

711 `p`-plane origin, the time function is constant in time (e.g., 

712 `\mathcal{L}\left\lbrace 1 \right\rbrace=1/p` has a pole at 

713 `p=0`). A pole in `\bar{f}(p)` to the left of the origin is a 

714 decreasing function of time (e.g., `\mathcal{L}\left\lbrace 

715 e^{-t/2} \right\rbrace=1/(p+1/2)` has a pole at `p=-1/2`), and 

716 a pole to the right of the origin leads to an increasing 

717 function in time (e.g., `\mathcal{L}\left\lbrace t e^{t/4} 

718 \right\rbrace = 1/(p-1/4)^2` has a pole at `p=1/4`). When 

719 singularities occur off the real `p` axis, the time-domain 

720 function is oscillatory. For example `\mathcal{L}\left\lbrace 

721 \mathrm{J}_0(t) \right\rbrace=1/\sqrt{p^2+1}` has a branch cut 

722 starting at `p=j=\sqrt{-1}` and is a decaying oscillatory 

723 function, This range of behaviors is illustrated in Duffy [3] 

724 Figure 4.10.4, p. 228. 

725 

726 In general as `p \rightarrow \infty` `t \rightarrow 0` and 

727 vice-versa. All numerical inverse Laplace transform methods 

728 require their abscissa to shift closer to the origin for 

729 larger times. If the abscissa shift left of the rightmost 

730 singularity in the Laplace domain, the answer will be 

731 completely wrong (the effect of singularities to the right of 

732 the Bromwich contour are not included in the results). 

733 

734 For example, the following exponentially growing function has 

735 a pole at `p=3`: 

736 

737 .. math :: 

738 

739 \bar{f}(p)=\frac{1}{p^2-9} 

740 

741 .. math :: 

742 

743 f(t)=\frac{1}{3}\sinh 3t 

744 

745 >>> mp.dps = 15; mp.pretty = True 

746 >>> fp = lambda p: 1/(p*p-9) 

747 >>> ft = lambda t: sinh(3*t)/3 

748 >>> tt = [0.01,0.1,1.0,10.0] 

749 >>> ft(tt[0]),invertlaplace(fp,tt[0],method='talbot') 

750 (0.0100015000675014, 0.0100015000675014) 

751 >>> ft(tt[1]),invertlaplace(fp,tt[1],method='talbot') 

752 (0.101506764482381, 0.101506764482381) 

753 >>> ft(tt[2]),invertlaplace(fp,tt[2],method='talbot') 

754 (3.33929164246997, 3.33929164246997) 

755 >>> ft(tt[3]),invertlaplace(fp,tt[3],method='talbot') 

756 (1781079096920.74, -1.61331069624091e-14) 

757 

758 **References** 

759 

760 1. [DLMF]_ section 1.14 (http://dlmf.nist.gov/1.14T4) 

761 2. Cohen, A.M. (2007). Numerical Methods for Laplace Transform 

762 Inversion, Springer. 

763 3. Duffy, D.G. (1998). Advanced Engineering Mathematics, CRC Press. 

764 

765 **Numerical Inverse Laplace Transform Reviews** 

766 

767 1. Bellman, R., R.E. Kalaba, J.A. Lockett (1966). *Numerical 

768 inversion of the Laplace transform: Applications to Biology, 

769 Economics, Engineering, and Physics*. Elsevier. 

770 2. Davies, B., B. Martin (1979). Numerical inversion of the 

771 Laplace transform: a survey and comparison of methods. *Journal 

772 of Computational Physics* 33:1-32, 

773 http://dx.doi.org/10.1016/0021-9991(79)90025-1 

774 3. Duffy, D.G. (1993). On the numerical inversion of Laplace 

775 transforms: Comparison of three new methods on characteristic 

776 problems from applications. *ACM Transactions on Mathematical 

777 Software* 19(3):333-359, http://dx.doi.org/10.1145/155743.155788 

778 4. Kuhlman, K.L., (2013). Review of Inverse Laplace Transform 

779 Algorithms for Laplace-Space Numerical Approaches, *Numerical 

780 Algorithms*, 63(2):339-355. 

781 http://dx.doi.org/10.1007/s11075-012-9625-3 

782 

783 """ 

784 

785 rule = kwargs.get('method','dehoog') 

786 if type(rule) is str: 

787 lrule = rule.lower() 

788 if lrule == 'talbot': 

789 rule = ctx._fixed_talbot 

790 elif lrule == 'stehfest': 

791 rule = ctx._stehfest 

792 elif lrule == 'dehoog': 

793 rule = ctx._de_hoog 

794 else: 

795 raise ValueError("unknown invlap algorithm: %s" % rule) 

796 else: 

797 rule = rule(ctx) 

798 

799 # determine the vector of Laplace-space parameter 

800 # needed for the requested method and desired time 

801 rule.calc_laplace_parameter(t,**kwargs) 

802 

803 # compute the Laplace-space function evalutations 

804 # at the required abscissa. 

805 fp = [f(p) for p in rule.p] 

806 

807 # compute the time-domain solution from the 

808 # Laplace-space function evaluations 

809 return rule.calc_time_domain_solution(fp,t) 

810 

811 # shortcuts for the above function for specific methods 

812 def invlaptalbot(ctx, *args, **kwargs): 

813 kwargs['method'] = 'talbot' 

814 return ctx.invertlaplace(*args, **kwargs) 

815 

816 def invlapstehfest(ctx, *args, **kwargs): 

817 kwargs['method'] = 'stehfest' 

818 return ctx.invertlaplace(*args, **kwargs) 

819 

820 def invlapdehoog(ctx, *args, **kwargs): 

821 kwargs['method'] = 'dehoog' 

822 return ctx.invertlaplace(*args, **kwargs) 

823 

824# **************************************** 

825 

826if __name__ == '__main__': 

827 import doctest 

828 doctest.testmod()