Buckets:
MisterAI/LocalAI_Demo_backends / cpu-diffusers.upgrade-tmp /venv /lib /python3.10 /site-packages /mpmath /calculus /inverselaplace.py
| # contributed to mpmath by Kristopher L. Kuhlman, February 2017 | |
| # contributed to mpmath by Guillermo Navas-Palencia, February 2022 | |
| class InverseLaplaceTransform(object): | |
| r""" | |
| Inverse Laplace transform methods are implemented using this | |
| class, in order to simplify the code and provide a common | |
| infrastructure. | |
| Implement a custom inverse Laplace transform algorithm by | |
| subclassing :class:`InverseLaplaceTransform` and implementing the | |
| appropriate methods. The subclass can then be used by | |
| :func:`~mpmath.invertlaplace` by passing it as the *method* | |
| argument. | |
| """ | |
| def __init__(self, ctx): | |
| self.ctx = ctx | |
| def calc_laplace_parameter(self, t, **kwargs): | |
| r""" | |
| Determine the vector of Laplace parameter values needed for an | |
| algorithm, this will depend on the choice of algorithm (de | |
| Hoog is default), the algorithm-specific parameters passed (or | |
| default ones), and desired time. | |
| """ | |
| raise NotImplementedError | |
| def calc_time_domain_solution(self, fp): | |
| r""" | |
| Compute the time domain solution, after computing the | |
| Laplace-space function evaluations at the abscissa required | |
| for the algorithm. Abscissa computed for one algorithm are | |
| typically not useful for another algorithm. | |
| """ | |
| raise NotImplementedError | |
| class FixedTalbot(InverseLaplaceTransform): | |
| def calc_laplace_parameter(self, t, **kwargs): | |
| r"""The "fixed" Talbot method deforms the Bromwich contour towards | |
| `-\infty` in the shape of a parabola. Traditionally the Talbot | |
| algorithm has adjustable parameters, but the "fixed" version | |
| does not. The `r` parameter could be passed in as a parameter, | |
| if you want to override the default given by (Abate & Valko, | |
| 2004). | |
| The Laplace parameter is sampled along a parabola opening | |
| along the negative imaginary axis, with the base of the | |
| parabola along the real axis at | |
| `p=\frac{r}{t_\mathrm{max}}`. As the number of terms used in | |
| the approximation (degree) grows, the abscissa required for | |
| function evaluation tend towards `-\infty`, requiring high | |
| precision to prevent overflow. If any poles, branch cuts or | |
| other singularities exist such that the deformed Bromwich | |
| contour lies to the left of the singularity, the method will | |
| fail. | |
| **Optional arguments** | |
| :class:`~mpmath.calculus.inverselaplace.FixedTalbot.calc_laplace_parameter` | |
| recognizes the following keywords | |
| *tmax* | |
| maximum time associated with vector of times | |
| (typically just the time requested) | |
| *degree* | |
| integer order of approximation (M = number of terms) | |
| *r* | |
| abscissa for `p_0` (otherwise computed using rule | |
| of thumb `2M/5`) | |
| The working precision will be increased according to a rule of | |
| thumb. If 'degree' is not specified, the working precision and | |
| degree are chosen to hopefully achieve the dps of the calling | |
| context. If 'degree' is specified, the working precision is | |
| chosen to achieve maximum resulting precision for the | |
| specified degree. | |
| .. math :: | |
| p_0=\frac{r}{t} | |
| .. math :: | |
| p_i=\frac{i r \pi}{Mt_\mathrm{max}}\left[\cot\left( | |
| \frac{i\pi}{M}\right) + j \right] \qquad 1\le i <M | |
| where `j=\sqrt{-1}`, `r=2M/5`, and `t_\mathrm{max}` is the | |
| maximum specified time. | |
| """ | |
| # required | |
| # ------------------------------ | |
| # time of desired approximation | |
| self.t = self.ctx.convert(t) | |
| # optional | |
| # ------------------------------ | |
| # maximum time desired (used for scaling) default is requested | |
| # time. | |
| self.tmax = self.ctx.convert(kwargs.get('tmax', self.t)) | |
| # empirical relationships used here based on a linear fit of | |
| # requested and delivered dps for exponentially decaying time | |
| # functions for requested dps up to 512. | |
| if 'degree' in kwargs: | |
| self.degree = kwargs['degree'] | |
| self.dps_goal = self.degree | |
| else: | |
| self.dps_goal = int(1.72*self.ctx.dps) | |
| self.degree = max(12, int(1.38*self.dps_goal)) | |
| M = self.degree | |
| # this is adjusting the dps of the calling context hopefully | |
| # the caller doesn't monkey around with it between calling | |
| # this routine and calc_time_domain_solution() | |
| self.dps_orig = self.ctx.dps | |
| self.ctx.dps = self.dps_goal | |
| # Abate & Valko rule of thumb for r parameter | |
| self.r = kwargs.get('r', self.ctx.fraction(2, 5)*M) | |
| self.theta = self.ctx.linspace(0.0, self.ctx.pi, M+1) | |
| self.cot_theta = self.ctx.matrix(M, 1) | |
| self.cot_theta[0] = 0 # not used | |
| # all but time-dependent part of p | |
| self.delta = self.ctx.matrix(M, 1) | |
| self.delta[0] = self.r | |
| for i in range(1, M): | |
| self.cot_theta[i] = self.ctx.cot(self.theta[i]) | |
| self.delta[i] = self.r*self.theta[i]*(self.cot_theta[i] + 1j) | |
| self.p = self.ctx.matrix(M, 1) | |
| self.p = self.delta/self.tmax | |
| # NB: p is complex (mpc) | |
| def calc_time_domain_solution(self, fp, t, manual_prec=False): | |
| r"""The fixed Talbot time-domain solution is computed from the | |
| Laplace-space function evaluations using | |
| .. math :: | |
| f(t,M)=\frac{2}{5t}\sum_{k=0}^{M-1}\Re \left[ | |
| \gamma_k \bar{f}(p_k)\right] | |
| where | |
| .. math :: | |
| \gamma_0 = \frac{1}{2}e^{r}\bar{f}(p_0) | |
| .. math :: | |
| \gamma_k = e^{tp_k}\left\lbrace 1 + \frac{jk\pi}{M}\left[1 + | |
| \cot \left( \frac{k \pi}{M} \right)^2 \right] - j\cot\left( | |
| \frac{k \pi}{M}\right)\right \rbrace \qquad 1\le k<M. | |
| Again, `j=\sqrt{-1}`. | |
| Before calling this function, call | |
| :class:`~mpmath.calculus.inverselaplace.FixedTalbot.calc_laplace_parameter` | |
| to set the parameters and compute the required coefficients. | |
| **References** | |
| 1. Abate, J., P. Valko (2004). Multi-precision Laplace | |
| transform inversion. *International Journal for Numerical | |
| Methods in Engineering* 60:979-993, | |
| http://dx.doi.org/10.1002/nme.995 | |
| 2. Talbot, A. (1979). The accurate numerical inversion of | |
| Laplace transforms. *IMA Journal of Applied Mathematics* | |
| 23(1):97, http://dx.doi.org/10.1093/imamat/23.1.97 | |
| """ | |
| # required | |
| # ------------------------------ | |
| self.t = self.ctx.convert(t) | |
| # assume fp was computed from p matrix returned from | |
| # calc_laplace_parameter(), so is already a list or matrix of | |
| # mpmath 'mpc' types | |
| # these were computed in previous call to | |
| # calc_laplace_parameter() | |
| theta = self.theta | |
| delta = self.delta | |
| M = self.degree | |
| p = self.p | |
| r = self.r | |
| ans = self.ctx.matrix(M, 1) | |
| ans[0] = self.ctx.exp(delta[0])*fp[0]/2 | |
| for i in range(1, M): | |
| ans[i] = self.ctx.exp(delta[i])*fp[i]*( | |
| 1 + 1j*theta[i]*(1 + self.cot_theta[i]**2) - | |
| 1j*self.cot_theta[i]) | |
| result = self.ctx.fraction(2, 5)*self.ctx.fsum(ans)/self.t | |
| # setting dps back to value when calc_laplace_parameter was | |
| # called, unless flag is set. | |
| if not manual_prec: | |
| self.ctx.dps = self.dps_orig | |
| return result.real | |
| # **************************************** | |
| class Stehfest(InverseLaplaceTransform): | |
| def calc_laplace_parameter(self, t, **kwargs): | |
| r""" | |
| The Gaver-Stehfest method is a discrete approximation of the | |
| Widder-Post inversion algorithm, rather than a direct | |
| approximation of the Bromwich contour integral. | |
| The method abscissa along the real axis, and therefore has | |
| issues inverting oscillatory functions (which have poles in | |
| pairs away from the real axis). | |
| The working precision will be increased according to a rule of | |
| thumb. If 'degree' is not specified, the working precision and | |
| degree are chosen to hopefully achieve the dps of the calling | |
| context. If 'degree' is specified, the working precision is | |
| chosen to achieve maximum resulting precision for the | |
| specified degree. | |
| .. math :: | |
| p_k = \frac{k \log 2}{t} \qquad 1 \le k \le M | |
| """ | |
| # required | |
| # ------------------------------ | |
| # time of desired approximation | |
| self.t = self.ctx.convert(t) | |
| # optional | |
| # ------------------------------ | |
| # empirical relationships used here based on a linear fit of | |
| # requested and delivered dps for exponentially decaying time | |
| # functions for requested dps up to 512. | |
| if 'degree' in kwargs: | |
| self.degree = kwargs['degree'] | |
| self.dps_goal = int(1.38*self.degree) | |
| else: | |
| self.dps_goal = int(2.93*self.ctx.dps) | |
| self.degree = max(16, self.dps_goal) | |
| # _coeff routine requires even degree | |
| if self.degree % 2 > 0: | |
| self.degree += 1 | |
| M = self.degree | |
| # this is adjusting the dps of the calling context | |
| # hopefully the caller doesn't monkey around with it | |
| # between calling this routine and calc_time_domain_solution() | |
| self.dps_orig = self.ctx.dps | |
| self.ctx.dps = self.dps_goal | |
| self.V = self._coeff() | |
| self.p = self.ctx.matrix(self.ctx.arange(1, M+1))*self.ctx.ln2/self.t | |
| # NB: p is real (mpf) | |
| def _coeff(self): | |
| r"""Salzer summation weights (aka, "Stehfest coefficients") | |
| only depend on the approximation order (M) and the precision""" | |
| M = self.degree | |
| M2 = int(M/2) # checked earlier that M is even | |
| V = self.ctx.matrix(M, 1) | |
| # Salzer summation weights | |
| # get very large in magnitude and oscillate in sign, | |
| # if the precision is not high enough, there will be | |
| # catastrophic cancellation | |
| for k in range(1, M+1): | |
| z = self.ctx.matrix(min(k, M2)+1, 1) | |
| for j in range(int((k+1)/2), min(k, M2)+1): | |
| z[j] = (self.ctx.power(j, M2)*self.ctx.fac(2*j)/ | |
| (self.ctx.fac(M2-j)*self.ctx.fac(j)* | |
| self.ctx.fac(j-1)*self.ctx.fac(k-j)* | |
| self.ctx.fac(2*j-k))) | |
| V[k-1] = self.ctx.power(-1, k+M2)*self.ctx.fsum(z) | |
| return V | |
| def calc_time_domain_solution(self, fp, t, manual_prec=False): | |
| r"""Compute time-domain Stehfest algorithm solution. | |
| .. math :: | |
| f(t,M) = \frac{\log 2}{t} \sum_{k=1}^{M} V_k \bar{f}\left( | |
| p_k \right) | |
| where | |
| .. math :: | |
| V_k = (-1)^{k + N/2} \sum^{\min(k,N/2)}_{i=\lfloor(k+1)/2 \rfloor} | |
| \frac{i^{\frac{N}{2}}(2i)!}{\left(\frac{N}{2}-i \right)! \, i! \, | |
| \left(i-1 \right)! \, \left(k-i\right)! \, \left(2i-k \right)!} | |
| As the degree increases, the abscissa (`p_k`) only increase | |
| linearly towards `\infty`, but the Stehfest coefficients | |
| (`V_k`) alternate in sign and increase rapidly in sign, | |
| requiring high precision to prevent overflow or loss of | |
| significance when evaluating the sum. | |
| **References** | |
| 1. Widder, D. (1941). *The Laplace Transform*. Princeton. | |
| 2. Stehfest, H. (1970). Algorithm 368: numerical inversion of | |
| Laplace transforms. *Communications of the ACM* 13(1):47-49, | |
| http://dx.doi.org/10.1145/361953.361969 | |
| """ | |
| # required | |
| self.t = self.ctx.convert(t) | |
| # assume fp was computed from p matrix returned from | |
| # calc_laplace_parameter(), so is already | |
| # a list or matrix of mpmath 'mpf' types | |
| result = self.ctx.fdot(self.V, fp)*self.ctx.ln2/self.t | |
| # setting dps back to value when calc_laplace_parameter was called | |
| if not manual_prec: | |
| self.ctx.dps = self.dps_orig | |
| # ignore any small imaginary part | |
| return result.real | |
| # **************************************** | |
| class deHoog(InverseLaplaceTransform): | |
| def calc_laplace_parameter(self, t, **kwargs): | |
| r"""the de Hoog, Knight & Stokes algorithm is an | |
| accelerated form of the Fourier series numerical | |
| inverse Laplace transform algorithms. | |
| .. math :: | |
| p_k = \gamma + \frac{jk}{T} \qquad 0 \le k < 2M+1 | |
| where | |
| .. math :: | |
| \gamma = \alpha - \frac{\log \mathrm{tol}}{2T}, | |
| `j=\sqrt{-1}`, `T = 2t_\mathrm{max}` is a scaled time, | |
| `\alpha=10^{-\mathrm{dps\_goal}}` is the real part of the | |
| rightmost pole or singularity, which is chosen based on the | |
| desired accuracy (assuming the rightmost singularity is 0), | |
| and `\mathrm{tol}=10\alpha` is the desired tolerance, which is | |
| chosen in relation to `\alpha`.` | |
| When increasing the degree, the abscissa increase towards | |
| `j\infty`, but more slowly than the fixed Talbot | |
| algorithm. The de Hoog et al. algorithm typically does better | |
| with oscillatory functions of time, and less well-behaved | |
| functions. The method tends to be slower than the Talbot and | |
| Stehfest algorithsm, especially so at very high precision | |
| (e.g., `>500` digits precision). | |
| """ | |
| # required | |
| # ------------------------------ | |
| self.t = self.ctx.convert(t) | |
| # optional | |
| # ------------------------------ | |
| self.tmax = kwargs.get('tmax', self.t) | |
| # empirical relationships used here based on a linear fit of | |
| # requested and delivered dps for exponentially decaying time | |
| # functions for requested dps up to 512. | |
| if 'degree' in kwargs: | |
| self.degree = kwargs['degree'] | |
| self.dps_goal = int(1.38*self.degree) | |
| else: | |
| self.dps_goal = int(self.ctx.dps*1.36) | |
| self.degree = max(10, self.dps_goal) | |
| # 2*M+1 terms in approximation | |
| M = self.degree | |
| # adjust alpha component of abscissa of convergence for higher | |
| # precision | |
| tmp = self.ctx.power(10.0, -self.dps_goal) | |
| self.alpha = self.ctx.convert(kwargs.get('alpha', tmp)) | |
| # desired tolerance (here simply related to alpha) | |
| self.tol = self.ctx.convert(kwargs.get('tol', self.alpha*10.0)) | |
| self.np = 2*self.degree+1 # number of terms in approximation | |
| # this is adjusting the dps of the calling context | |
| # hopefully the caller doesn't monkey around with it | |
| # between calling this routine and calc_time_domain_solution() | |
| self.dps_orig = self.ctx.dps | |
| self.ctx.dps = self.dps_goal | |
| # scaling factor (likely tun-able, but 2 is typical) | |
| self.scale = kwargs.get('scale', 2) | |
| self.T = self.ctx.convert(kwargs.get('T', self.scale*self.tmax)) | |
| self.p = self.ctx.matrix(2*M+1, 1) | |
| self.gamma = self.alpha - self.ctx.log(self.tol)/(self.scale*self.T) | |
| self.p = (self.gamma + self.ctx.pi* | |
| self.ctx.matrix(self.ctx.arange(self.np))/self.T*1j) | |
| # NB: p is complex (mpc) | |
| def calc_time_domain_solution(self, fp, t, manual_prec=False): | |
| r"""Calculate time-domain solution for | |
| de Hoog, Knight & Stokes algorithm. | |
| The un-accelerated Fourier series approach is: | |
| .. math :: | |
| f(t,2M+1) = \frac{e^{\gamma t}}{T} \sum_{k=0}^{2M}{}^{'} | |
| \Re\left[\bar{f}\left( p_k \right) | |
| e^{i\pi t/T} \right], | |
| where the prime on the summation indicates the first term is halved. | |
| This simplistic approach requires so many function evaluations | |
| that it is not practical. Non-linear acceleration is | |
| accomplished via Pade-approximation and an analytic expression | |
| for the remainder of the continued fraction. See the original | |
| paper (reference 2 below) a detailed description of the | |
| numerical approach. | |
| **References** | |
| 1. Davies, B. (2005). *Integral Transforms and their | |
| Applications*, Third Edition. Springer. | |
| 2. de Hoog, F., J. Knight, A. Stokes (1982). An improved | |
| method for numerical inversion of Laplace transforms. *SIAM | |
| Journal of Scientific and Statistical Computing* 3:357-366, | |
| http://dx.doi.org/10.1137/0903022 | |
| """ | |
| M = self.degree | |
| np = self.np | |
| T = self.T | |
| self.t = self.ctx.convert(t) | |
| # would it be useful to try re-using | |
| # space between e&q and A&B? | |
| e = self.ctx.zeros(np, M+1) | |
| q = self.ctx.matrix(2*M, M) | |
| d = self.ctx.matrix(np, 1) | |
| A = self.ctx.zeros(np+1, 1) | |
| B = self.ctx.ones(np+1, 1) | |
| # initialize Q-D table | |
| e[:, 0] = 0.0 + 0j | |
| q[0, 0] = fp[1]/(fp[0]/2) | |
| for i in range(1, 2*M): | |
| q[i, 0] = fp[i+1]/fp[i] | |
| # rhombus rule for filling triangular Q-D table (e & q) | |
| for r in range(1, M+1): | |
| # start with e, column 1, 0:2*M-2 | |
| mr = 2*(M-r) + 1 | |
| e[0:mr, r] = q[1:mr+1, r-1] - q[0:mr, r-1] + e[1:mr+1, r-1] | |
| if not r == M: | |
| rq = r+1 | |
| mr = 2*(M-rq)+1 + 2 | |
| for i in range(mr): | |
| q[i, rq-1] = q[i+1, rq-2]*e[i+1, rq-1]/e[i, rq-1] | |
| # build up continued fraction coefficients (d) | |
| d[0] = fp[0]/2 | |
| for r in range(1, M+1): | |
| d[2*r-1] = -q[0, r-1] # even terms | |
| d[2*r] = -e[0, r] # odd terms | |
| # seed A and B for recurrence | |
| A[0] = 0.0 + 0.0j | |
| A[1] = d[0] | |
| B[0:2] = 1.0 + 0.0j | |
| # base of the power series | |
| z = self.ctx.expjpi(self.t/T) # i*pi is already in fcn | |
| # coefficients of Pade approximation (A & B) | |
| # using recurrence for all but last term | |
| for i in range(1, 2*M): | |
| A[i+1] = A[i] + d[i]*A[i-1]*z | |
| B[i+1] = B[i] + d[i]*B[i-1]*z | |
| # "improved remainder" to continued fraction | |
| brem = (1 + (d[2*M-1] - d[2*M])*z)/2 | |
| # powm1(x,y) computes x^y - 1 more accurately near zero | |
| rem = brem*self.ctx.powm1(1 + d[2*M]*z/brem, | |
| self.ctx.fraction(1, 2)) | |
| # last term of recurrence using new remainder | |
| A[np] = A[2*M] + rem*A[2*M-1] | |
| B[np] = B[2*M] + rem*B[2*M-1] | |
| # diagonal Pade approximation | |
| # F=A/B represents accelerated trapezoid rule | |
| result = self.ctx.exp(self.gamma*self.t)/T*(A[np]/B[np]).real | |
| # setting dps back to value when calc_laplace_parameter was called | |
| if not manual_prec: | |
| self.ctx.dps = self.dps_orig | |
| return result | |
| # **************************************** | |
| class Cohen(InverseLaplaceTransform): | |
| def calc_laplace_parameter(self, t, **kwargs): | |
| r"""The Cohen algorithm accelerates the convergence of the nearly | |
| alternating series resulting from the application of the trapezoidal | |
| rule to the Bromwich contour inversion integral. | |
| .. math :: | |
| p_k = \frac{\gamma}{2 t} + \frac{\pi i k}{t} \qquad 0 \le k < M | |
| where | |
| .. math :: | |
| \gamma = \frac{2}{3} (d + \log(10) + \log(2 t)), | |
| `d = \mathrm{dps\_goal}`, which is chosen based on the desired | |
| accuracy using the method developed in [1] to improve numerical | |
| stability. The Cohen algorithm shows robustness similar to the de Hoog | |
| et al. algorithm, but it is faster than the fixed Talbot algorithm. | |
| **Optional arguments** | |
| *degree* | |
| integer order of the approximation (M = number of terms) | |
| *alpha* | |
| abscissa for `p_0` (controls the discretization error) | |
| The working precision will be increased according to a rule of | |
| thumb. If 'degree' is not specified, the working precision and | |
| degree are chosen to hopefully achieve the dps of the calling | |
| context. If 'degree' is specified, the working precision is | |
| chosen to achieve maximum resulting precision for the | |
| specified degree. | |
| **References** | |
| 1. P. Glasserman, J. Ruiz-Mata (2006). Computing the credit loss | |
| distribution in the Gaussian copula model: a comparison of methods. | |
| *Journal of Credit Risk* 2(4):33-66, 10.21314/JCR.2006.057 | |
| """ | |
| self.t = self.ctx.convert(t) | |
| if 'degree' in kwargs: | |
| self.degree = kwargs['degree'] | |
| self.dps_goal = int(1.5 * self.degree) | |
| else: | |
| self.dps_goal = int(self.ctx.dps * 1.74) | |
| self.degree = max(22, int(1.31 * self.dps_goal)) | |
| M = self.degree + 1 | |
| # this is adjusting the dps of the calling context hopefully | |
| # the caller doesn't monkey around with it between calling | |
| # this routine and calc_time_domain_solution() | |
| self.dps_orig = self.ctx.dps | |
| self.ctx.dps = self.dps_goal | |
| ttwo = 2 * self.t | |
| tmp = self.ctx.dps * self.ctx.log(10) + self.ctx.log(ttwo) | |
| tmp = self.ctx.fraction(2, 3) * tmp | |
| self.alpha = self.ctx.convert(kwargs.get('alpha', tmp)) | |
| # all but time-dependent part of p | |
| a_t = self.alpha / ttwo | |
| p_t = self.ctx.pi * 1j / self.t | |
| self.p = self.ctx.matrix(M, 1) | |
| self.p[0] = a_t | |
| for i in range(1, M): | |
| self.p[i] = a_t + i * p_t | |
| def calc_time_domain_solution(self, fp, t, manual_prec=False): | |
| r"""Calculate time-domain solution for Cohen algorithm. | |
| The accelerated nearly alternating series is: | |
| .. math :: | |
| f(t, M) = \frac{e^{\gamma / 2}}{t} \left[\frac{1}{2} | |
| \Re\left(\bar{f}\left(\frac{\gamma}{2t}\right) \right) - | |
| \sum_{k=0}^{M-1}\frac{c_{M,k}}{d_M}\Re\left(\bar{f} | |
| \left(\frac{\gamma + 2(k+1) \pi i}{2t}\right)\right)\right], | |
| where coefficients `\frac{c_{M, k}}{d_M}` are described in [1]. | |
| 1. H. Cohen, F. Rodriguez Villegas, D. Zagier (2000). Convergence | |
| acceleration of alternating series. *Experiment. Math* 9(1):3-12 | |
| """ | |
| self.t = self.ctx.convert(t) | |
| n = self.degree | |
| M = n + 1 | |
| A = self.ctx.matrix(M, 1) | |
| for i in range(M): | |
| A[i] = fp[i].real | |
| d = (3 + self.ctx.sqrt(8)) ** n | |
| d = (d + 1 / d) / 2 | |
| b = -self.ctx.one | |
| c = -d | |
| s = 0 | |
| for k in range(n): | |
| c = b - c | |
| s = s + c * A[k + 1] | |
| b = 2 * (k + n) * (k - n) * b / ((2 * k + 1) * (k + self.ctx.one)) | |
| result = self.ctx.exp(self.alpha / 2) / self.t * (A[0] / 2 - s / d) | |
| # setting dps back to value when calc_laplace_parameter was | |
| # called, unless flag is set. | |
| if not manual_prec: | |
| self.ctx.dps = self.dps_orig | |
| return result | |
| # **************************************** | |
| class LaplaceTransformInversionMethods(object): | |
| def __init__(ctx, *args, **kwargs): | |
| ctx._fixed_talbot = FixedTalbot(ctx) | |
| ctx._stehfest = Stehfest(ctx) | |
| ctx._de_hoog = deHoog(ctx) | |
| ctx._cohen = Cohen(ctx) | |
| def invertlaplace(ctx, f, t, **kwargs): | |
| r"""Computes the numerical inverse Laplace transform for a | |
| Laplace-space function at a given time. The function being | |
| evaluated is assumed to be a real-valued function of time. | |
| The user must supply a Laplace-space function `\bar{f}(p)`, | |
| and a desired time at which to estimate the time-domain | |
| solution `f(t)`. | |
| A few basic examples of Laplace-space functions with known | |
| inverses (see references [1,2]) : | |
| .. math :: | |
| \mathcal{L}\left\lbrace f(t) \right\rbrace=\bar{f}(p) | |
| .. math :: | |
| \mathcal{L}^{-1}\left\lbrace \bar{f}(p) \right\rbrace = f(t) | |
| .. math :: | |
| \bar{f}(p) = \frac{1}{(p+1)^2} | |
| .. math :: | |
| f(t) = t e^{-t} | |
| >>> from mpmath import * | |
| >>> mp.dps = 15; mp.pretty = True | |
| >>> tt = [0.001, 0.01, 0.1, 1, 10] | |
| >>> fp = lambda p: 1/(p+1)**2 | |
| >>> ft = lambda t: t*exp(-t) | |
| >>> ft(tt[0]),ft(tt[0])-invertlaplace(fp,tt[0],method='talbot') | |
| (0.000999000499833375, 8.57923043561212e-20) | |
| >>> ft(tt[1]),ft(tt[1])-invertlaplace(fp,tt[1],method='talbot') | |
| (0.00990049833749168, 3.27007646698047e-19) | |
| >>> ft(tt[2]),ft(tt[2])-invertlaplace(fp,tt[2],method='talbot') | |
| (0.090483741803596, -1.75215800052168e-18) | |
| >>> ft(tt[3]),ft(tt[3])-invertlaplace(fp,tt[3],method='talbot') | |
| (0.367879441171442, 1.2428864009344e-17) | |
| >>> ft(tt[4]),ft(tt[4])-invertlaplace(fp,tt[4],method='talbot') | |
| (0.000453999297624849, 4.04513489306658e-20) | |
| The methods also work for higher precision: | |
| >>> mp.dps = 100; mp.pretty = True | |
| >>> nstr(ft(tt[0]),15),nstr(ft(tt[0])-invertlaplace(fp,tt[0],method='talbot'),15) | |
| ('0.000999000499833375', '-4.96868310693356e-105') | |
| >>> nstr(ft(tt[1]),15),nstr(ft(tt[1])-invertlaplace(fp,tt[1],method='talbot'),15) | |
| ('0.00990049833749168', '1.23032291513122e-104') | |
| .. math :: | |
| \bar{f}(p) = \frac{1}{p^2+1} | |
| .. math :: | |
| f(t) = \mathrm{J}_0(t) | |
| >>> mp.dps = 15; mp.pretty = True | |
| >>> fp = lambda p: 1/sqrt(p*p + 1) | |
| >>> ft = lambda t: besselj(0,t) | |
| >>> ft(tt[0]),ft(tt[0])-invertlaplace(fp,tt[0],method='dehoog') | |
| (0.999999750000016, -6.09717765032273e-18) | |
| >>> ft(tt[1]),ft(tt[1])-invertlaplace(fp,tt[1],method='dehoog') | |
| (0.99997500015625, -5.61756281076169e-17) | |
| .. math :: | |
| \bar{f}(p) = \frac{\log p}{p} | |
| .. math :: | |
| f(t) = -\gamma -\log t | |
| >>> mp.dps = 15; mp.pretty = True | |
| >>> fp = lambda p: log(p)/p | |
| >>> ft = lambda t: -euler-log(t) | |
| >>> ft(tt[0]),ft(tt[0])-invertlaplace(fp,tt[0],method='stehfest') | |
| (6.3305396140806, -1.92126634837863e-16) | |
| >>> ft(tt[1]),ft(tt[1])-invertlaplace(fp,tt[1],method='stehfest') | |
| (4.02795452108656, -4.81486093200704e-16) | |
| **Options** | |
| :func:`~mpmath.invertlaplace` recognizes the following optional | |
| keywords valid for all methods: | |
| *method* | |
| Chooses numerical inverse Laplace transform algorithm | |
| (described below). | |
| *degree* | |
| Number of terms used in the approximation | |
| **Algorithms** | |
| Mpmath implements four numerical inverse Laplace transform | |
| algorithms, attributed to: Talbot, Stehfest, and de Hoog, | |
| Knight and Stokes. These can be selected by using | |
| *method='talbot'*, *method='stehfest'*, *method='dehoog'* or | |
| *method='cohen'* or by passing the classes *method=FixedTalbot*, | |
| *method=Stehfest*, *method=deHoog*, or *method=Cohen*. The functions | |
| :func:`~mpmath.invlaptalbot`, :func:`~mpmath.invlapstehfest`, | |
| :func:`~mpmath.invlapdehoog`, and :func:`~mpmath.invlapcohen` | |
| are also available as shortcuts. | |
| All four algorithms implement a heuristic balance between the | |
| requested precision and the precision used internally for the | |
| calculations. This has been tuned for a typical exponentially | |
| decaying function and precision up to few hundred decimal | |
| digits. | |
| The Laplace transform converts the variable time (i.e., along | |
| a line) into a parameter given by the right half of the | |
| complex `p`-plane. Singularities, poles, and branch cuts in | |
| the complex `p`-plane contain all the information regarding | |
| the time behavior of the corresponding function. Any numerical | |
| method must therefore sample `p`-plane "close enough" to the | |
| singularities to accurately characterize them, while not | |
| getting too close to have catastrophic cancellation, overflow, | |
| or underflow issues. Most significantly, if one or more of the | |
| singularities in the `p`-plane is not on the left side of the | |
| Bromwich contour, its effects will be left out of the computed | |
| solution, and the answer will be completely wrong. | |
| *Talbot* | |
| The fixed Talbot method is high accuracy and fast, but the | |
| method can catastrophically fail for certain classes of time-domain | |
| behavior, including a Heaviside step function for positive | |
| time (e.g., `H(t-2)`), or some oscillatory behaviors. The | |
| Talbot method usually has adjustable parameters, but the | |
| "fixed" variety implemented here does not. This method | |
| deforms the Bromwich integral contour in the shape of a | |
| parabola towards `-\infty`, which leads to problems | |
| when the solution has a decaying exponential in it (e.g., a | |
| Heaviside step function is equivalent to multiplying by a | |
| decaying exponential in Laplace space). | |
| *Stehfest* | |
| The Stehfest algorithm only uses abscissa along the real axis | |
| of the complex `p`-plane to estimate the time-domain | |
| function. Oscillatory time-domain functions have poles away | |
| from the real axis, so this method does not work well with | |
| oscillatory functions, especially high-frequency ones. This | |
| method also depends on summation of terms in a series that | |
| grows very large, and will have catastrophic cancellation | |
| during summation if the working precision is too low. | |
| *de Hoog et al.* | |
| The de Hoog, Knight, and Stokes method is essentially a | |
| Fourier-series quadrature-type approximation to the Bromwich | |
| contour integral, with non-linear series acceleration and an | |
| analytical expression for the remainder term. This method is | |
| typically one of the most robust. This method also involves the | |
| greatest amount of overhead, so it is typically the slowest of the | |
| four methods at high precision. | |
| *Cohen* | |
| The Cohen method is a trapezoidal rule approximation to the Bromwich | |
| contour integral, with linear acceleration for alternating | |
| series. This method is as robust as the de Hoog et al method and the | |
| fastest of the four methods at high precision, and is therefore the | |
| default method. | |
| **Singularities** | |
| All numerical inverse Laplace transform methods have problems | |
| at large time when the Laplace-space function has poles, | |
| singularities, or branch cuts to the right of the origin in | |
| the complex plane. For simple poles in `\bar{f}(p)` at the | |
| `p`-plane origin, the time function is constant in time (e.g., | |
| `\mathcal{L}\left\lbrace 1 \right\rbrace=1/p` has a pole at | |
| `p=0`). A pole in `\bar{f}(p)` to the left of the origin is a | |
| decreasing function of time (e.g., `\mathcal{L}\left\lbrace | |
| e^{-t/2} \right\rbrace=1/(p+1/2)` has a pole at `p=-1/2`), and | |
| a pole to the right of the origin leads to an increasing | |
| function in time (e.g., `\mathcal{L}\left\lbrace t e^{t/4} | |
| \right\rbrace = 1/(p-1/4)^2` has a pole at `p=1/4`). When | |
| singularities occur off the real `p` axis, the time-domain | |
| function is oscillatory. For example `\mathcal{L}\left\lbrace | |
| \mathrm{J}_0(t) \right\rbrace=1/\sqrt{p^2+1}` has a branch cut | |
| starting at `p=j=\sqrt{-1}` and is a decaying oscillatory | |
| function, This range of behaviors is illustrated in Duffy [3] | |
| Figure 4.10.4, p. 228. | |
| In general as `p \rightarrow \infty` `t \rightarrow 0` and | |
| vice-versa. All numerical inverse Laplace transform methods | |
| require their abscissa to shift closer to the origin for | |
| larger times. If the abscissa shift left of the rightmost | |
| singularity in the Laplace domain, the answer will be | |
| completely wrong (the effect of singularities to the right of | |
| the Bromwich contour are not included in the results). | |
| For example, the following exponentially growing function has | |
| a pole at `p=3`: | |
| .. math :: | |
| \bar{f}(p)=\frac{1}{p^2-9} | |
| .. math :: | |
| f(t)=\frac{1}{3}\sinh 3t | |
| >>> mp.dps = 15; mp.pretty = True | |
| >>> fp = lambda p: 1/(p*p-9) | |
| >>> ft = lambda t: sinh(3*t)/3 | |
| >>> tt = [0.01,0.1,1.0,10.0] | |
| >>> ft(tt[0]),invertlaplace(fp,tt[0],method='talbot') | |
| (0.0100015000675014, 0.0100015000675014) | |
| >>> ft(tt[1]),invertlaplace(fp,tt[1],method='talbot') | |
| (0.101506764482381, 0.101506764482381) | |
| >>> ft(tt[2]),invertlaplace(fp,tt[2],method='talbot') | |
| (3.33929164246997, 3.33929164246997) | |
| >>> ft(tt[3]),invertlaplace(fp,tt[3],method='talbot') | |
| (1781079096920.74, -1.61331069624091e-14) | |
| **References** | |
| 1. [DLMF]_ section 1.14 (http://dlmf.nist.gov/1.14T4) | |
| 2. Cohen, A.M. (2007). Numerical Methods for Laplace Transform | |
| Inversion, Springer. | |
| 3. Duffy, D.G. (1998). Advanced Engineering Mathematics, CRC Press. | |
| **Numerical Inverse Laplace Transform Reviews** | |
| 1. Bellman, R., R.E. Kalaba, J.A. Lockett (1966). *Numerical | |
| inversion of the Laplace transform: Applications to Biology, | |
| Economics, Engineering, and Physics*. Elsevier. | |
| 2. Davies, B., B. Martin (1979). Numerical inversion of the | |
| Laplace transform: a survey and comparison of methods. *Journal | |
| of Computational Physics* 33:1-32, | |
| http://dx.doi.org/10.1016/0021-9991(79)90025-1 | |
| 3. Duffy, D.G. (1993). On the numerical inversion of Laplace | |
| transforms: Comparison of three new methods on characteristic | |
| problems from applications. *ACM Transactions on Mathematical | |
| Software* 19(3):333-359, http://dx.doi.org/10.1145/155743.155788 | |
| 4. Kuhlman, K.L., (2013). Review of Inverse Laplace Transform | |
| Algorithms for Laplace-Space Numerical Approaches, *Numerical | |
| Algorithms*, 63(2):339-355. | |
| http://dx.doi.org/10.1007/s11075-012-9625-3 | |
| """ | |
| rule = kwargs.get('method', 'cohen') | |
| if type(rule) is str: | |
| lrule = rule.lower() | |
| if lrule == 'talbot': | |
| rule = ctx._fixed_talbot | |
| elif lrule == 'stehfest': | |
| rule = ctx._stehfest | |
| elif lrule == 'dehoog': | |
| rule = ctx._de_hoog | |
| elif rule == 'cohen': | |
| rule = ctx._cohen | |
| else: | |
| raise ValueError("unknown invlap algorithm: %s" % rule) | |
| else: | |
| rule = rule(ctx) | |
| # determine the vector of Laplace-space parameter | |
| # needed for the requested method and desired time | |
| rule.calc_laplace_parameter(t, **kwargs) | |
| # compute the Laplace-space function evalutations | |
| # at the required abscissa. | |
| fp = [f(p) for p in rule.p] | |
| # compute the time-domain solution from the | |
| # Laplace-space function evaluations | |
| return rule.calc_time_domain_solution(fp, t) | |
| # shortcuts for the above function for specific methods | |
| def invlaptalbot(ctx, *args, **kwargs): | |
| kwargs['method'] = 'talbot' | |
| return ctx.invertlaplace(*args, **kwargs) | |
| def invlapstehfest(ctx, *args, **kwargs): | |
| kwargs['method'] = 'stehfest' | |
| return ctx.invertlaplace(*args, **kwargs) | |
| def invlapdehoog(ctx, *args, **kwargs): | |
| kwargs['method'] = 'dehoog' | |
| return ctx.invertlaplace(*args, **kwargs) | |
| def invlapcohen(ctx, *args, **kwargs): | |
| kwargs['method'] = 'cohen' | |
| return ctx.invertlaplace(*args, **kwargs) | |
| # **************************************** | |
| if __name__ == '__main__': | |
| import doctest | |
| doctest.testmod() | |
Xet Storage Details
- Size:
- 36.1 kB
- Xet hash:
- 751c056b0dd9ea321b8eac3022554872398a0c05c728897667d5e94fd05d7ef1
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.