inverselaplace.py 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973
  1. # contributed to mpmath by Kristopher L. Kuhlman, February 2017
  2. # contributed to mpmath by Guillermo Navas-Palencia, February 2022
  3. class 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. Implement a custom inverse Laplace transform algorithm by
  9. subclassing :class:`InverseLaplaceTransform` and implementing the
  10. appropriate methods. The subclass can then be used by
  11. :func:`~mpmath.invertlaplace` by passing it as the *method*
  12. argument.
  13. """
  14. def __init__(self, ctx):
  15. self.ctx = ctx
  16. def calc_laplace_parameter(self, t, **kwargs):
  17. r"""
  18. Determine the vector of Laplace parameter values needed for an
  19. algorithm, this will depend on the choice of algorithm (de
  20. Hoog is default), the algorithm-specific parameters passed (or
  21. default ones), and desired time.
  22. """
  23. raise NotImplementedError
  24. def calc_time_domain_solution(self, fp):
  25. r"""
  26. Compute the time domain solution, after computing the
  27. Laplace-space function evaluations at the abscissa required
  28. for the algorithm. Abscissa computed for one algorithm are
  29. typically not useful for another algorithm.
  30. """
  31. raise NotImplementedError
  32. class FixedTalbot(InverseLaplaceTransform):
  33. def calc_laplace_parameter(self, t, **kwargs):
  34. r"""The "fixed" Talbot method deforms the Bromwich contour towards
  35. `-\infty` in the shape of a parabola. Traditionally the Talbot
  36. algorithm has adjustable parameters, but the "fixed" version
  37. does not. The `r` parameter could be passed in as a parameter,
  38. if you want to override the default given by (Abate & Valko,
  39. 2004).
  40. The Laplace parameter is sampled along a parabola opening
  41. along the negative imaginary axis, with the base of the
  42. parabola along the real axis at
  43. `p=\frac{r}{t_\mathrm{max}}`. As the number of terms used in
  44. the approximation (degree) grows, the abscissa required for
  45. function evaluation tend towards `-\infty`, requiring high
  46. precision to prevent overflow. If any poles, branch cuts or
  47. other singularities exist such that the deformed Bromwich
  48. contour lies to the left of the singularity, the method will
  49. fail.
  50. **Optional arguments**
  51. :class:`~mpmath.calculus.inverselaplace.FixedTalbot.calc_laplace_parameter`
  52. recognizes the following keywords
  53. *tmax*
  54. maximum time associated with vector of times
  55. (typically just the time requested)
  56. *degree*
  57. integer order of approximation (M = number of terms)
  58. *r*
  59. abscissa for `p_0` (otherwise computed using rule
  60. of thumb `2M/5`)
  61. The working precision will be increased according to a rule of
  62. thumb. If 'degree' is not specified, the working precision and
  63. degree are chosen to hopefully achieve the dps of the calling
  64. context. If 'degree' is specified, the working precision is
  65. chosen to achieve maximum resulting precision for the
  66. specified degree.
  67. .. math ::
  68. p_0=\frac{r}{t}
  69. .. math ::
  70. p_i=\frac{i r \pi}{Mt_\mathrm{max}}\left[\cot\left(
  71. \frac{i\pi}{M}\right) + j \right] \qquad 1\le i <M
  72. where `j=\sqrt{-1}`, `r=2M/5`, and `t_\mathrm{max}` is the
  73. maximum specified time.
  74. """
  75. # required
  76. # ------------------------------
  77. # time of desired approximation
  78. self.t = self.ctx.convert(t)
  79. # optional
  80. # ------------------------------
  81. # maximum time desired (used for scaling) default is requested
  82. # time.
  83. self.tmax = self.ctx.convert(kwargs.get('tmax', self.t))
  84. # empirical relationships used here based on a linear fit of
  85. # requested and delivered dps for exponentially decaying time
  86. # functions for requested dps up to 512.
  87. if 'degree' in kwargs:
  88. self.degree = kwargs['degree']
  89. self.dps_goal = self.degree
  90. else:
  91. self.dps_goal = int(1.72*self.ctx.dps)
  92. self.degree = max(12, int(1.38*self.dps_goal))
  93. M = self.degree
  94. # this is adjusting the dps of the calling context hopefully
  95. # the caller doesn't monkey around with it between calling
  96. # this routine and calc_time_domain_solution()
  97. self.dps_orig = self.ctx.dps
  98. self.ctx.dps = self.dps_goal
  99. # Abate & Valko rule of thumb for r parameter
  100. self.r = kwargs.get('r', self.ctx.fraction(2, 5)*M)
  101. self.theta = self.ctx.linspace(0.0, self.ctx.pi, M+1)
  102. self.cot_theta = self.ctx.matrix(M, 1)
  103. self.cot_theta[0] = 0 # not used
  104. # all but time-dependent part of p
  105. self.delta = self.ctx.matrix(M, 1)
  106. self.delta[0] = self.r
  107. for i in range(1, M):
  108. self.cot_theta[i] = self.ctx.cot(self.theta[i])
  109. self.delta[i] = self.r*self.theta[i]*(self.cot_theta[i] + 1j)
  110. self.p = self.ctx.matrix(M, 1)
  111. self.p = self.delta/self.tmax
  112. # NB: p is complex (mpc)
  113. def calc_time_domain_solution(self, fp, t, manual_prec=False):
  114. r"""The fixed Talbot time-domain solution is computed from the
  115. Laplace-space function evaluations using
  116. .. math ::
  117. f(t,M)=\frac{2}{5t}\sum_{k=0}^{M-1}\Re \left[
  118. \gamma_k \bar{f}(p_k)\right]
  119. where
  120. .. math ::
  121. \gamma_0 = \frac{1}{2}e^{r}\bar{f}(p_0)
  122. .. math ::
  123. \gamma_k = e^{tp_k}\left\lbrace 1 + \frac{jk\pi}{M}\left[1 +
  124. \cot \left( \frac{k \pi}{M} \right)^2 \right] - j\cot\left(
  125. \frac{k \pi}{M}\right)\right \rbrace \qquad 1\le k<M.
  126. Again, `j=\sqrt{-1}`.
  127. Before calling this function, call
  128. :class:`~mpmath.calculus.inverselaplace.FixedTalbot.calc_laplace_parameter`
  129. to set the parameters and compute the required coefficients.
  130. **References**
  131. 1. Abate, J., P. Valko (2004). Multi-precision Laplace
  132. transform inversion. *International Journal for Numerical
  133. Methods in Engineering* 60:979-993,
  134. http://dx.doi.org/10.1002/nme.995
  135. 2. Talbot, A. (1979). The accurate numerical inversion of
  136. Laplace transforms. *IMA Journal of Applied Mathematics*
  137. 23(1):97, http://dx.doi.org/10.1093/imamat/23.1.97
  138. """
  139. # required
  140. # ------------------------------
  141. self.t = self.ctx.convert(t)
  142. # assume fp was computed from p matrix returned from
  143. # calc_laplace_parameter(), so is already a list or matrix of
  144. # mpmath 'mpc' types
  145. # these were computed in previous call to
  146. # calc_laplace_parameter()
  147. theta = self.theta
  148. delta = self.delta
  149. M = self.degree
  150. p = self.p
  151. r = self.r
  152. ans = self.ctx.matrix(M, 1)
  153. ans[0] = self.ctx.exp(delta[0])*fp[0]/2
  154. for i in range(1, M):
  155. ans[i] = self.ctx.exp(delta[i])*fp[i]*(
  156. 1 + 1j*theta[i]*(1 + self.cot_theta[i]**2) -
  157. 1j*self.cot_theta[i])
  158. result = self.ctx.fraction(2, 5)*self.ctx.fsum(ans)/self.t
  159. # setting dps back to value when calc_laplace_parameter was
  160. # called, unless flag is set.
  161. if not manual_prec:
  162. self.ctx.dps = self.dps_orig
  163. return result.real
  164. # ****************************************
  165. class Stehfest(InverseLaplaceTransform):
  166. def calc_laplace_parameter(self, t, **kwargs):
  167. r"""
  168. The Gaver-Stehfest method is a discrete approximation of the
  169. Widder-Post inversion algorithm, rather than a direct
  170. approximation of the Bromwich contour integral.
  171. The method abscissa along the real axis, and therefore has
  172. issues inverting oscillatory functions (which have poles in
  173. pairs away from the real axis).
  174. The working precision will be increased according to a rule of
  175. thumb. If 'degree' is not specified, the working precision and
  176. degree are chosen to hopefully achieve the dps of the calling
  177. context. If 'degree' is specified, the working precision is
  178. chosen to achieve maximum resulting precision for the
  179. specified degree.
  180. .. math ::
  181. p_k = \frac{k \log 2}{t} \qquad 1 \le k \le M
  182. """
  183. # required
  184. # ------------------------------
  185. # time of desired approximation
  186. self.t = self.ctx.convert(t)
  187. # optional
  188. # ------------------------------
  189. # empirical relationships used here based on a linear fit of
  190. # requested and delivered dps for exponentially decaying time
  191. # functions for requested dps up to 512.
  192. if 'degree' in kwargs:
  193. self.degree = kwargs['degree']
  194. self.dps_goal = int(1.38*self.degree)
  195. else:
  196. self.dps_goal = int(2.93*self.ctx.dps)
  197. self.degree = max(16, self.dps_goal)
  198. # _coeff routine requires even degree
  199. if self.degree % 2 > 0:
  200. self.degree += 1
  201. M = self.degree
  202. # this is adjusting the dps of the calling context
  203. # hopefully the caller doesn't monkey around with it
  204. # between calling this routine and calc_time_domain_solution()
  205. self.dps_orig = self.ctx.dps
  206. self.ctx.dps = self.dps_goal
  207. self.V = self._coeff()
  208. self.p = self.ctx.matrix(self.ctx.arange(1, M+1))*self.ctx.ln2/self.t
  209. # NB: p is real (mpf)
  210. def _coeff(self):
  211. r"""Salzer summation weights (aka, "Stehfest coefficients")
  212. only depend on the approximation order (M) and the precision"""
  213. M = self.degree
  214. M2 = int(M/2) # checked earlier that M is even
  215. V = self.ctx.matrix(M, 1)
  216. # Salzer summation weights
  217. # get very large in magnitude and oscillate in sign,
  218. # if the precision is not high enough, there will be
  219. # catastrophic cancellation
  220. for k in range(1, M+1):
  221. z = self.ctx.matrix(min(k, M2)+1, 1)
  222. for j in range(int((k+1)/2), min(k, M2)+1):
  223. z[j] = (self.ctx.power(j, M2)*self.ctx.fac(2*j)/
  224. (self.ctx.fac(M2-j)*self.ctx.fac(j)*
  225. self.ctx.fac(j-1)*self.ctx.fac(k-j)*
  226. self.ctx.fac(2*j-k)))
  227. V[k-1] = self.ctx.power(-1, k+M2)*self.ctx.fsum(z)
  228. return V
  229. def calc_time_domain_solution(self, fp, t, manual_prec=False):
  230. r"""Compute time-domain Stehfest algorithm solution.
  231. .. math ::
  232. f(t,M) = \frac{\log 2}{t} \sum_{k=1}^{M} V_k \bar{f}\left(
  233. p_k \right)
  234. where
  235. .. math ::
  236. V_k = (-1)^{k + N/2} \sum^{\min(k,N/2)}_{i=\lfloor(k+1)/2 \rfloor}
  237. \frac{i^{\frac{N}{2}}(2i)!}{\left(\frac{N}{2}-i \right)! \, i! \,
  238. \left(i-1 \right)! \, \left(k-i\right)! \, \left(2i-k \right)!}
  239. As the degree increases, the abscissa (`p_k`) only increase
  240. linearly towards `\infty`, but the Stehfest coefficients
  241. (`V_k`) alternate in sign and increase rapidly in sign,
  242. requiring high precision to prevent overflow or loss of
  243. significance when evaluating the sum.
  244. **References**
  245. 1. Widder, D. (1941). *The Laplace Transform*. Princeton.
  246. 2. Stehfest, H. (1970). Algorithm 368: numerical inversion of
  247. Laplace transforms. *Communications of the ACM* 13(1):47-49,
  248. http://dx.doi.org/10.1145/361953.361969
  249. """
  250. # required
  251. self.t = self.ctx.convert(t)
  252. # assume fp was computed from p matrix returned from
  253. # calc_laplace_parameter(), so is already
  254. # a list or matrix of mpmath 'mpf' types
  255. result = self.ctx.fdot(self.V, fp)*self.ctx.ln2/self.t
  256. # setting dps back to value when calc_laplace_parameter was called
  257. if not manual_prec:
  258. self.ctx.dps = self.dps_orig
  259. # ignore any small imaginary part
  260. return result.real
  261. # ****************************************
  262. class deHoog(InverseLaplaceTransform):
  263. def calc_laplace_parameter(self, t, **kwargs):
  264. r"""the de Hoog, Knight & Stokes algorithm is an
  265. accelerated form of the Fourier series numerical
  266. inverse Laplace transform algorithms.
  267. .. math ::
  268. p_k = \gamma + \frac{jk}{T} \qquad 0 \le k < 2M+1
  269. where
  270. .. math ::
  271. \gamma = \alpha - \frac{\log \mathrm{tol}}{2T},
  272. `j=\sqrt{-1}`, `T = 2t_\mathrm{max}` is a scaled time,
  273. `\alpha=10^{-\mathrm{dps\_goal}}` is the real part of the
  274. rightmost pole or singularity, which is chosen based on the
  275. desired accuracy (assuming the rightmost singularity is 0),
  276. and `\mathrm{tol}=10\alpha` is the desired tolerance, which is
  277. chosen in relation to `\alpha`.`
  278. When increasing the degree, the abscissa increase towards
  279. `j\infty`, but more slowly than the fixed Talbot
  280. algorithm. The de Hoog et al. algorithm typically does better
  281. with oscillatory functions of time, and less well-behaved
  282. functions. The method tends to be slower than the Talbot and
  283. Stehfest algorithsm, especially so at very high precision
  284. (e.g., `>500` digits precision).
  285. """
  286. # required
  287. # ------------------------------
  288. self.t = self.ctx.convert(t)
  289. # optional
  290. # ------------------------------
  291. self.tmax = kwargs.get('tmax', self.t)
  292. # empirical relationships used here based on a linear fit of
  293. # requested and delivered dps for exponentially decaying time
  294. # functions for requested dps up to 512.
  295. if 'degree' in kwargs:
  296. self.degree = kwargs['degree']
  297. self.dps_goal = int(1.38*self.degree)
  298. else:
  299. self.dps_goal = int(self.ctx.dps*1.36)
  300. self.degree = max(10, self.dps_goal)
  301. # 2*M+1 terms in approximation
  302. M = self.degree
  303. # adjust alpha component of abscissa of convergence for higher
  304. # precision
  305. tmp = self.ctx.power(10.0, -self.dps_goal)
  306. self.alpha = self.ctx.convert(kwargs.get('alpha', tmp))
  307. # desired tolerance (here simply related to alpha)
  308. self.tol = self.ctx.convert(kwargs.get('tol', self.alpha*10.0))
  309. self.np = 2*self.degree+1 # number of terms in approximation
  310. # this is adjusting the dps of the calling context
  311. # hopefully the caller doesn't monkey around with it
  312. # between calling this routine and calc_time_domain_solution()
  313. self.dps_orig = self.ctx.dps
  314. self.ctx.dps = self.dps_goal
  315. # scaling factor (likely tun-able, but 2 is typical)
  316. self.scale = kwargs.get('scale', 2)
  317. self.T = self.ctx.convert(kwargs.get('T', self.scale*self.tmax))
  318. self.p = self.ctx.matrix(2*M+1, 1)
  319. self.gamma = self.alpha - self.ctx.log(self.tol)/(self.scale*self.T)
  320. self.p = (self.gamma + self.ctx.pi*
  321. self.ctx.matrix(self.ctx.arange(self.np))/self.T*1j)
  322. # NB: p is complex (mpc)
  323. def calc_time_domain_solution(self, fp, t, manual_prec=False):
  324. r"""Calculate time-domain solution for
  325. de Hoog, Knight & Stokes algorithm.
  326. The un-accelerated Fourier series approach is:
  327. .. math ::
  328. f(t,2M+1) = \frac{e^{\gamma t}}{T} \sum_{k=0}^{2M}{}^{'}
  329. \Re\left[\bar{f}\left( p_k \right)
  330. e^{i\pi t/T} \right],
  331. where the prime on the summation indicates the first term is halved.
  332. This simplistic approach requires so many function evaluations
  333. that it is not practical. Non-linear acceleration is
  334. accomplished via Pade-approximation and an analytic expression
  335. for the remainder of the continued fraction. See the original
  336. paper (reference 2 below) a detailed description of the
  337. numerical approach.
  338. **References**
  339. 1. Davies, B. (2005). *Integral Transforms and their
  340. Applications*, Third Edition. Springer.
  341. 2. de Hoog, F., J. Knight, A. Stokes (1982). An improved
  342. method for numerical inversion of Laplace transforms. *SIAM
  343. Journal of Scientific and Statistical Computing* 3:357-366,
  344. http://dx.doi.org/10.1137/0903022
  345. """
  346. M = self.degree
  347. np = self.np
  348. T = self.T
  349. self.t = self.ctx.convert(t)
  350. # would it be useful to try re-using
  351. # space between e&q and A&B?
  352. e = self.ctx.zeros(np, M+1)
  353. q = self.ctx.matrix(2*M, M)
  354. d = self.ctx.matrix(np, 1)
  355. A = self.ctx.zeros(np+1, 1)
  356. B = self.ctx.ones(np+1, 1)
  357. # initialize Q-D table
  358. e[:, 0] = 0.0 + 0j
  359. q[0, 0] = fp[1]/(fp[0]/2)
  360. for i in range(1, 2*M):
  361. q[i, 0] = fp[i+1]/fp[i]
  362. # rhombus rule for filling triangular Q-D table (e & q)
  363. for r in range(1, M+1):
  364. # start with e, column 1, 0:2*M-2
  365. mr = 2*(M-r) + 1
  366. e[0:mr, r] = q[1:mr+1, r-1] - q[0:mr, r-1] + e[1:mr+1, r-1]
  367. if not r == M:
  368. rq = r+1
  369. mr = 2*(M-rq)+1 + 2
  370. for i in range(mr):
  371. q[i, rq-1] = q[i+1, rq-2]*e[i+1, rq-1]/e[i, rq-1]
  372. # build up continued fraction coefficients (d)
  373. d[0] = fp[0]/2
  374. for r in range(1, M+1):
  375. d[2*r-1] = -q[0, r-1] # even terms
  376. d[2*r] = -e[0, r] # odd terms
  377. # seed A and B for recurrence
  378. A[0] = 0.0 + 0.0j
  379. A[1] = d[0]
  380. B[0:2] = 1.0 + 0.0j
  381. # base of the power series
  382. z = self.ctx.expjpi(self.t/T) # i*pi is already in fcn
  383. # coefficients of Pade approximation (A & B)
  384. # using recurrence for all but last term
  385. for i in range(1, 2*M):
  386. A[i+1] = A[i] + d[i]*A[i-1]*z
  387. B[i+1] = B[i] + d[i]*B[i-1]*z
  388. # "improved remainder" to continued fraction
  389. brem = (1 + (d[2*M-1] - d[2*M])*z)/2
  390. # powm1(x,y) computes x^y - 1 more accurately near zero
  391. rem = brem*self.ctx.powm1(1 + d[2*M]*z/brem,
  392. self.ctx.fraction(1, 2))
  393. # last term of recurrence using new remainder
  394. A[np] = A[2*M] + rem*A[2*M-1]
  395. B[np] = B[2*M] + rem*B[2*M-1]
  396. # diagonal Pade approximation
  397. # F=A/B represents accelerated trapezoid rule
  398. result = self.ctx.exp(self.gamma*self.t)/T*(A[np]/B[np]).real
  399. # setting dps back to value when calc_laplace_parameter was called
  400. if not manual_prec:
  401. self.ctx.dps = self.dps_orig
  402. return result
  403. # ****************************************
  404. class Cohen(InverseLaplaceTransform):
  405. def calc_laplace_parameter(self, t, **kwargs):
  406. r"""The Cohen algorithm accelerates the convergence of the nearly
  407. alternating series resulting from the application of the trapezoidal
  408. rule to the Bromwich contour inversion integral.
  409. .. math ::
  410. p_k = \frac{\gamma}{2 t} + \frac{\pi i k}{t} \qquad 0 \le k < M
  411. where
  412. .. math ::
  413. \gamma = \frac{2}{3} (d + \log(10) + \log(2 t)),
  414. `d = \mathrm{dps\_goal}`, which is chosen based on the desired
  415. accuracy using the method developed in [1] to improve numerical
  416. stability. The Cohen algorithm shows robustness similar to the de Hoog
  417. et al. algorithm, but it is faster than the fixed Talbot algorithm.
  418. **Optional arguments**
  419. *degree*
  420. integer order of the approximation (M = number of terms)
  421. *alpha*
  422. abscissa for `p_0` (controls the discretization error)
  423. The working precision will be increased according to a rule of
  424. thumb. If 'degree' is not specified, the working precision and
  425. degree are chosen to hopefully achieve the dps of the calling
  426. context. If 'degree' is specified, the working precision is
  427. chosen to achieve maximum resulting precision for the
  428. specified degree.
  429. **References**
  430. 1. P. Glasserman, J. Ruiz-Mata (2006). Computing the credit loss
  431. distribution in the Gaussian copula model: a comparison of methods.
  432. *Journal of Credit Risk* 2(4):33-66, 10.21314/JCR.2006.057
  433. """
  434. self.t = self.ctx.convert(t)
  435. if 'degree' in kwargs:
  436. self.degree = kwargs['degree']
  437. self.dps_goal = int(1.5 * self.degree)
  438. else:
  439. self.dps_goal = int(self.ctx.dps * 1.74)
  440. self.degree = max(22, int(1.31 * self.dps_goal))
  441. M = self.degree + 1
  442. # this is adjusting the dps of the calling context hopefully
  443. # the caller doesn't monkey around with it between calling
  444. # this routine and calc_time_domain_solution()
  445. self.dps_orig = self.ctx.dps
  446. self.ctx.dps = self.dps_goal
  447. ttwo = 2 * self.t
  448. tmp = self.ctx.dps * self.ctx.log(10) + self.ctx.log(ttwo)
  449. tmp = self.ctx.fraction(2, 3) * tmp
  450. self.alpha = self.ctx.convert(kwargs.get('alpha', tmp))
  451. # all but time-dependent part of p
  452. a_t = self.alpha / ttwo
  453. p_t = self.ctx.pi * 1j / self.t
  454. self.p = self.ctx.matrix(M, 1)
  455. self.p[0] = a_t
  456. for i in range(1, M):
  457. self.p[i] = a_t + i * p_t
  458. def calc_time_domain_solution(self, fp, t, manual_prec=False):
  459. r"""Calculate time-domain solution for Cohen algorithm.
  460. The accelerated nearly alternating series is:
  461. .. math ::
  462. f(t, M) = \frac{e^{\gamma / 2}}{t} \left[\frac{1}{2}
  463. \Re\left(\bar{f}\left(\frac{\gamma}{2t}\right) \right) -
  464. \sum_{k=0}^{M-1}\frac{c_{M,k}}{d_M}\Re\left(\bar{f}
  465. \left(\frac{\gamma + 2(k+1) \pi i}{2t}\right)\right)\right],
  466. where coefficients `\frac{c_{M, k}}{d_M}` are described in [1].
  467. 1. H. Cohen, F. Rodriguez Villegas, D. Zagier (2000). Convergence
  468. acceleration of alternating series. *Experiment. Math* 9(1):3-12
  469. """
  470. self.t = self.ctx.convert(t)
  471. n = self.degree
  472. M = n + 1
  473. A = self.ctx.matrix(M, 1)
  474. for i in range(M):
  475. A[i] = fp[i].real
  476. d = (3 + self.ctx.sqrt(8)) ** n
  477. d = (d + 1 / d) / 2
  478. b = -self.ctx.one
  479. c = -d
  480. s = 0
  481. for k in range(n):
  482. c = b - c
  483. s = s + c * A[k + 1]
  484. b = 2 * (k + n) * (k - n) * b / ((2 * k + 1) * (k + self.ctx.one))
  485. result = self.ctx.exp(self.alpha / 2) / self.t * (A[0] / 2 - s / d)
  486. # setting dps back to value when calc_laplace_parameter was
  487. # called, unless flag is set.
  488. if not manual_prec:
  489. self.ctx.dps = self.dps_orig
  490. return result
  491. # ****************************************
  492. class LaplaceTransformInversionMethods(object):
  493. def __init__(ctx, *args, **kwargs):
  494. ctx._fixed_talbot = FixedTalbot(ctx)
  495. ctx._stehfest = Stehfest(ctx)
  496. ctx._de_hoog = deHoog(ctx)
  497. ctx._cohen = Cohen(ctx)
  498. def invertlaplace(ctx, f, t, **kwargs):
  499. r"""Computes the numerical inverse Laplace transform for a
  500. Laplace-space function at a given time. The function being
  501. evaluated is assumed to be a real-valued function of time.
  502. The user must supply a Laplace-space function `\bar{f}(p)`,
  503. and a desired time at which to estimate the time-domain
  504. solution `f(t)`.
  505. A few basic examples of Laplace-space functions with known
  506. inverses (see references [1,2]) :
  507. .. math ::
  508. \mathcal{L}\left\lbrace f(t) \right\rbrace=\bar{f}(p)
  509. .. math ::
  510. \mathcal{L}^{-1}\left\lbrace \bar{f}(p) \right\rbrace = f(t)
  511. .. math ::
  512. \bar{f}(p) = \frac{1}{(p+1)^2}
  513. .. math ::
  514. f(t) = t e^{-t}
  515. >>> from mpmath import *
  516. >>> mp.dps = 15; mp.pretty = True
  517. >>> tt = [0.001, 0.01, 0.1, 1, 10]
  518. >>> fp = lambda p: 1/(p+1)**2
  519. >>> ft = lambda t: t*exp(-t)
  520. >>> ft(tt[0]),ft(tt[0])-invertlaplace(fp,tt[0],method='talbot')
  521. (0.000999000499833375, 8.57923043561212e-20)
  522. >>> ft(tt[1]),ft(tt[1])-invertlaplace(fp,tt[1],method='talbot')
  523. (0.00990049833749168, 3.27007646698047e-19)
  524. >>> ft(tt[2]),ft(tt[2])-invertlaplace(fp,tt[2],method='talbot')
  525. (0.090483741803596, -1.75215800052168e-18)
  526. >>> ft(tt[3]),ft(tt[3])-invertlaplace(fp,tt[3],method='talbot')
  527. (0.367879441171442, 1.2428864009344e-17)
  528. >>> ft(tt[4]),ft(tt[4])-invertlaplace(fp,tt[4],method='talbot')
  529. (0.000453999297624849, 4.04513489306658e-20)
  530. The methods also work for higher precision:
  531. >>> mp.dps = 100; mp.pretty = True
  532. >>> nstr(ft(tt[0]),15),nstr(ft(tt[0])-invertlaplace(fp,tt[0],method='talbot'),15)
  533. ('0.000999000499833375', '-4.96868310693356e-105')
  534. >>> nstr(ft(tt[1]),15),nstr(ft(tt[1])-invertlaplace(fp,tt[1],method='talbot'),15)
  535. ('0.00990049833749168', '1.23032291513122e-104')
  536. .. math ::
  537. \bar{f}(p) = \frac{1}{p^2+1}
  538. .. math ::
  539. f(t) = \mathrm{J}_0(t)
  540. >>> mp.dps = 15; mp.pretty = True
  541. >>> fp = lambda p: 1/sqrt(p*p + 1)
  542. >>> ft = lambda t: besselj(0,t)
  543. >>> ft(tt[0]),ft(tt[0])-invertlaplace(fp,tt[0],method='dehoog')
  544. (0.999999750000016, -6.09717765032273e-18)
  545. >>> ft(tt[1]),ft(tt[1])-invertlaplace(fp,tt[1],method='dehoog')
  546. (0.99997500015625, -5.61756281076169e-17)
  547. .. math ::
  548. \bar{f}(p) = \frac{\log p}{p}
  549. .. math ::
  550. f(t) = -\gamma -\log t
  551. >>> mp.dps = 15; mp.pretty = True
  552. >>> fp = lambda p: log(p)/p
  553. >>> ft = lambda t: -euler-log(t)
  554. >>> ft(tt[0]),ft(tt[0])-invertlaplace(fp,tt[0],method='stehfest')
  555. (6.3305396140806, -1.92126634837863e-16)
  556. >>> ft(tt[1]),ft(tt[1])-invertlaplace(fp,tt[1],method='stehfest')
  557. (4.02795452108656, -4.81486093200704e-16)
  558. **Options**
  559. :func:`~mpmath.invertlaplace` recognizes the following optional
  560. keywords valid for all methods:
  561. *method*
  562. Chooses numerical inverse Laplace transform algorithm
  563. (described below).
  564. *degree*
  565. Number of terms used in the approximation
  566. **Algorithms**
  567. Mpmath implements four numerical inverse Laplace transform
  568. algorithms, attributed to: Talbot, Stehfest, and de Hoog,
  569. Knight and Stokes. These can be selected by using
  570. *method='talbot'*, *method='stehfest'*, *method='dehoog'* or
  571. *method='cohen'* or by passing the classes *method=FixedTalbot*,
  572. *method=Stehfest*, *method=deHoog*, or *method=Cohen*. The functions
  573. :func:`~mpmath.invlaptalbot`, :func:`~mpmath.invlapstehfest`,
  574. :func:`~mpmath.invlapdehoog`, and :func:`~mpmath.invlapcohen`
  575. are also available as shortcuts.
  576. All four algorithms implement a heuristic balance between the
  577. requested precision and the precision used internally for the
  578. calculations. This has been tuned for a typical exponentially
  579. decaying function and precision up to few hundred decimal
  580. digits.
  581. The Laplace transform converts the variable time (i.e., along
  582. a line) into a parameter given by the right half of the
  583. complex `p`-plane. Singularities, poles, and branch cuts in
  584. the complex `p`-plane contain all the information regarding
  585. the time behavior of the corresponding function. Any numerical
  586. method must therefore sample `p`-plane "close enough" to the
  587. singularities to accurately characterize them, while not
  588. getting too close to have catastrophic cancellation, overflow,
  589. or underflow issues. Most significantly, if one or more of the
  590. singularities in the `p`-plane is not on the left side of the
  591. Bromwich contour, its effects will be left out of the computed
  592. solution, and the answer will be completely wrong.
  593. *Talbot*
  594. The fixed Talbot method is high accuracy and fast, but the
  595. method can catastrophically fail for certain classes of time-domain
  596. behavior, including a Heaviside step function for positive
  597. time (e.g., `H(t-2)`), or some oscillatory behaviors. The
  598. Talbot method usually has adjustable parameters, but the
  599. "fixed" variety implemented here does not. This method
  600. deforms the Bromwich integral contour in the shape of a
  601. parabola towards `-\infty`, which leads to problems
  602. when the solution has a decaying exponential in it (e.g., a
  603. Heaviside step function is equivalent to multiplying by a
  604. decaying exponential in Laplace space).
  605. *Stehfest*
  606. The Stehfest algorithm only uses abscissa along the real axis
  607. of the complex `p`-plane to estimate the time-domain
  608. function. Oscillatory time-domain functions have poles away
  609. from the real axis, so this method does not work well with
  610. oscillatory functions, especially high-frequency ones. This
  611. method also depends on summation of terms in a series that
  612. grows very large, and will have catastrophic cancellation
  613. during summation if the working precision is too low.
  614. *de Hoog et al.*
  615. The de Hoog, Knight, and Stokes method is essentially a
  616. Fourier-series quadrature-type approximation to the Bromwich
  617. contour integral, with non-linear series acceleration and an
  618. analytical expression for the remainder term. This method is
  619. typically one of the most robust. This method also involves the
  620. greatest amount of overhead, so it is typically the slowest of the
  621. four methods at high precision.
  622. *Cohen*
  623. The Cohen method is a trapezoidal rule approximation to the Bromwich
  624. contour integral, with linear acceleration for alternating
  625. series. This method is as robust as the de Hoog et al method and the
  626. fastest of the four methods at high precision, and is therefore the
  627. default method.
  628. **Singularities**
  629. All numerical inverse Laplace transform methods have problems
  630. at large time when the Laplace-space function has poles,
  631. singularities, or branch cuts to the right of the origin in
  632. the complex plane. For simple poles in `\bar{f}(p)` at the
  633. `p`-plane origin, the time function is constant in time (e.g.,
  634. `\mathcal{L}\left\lbrace 1 \right\rbrace=1/p` has a pole at
  635. `p=0`). A pole in `\bar{f}(p)` to the left of the origin is a
  636. decreasing function of time (e.g., `\mathcal{L}\left\lbrace
  637. e^{-t/2} \right\rbrace=1/(p+1/2)` has a pole at `p=-1/2`), and
  638. a pole to the right of the origin leads to an increasing
  639. function in time (e.g., `\mathcal{L}\left\lbrace t e^{t/4}
  640. \right\rbrace = 1/(p-1/4)^2` has a pole at `p=1/4`). When
  641. singularities occur off the real `p` axis, the time-domain
  642. function is oscillatory. For example `\mathcal{L}\left\lbrace
  643. \mathrm{J}_0(t) \right\rbrace=1/\sqrt{p^2+1}` has a branch cut
  644. starting at `p=j=\sqrt{-1}` and is a decaying oscillatory
  645. function, This range of behaviors is illustrated in Duffy [3]
  646. Figure 4.10.4, p. 228.
  647. In general as `p \rightarrow \infty` `t \rightarrow 0` and
  648. vice-versa. All numerical inverse Laplace transform methods
  649. require their abscissa to shift closer to the origin for
  650. larger times. If the abscissa shift left of the rightmost
  651. singularity in the Laplace domain, the answer will be
  652. completely wrong (the effect of singularities to the right of
  653. the Bromwich contour are not included in the results).
  654. For example, the following exponentially growing function has
  655. a pole at `p=3`:
  656. .. math ::
  657. \bar{f}(p)=\frac{1}{p^2-9}
  658. .. math ::
  659. f(t)=\frac{1}{3}\sinh 3t
  660. >>> mp.dps = 15; mp.pretty = True
  661. >>> fp = lambda p: 1/(p*p-9)
  662. >>> ft = lambda t: sinh(3*t)/3
  663. >>> tt = [0.01,0.1,1.0,10.0]
  664. >>> ft(tt[0]),invertlaplace(fp,tt[0],method='talbot')
  665. (0.0100015000675014, 0.0100015000675014)
  666. >>> ft(tt[1]),invertlaplace(fp,tt[1],method='talbot')
  667. (0.101506764482381, 0.101506764482381)
  668. >>> ft(tt[2]),invertlaplace(fp,tt[2],method='talbot')
  669. (3.33929164246997, 3.33929164246997)
  670. >>> ft(tt[3]),invertlaplace(fp,tt[3],method='talbot')
  671. (1781079096920.74, -1.61331069624091e-14)
  672. **References**
  673. 1. [DLMF]_ section 1.14 (http://dlmf.nist.gov/1.14T4)
  674. 2. Cohen, A.M. (2007). Numerical Methods for Laplace Transform
  675. Inversion, Springer.
  676. 3. Duffy, D.G. (1998). Advanced Engineering Mathematics, CRC Press.
  677. **Numerical Inverse Laplace Transform Reviews**
  678. 1. Bellman, R., R.E. Kalaba, J.A. Lockett (1966). *Numerical
  679. inversion of the Laplace transform: Applications to Biology,
  680. Economics, Engineering, and Physics*. Elsevier.
  681. 2. Davies, B., B. Martin (1979). Numerical inversion of the
  682. Laplace transform: a survey and comparison of methods. *Journal
  683. of Computational Physics* 33:1-32,
  684. http://dx.doi.org/10.1016/0021-9991(79)90025-1
  685. 3. Duffy, D.G. (1993). On the numerical inversion of Laplace
  686. transforms: Comparison of three new methods on characteristic
  687. problems from applications. *ACM Transactions on Mathematical
  688. Software* 19(3):333-359, http://dx.doi.org/10.1145/155743.155788
  689. 4. Kuhlman, K.L., (2013). Review of Inverse Laplace Transform
  690. Algorithms for Laplace-Space Numerical Approaches, *Numerical
  691. Algorithms*, 63(2):339-355.
  692. http://dx.doi.org/10.1007/s11075-012-9625-3
  693. """
  694. rule = kwargs.get('method', 'cohen')
  695. if type(rule) is str:
  696. lrule = rule.lower()
  697. if lrule == 'talbot':
  698. rule = ctx._fixed_talbot
  699. elif lrule == 'stehfest':
  700. rule = ctx._stehfest
  701. elif lrule == 'dehoog':
  702. rule = ctx._de_hoog
  703. elif rule == 'cohen':
  704. rule = ctx._cohen
  705. else:
  706. raise ValueError("unknown invlap algorithm: %s" % rule)
  707. else:
  708. rule = rule(ctx)
  709. # determine the vector of Laplace-space parameter
  710. # needed for the requested method and desired time
  711. rule.calc_laplace_parameter(t, **kwargs)
  712. # compute the Laplace-space function evalutations
  713. # at the required abscissa.
  714. fp = [f(p) for p in rule.p]
  715. # compute the time-domain solution from the
  716. # Laplace-space function evaluations
  717. return rule.calc_time_domain_solution(fp, t)
  718. # shortcuts for the above function for specific methods
  719. def invlaptalbot(ctx, *args, **kwargs):
  720. kwargs['method'] = 'talbot'
  721. return ctx.invertlaplace(*args, **kwargs)
  722. def invlapstehfest(ctx, *args, **kwargs):
  723. kwargs['method'] = 'stehfest'
  724. return ctx.invertlaplace(*args, **kwargs)
  725. def invlapdehoog(ctx, *args, **kwargs):
  726. kwargs['method'] = 'dehoog'
  727. return ctx.invertlaplace(*args, **kwargs)
  728. def invlapcohen(ctx, *args, **kwargs):
  729. kwargs['method'] = 'cohen'
  730. return ctx.invertlaplace(*args, **kwargs)
  731. # ****************************************
  732. if __name__ == '__main__':
  733. import doctest
  734. doctest.testmod()