5#ifndef LINE_API_LTI_LAPLACE_INVERT_H
6#define LINE_API_LTI_LAPLACE_INVERT_H
62using Cplx = std::complex<double>;
72inline double binom(std::size_t n, std::size_t k) {
73 if (k > n)
return 0.0;
75 for (std::size_t i = 0; i < k; ++i)
76 v = v *
static_cast<double>(n - i) /
static_cast<double>(i + 1);
84 std::vector<Cplx> a(n);
85 for (std::size_t i = 0; i < n; ++i)
86 a[i] =
Cplx(
static_cast<double>(n - 1) * std::log(10.0) / 6.0,
87 M_PI *
static_cast<double>(i));
99 if (n < 3)
throw InputError(
"euler_get_eta: at least three terms are required");
100 std::vector<double> res(n, 0.0);
102 for (std::size_t i = 1; i < (n + 1) / 2; ++i) res[i] = 1.0;
103 res[n - 1] = 1.0 / std::pow(2.0, (
static_cast<double>(n) - 1.0) / 2.0);
104 for (std::size_t i = 1; i < (n - 1) / 2; ++i)
105 res[n - i - 1] = res[n - i] + std::pow(2.0, (1.0 -
static_cast<double>(n)) / 2.0) *
106 ltidetail::binom((n - 1) / 2, i);
113 std::vector<Cplx> res(n);
114 const double scale = std::pow(10.0, (
static_cast<double>(n) - 1.0) / 6.0);
115 for (std::size_t i = 0; i < n; ++i)
116 res[i] =
Cplx(scale * ((i % 2 == 0) ? 1.0 : -1.0) * eta[i], 0.0);
122 if (n == 0)
throw InputError(
"talbot_get_alpha: at least one term is required");
123 std::vector<Cplx> a(n);
124 a[0] =
Cplx(2.0 *
static_cast<double>(n) / 5.0, 0.0);
125 for (std::size_t i = 1; i < n; ++i) {
126 const double th =
static_cast<double>(i) * M_PI /
static_cast<double>(n);
127 a[i] =
Cplx(2.0 *
static_cast<double>(i) * M_PI / 5.0 * (1.0 / std::tan(th)),
128 2.0 *
static_cast<double>(i) * M_PI / 5.0);
135 if (alpha.size() != n)
throw InputError(
"talbot_get_omega: alpha has the wrong length");
136 std::vector<Cplx> w(n);
137 w[0] = std::exp(alpha[0]) / 5.0;
138 for (std::size_t i = 1; i < n; ++i) {
139 const double th =
static_cast<double>(i) * M_PI /
static_cast<double>(n);
140 const double cot = 1.0 / std::tan(th);
141 const Cplx mult(1.0, th * (1.0 + cot * cot) - cot);
142 w[i] = 2.0 * std::exp(alpha[i]) / 5.0 * mult;
150 std::vector<double> a(n);
151 for (std::size_t k = 1; k <= n; ++k) a[k - 1] =
static_cast<double>(k) * std::log(2.0);
163 if (n == 0)
throw InputError(
"gaver_stehfest_get_omega: at least two terms are required");
164 const std::size_t h = n / 2;
166 for (std::size_t i = 2; i <= h; ++i) fact *= static_cast<double>(i);
168 std::vector<double> res(n, 0.0);
169 for (std::size_t k = 1; k <= n; ++k) {
171 for (std::size_t j = (k + 1) / 2; j <= std::min(k, h); ++j)
172 sum += std::pow(
static_cast<double>(j),
static_cast<double>(h + 1)) / fact *
173 ltidetail::binom(h, j) * ltidetail::binom(2 * j, j) *
174 ltidetail::binom(j, k - j);
175 res[k - 1] = (((h + k) % 2 == 0) ? 1.0 : -1.0) * std::log(2.0) *
sum;
202 if (!(t > 0.0))
throw InputError(
"laplace_invert_euler: t must be positive");
206 for (std::size_t i = 0; i < n; ++i) r += (w[i] * F(a[i] / t)).real();
212 if (!(t > 0.0))
throw InputError(
"laplace_invert_talbot: t must be positive");
213 if (n == 0)
throw InputError(
"laplace_invert_talbot: at least one term is required");
217 for (std::size_t i = 0; i < n; ++i) r += (w[i] * F(a[i] / t)).real();
228 std::size_t n = 12) {
229 if (!(t > 0.0))
throw InputError(
"laplace_invert_gaver_stehfest: t must be positive");
234 for (std::size_t i = 0; i < a.size(); ++i) r += w[i] * F(a[i] / t);
247 std::vector<double>
q;
270 double b = 1.0, std::size_t p0 = 200) {
271 if (!(b > 0.0))
throw InputError(
"laplace_weeks_coeffs: b must be positive");
272 if (p0 == 0)
throw InputError(
"laplace_weeks_coeffs: p0 must be positive");
273 const std::size_t N = 2 * p0;
274 const double r = std::pow(0.1, 4.0 /
static_cast<double>(p0));
275 const double twopi = 2.0 * 3.14159265358979323846;
277 std::vector<Cplx> Q(N);
278 for (std::size_t j = 0; j < N; ++j) {
279 const double u = twopi *
static_cast<double>(j) /
static_cast<double>(N);
280 const Cplx z = r *
Cplx(std::cos(u), std::sin(u));
281 const Cplx s = b * (
Cplx(1.0, 0.0) + z) / (2.0 * (
Cplx(1.0, 0.0) - z)) + b * sigma;
282 Q[j] = b / (
Cplx(1.0, 0.0) - z) * F(s);
285 std::vector<double> q(N, 0.0);
287 for (std::size_t n = 0; n < N; ++n) {
289 for (std::size_t j = 0; j < N; ++j) {
290 const double u = -twopi *
static_cast<double>(n) *
static_cast<double>(j) /
291 static_cast<double>(N);
292 acc += Q[j] *
Cplx(std::cos(u), std::sin(u));
294 q[n] = acc.real() /
static_cast<double>(N) / rpow;
312 double tol = 1e-10) {
318 if (std::abs(w.
q[p0]) <= tol && std::abs(w.
q[p0 + 1]) <= tol)
return w;
324 "laplace_weeks_scaling: no suitable scaling parameters were found for the "
325 "Laguerre inversion: the transform's density is not smooth enough for a "
326 "Laguerre series. Use the euler method instead.");
332namespace weeks_detail {
341inline std::size_t nterms(
const std::vector<double>& q) {
342 const std::size_t p0 = q.size() / 2;
343 for (std::size_t n = 1; n + 1 < p0; ++n)
344 if (std::abs(q[n]) <= 1e-13 && std::abs(q[n + 1]) <= 1e-13)
return n;
349inline std::vector<double> functions(
double t, std::size_t N) {
350 std::vector<double> l(N, 0.0);
351 if (N == 0)
return l;
352 l[0] = std::exp(-t / 2.0);
353 if (N > 1) l[1] = (1.0 - t) * l[0];
354 for (std::size_t n = 2; n < N; ++n) {
355 const double dn =
static_cast<double>(n);
356 l[n] = ((2.0 * dn - 1.0 - t) / dn) * l[n - 1] - ((dn - 1.0) / dn) * l[n - 2];
373 if (!(t > 0.0))
return 0.0;
374 const std::size_t n = weeks_detail::nterms(w.
q);
375 const std::vector<double> l = weeks_detail::functions(w.
b * t, n);
377 for (std::size_t i = 0; i < n; ++i) acc += w.
q[i] * l[i];
378 return std::exp(w.
sigma * w.
b * t) * acc;
393 if (s ==
"gaver-stehfest" || s ==
"gaver_stehfest" || s ==
"gaver")
397 throw InputError(
"laplace_invert: unknown method '" + s +
398 "', expected euler, talbot, gaver-stehfest, cme or weeks");
417 [&F](
double s) {
return F(
Cplx(s, 0.0)).real(); }, t, n ? n : 12);
419 std::vector<double> tv(1, t);
428 throw InputError(
"laplace_invert: unreachable method");
440 std::vector<double> out(t.size(), 0.0);
444 for (std::size_t i = 0; i < t.size(); ++i)
448 for (std::size_t i = 0; i < t.size(); ++i) {
449 if (!(t[i] > 0.0))
continue;
468 if (std::abs(s) < 1e-15)
return Cplx(1.0, 0.0);
471 std::vector<double> out(t.size(), 0.0);
474 for (std::size_t i = 0; i < t.size(); ++i) {
476 out[i] = std::min(1.0, std::max(0.0, v));
478 for (std::size_t i = 1; i < out.size(); ++i) out[i] = std::max(out[i], out[i - 1]);
481 for (std::size_t i = 0; i < t.size(); ++i) {
482 if (!(t[i] > 0.0))
continue;
484 if (v < 0.0) v = 0.0;
485 if (v > 1.0) v = 1.0;
488 for (std::size_t i = 1; i < out.size(); ++i) out[i] = std::max(out[i], out[i - 1]);
NumericError(const std::string &what)
The exception types the port throws.
Numerical inverse Laplace transform in the Abate-Whitt framework, the port of matlab/lib/thirdparty/i...
double laplace_invert_euler(const LaplaceFn &F, double t, std::size_t n=41)
Euler inversion of F at t.
std::vector< double > laplace_invert_cdf(const LaplaceFn &F, const std::vector< double > &t, LaplaceMethod method=LaplaceMethod::Euler, std::size_t n=0)
The DISTRIBUTION on a grid, from the transform of the DENSITY.
double laplace_invert(const LaplaceFn &F, double t, LaplaceMethod method=LaplaceMethod::Euler, std::size_t n=0)
Invert F at t by the named method.
std::function< double(double)> RealLaplaceFn
A transform that can only be evaluated on the real axis.
std::vector< Cplx > talbot_get_alpha(std::size_t n)
Talbot nodes: the cotangent contour, bending into the left half plane.
std::vector< double > laplace_invert_pdf(const LaplaceFn &F, const std::vector< double > &t, LaplaceMethod method=LaplaceMethod::Euler, std::size_t n=0)
The DENSITY on a grid: the inversion clamped at zero.
double laplace_invert_weeks(const WeeksParams &w, double t)
Invert by the Laguerre series f(t) = sum_n q_n l_n(t), recovered as exp(sigma*b*t) f_{sigma,...
double laplace_invert_talbot(const LaplaceFn &F, double t, std::size_t n=32)
Talbot inversion of F at t.
std::vector< double > gaver_stehfest_get_omega(std::size_t n)
Gaver-Stehfest weights.
std::vector< Cplx > talbot_get_omega(std::size_t n, const std::vector< Cplx > &alpha)
Talbot weights, which carry the contour's own derivative.
std::vector< Cplx > euler_get_omega(std::size_t n)
Euler weights: eta, alternating in sign, scaled by 10^((n-1)/6).
LaplaceMethod
The methods laplace_invert accepts.
std::complex< double > Cplx
std::vector< double > euler_get_eta(std::size_t n)
Euler weights before the alternating sign and the scale.
LaplaceMethod laplace_method(const std::string &s)
Parse the reference's method names, including its two Gaver spellings.
std::vector< double > gaver_stehfest_get_alpha(std::size_t n)
Gaver-Stehfest nodes: k log 2, on the REAL axis.
WeeksParams laplace_weeks_scaling(const LaplaceFn &F, std::size_t p0=200, double tol=1e-10)
The automatic (sigma, b) search of Fig.
std::function< Cplx(Cplx)> LaplaceFn
The transform, evaluated at complex argument.
std::vector< Cplx > euler_get_alpha(std::size_t n)
Euler nodes: a vertical line at Re = (n-1) log(10) / 6.
double laplace_invert_gaver_stehfest(const RealLaplaceFn &F, double t, std::size_t n=12)
Gaver-Stehfest inversion of F at t.
std::vector< double > laplace_weeks_coeffs(const LaplaceFn &F, double sigma=0.0, double b=1.0, std::size_t p0=200)
Laguerre coefficients q_n, n = 0..2*p0-1, of f_{sigma,b}(t) = exp(-sigma t) f(t/b),...
std::vector< double > matlab_ilt(const std::function< std::complex< double >(const std::complex< double > &)> &fun, const std::vector< double > ×, std::size_t maxFnEvals, IltMethod method=IltMethod::Cme)
Invert a Laplace transform at the requested time points.
A Laguerre expansion: the damping, the scaling and the coefficients.