O desenvolvimento da Fórmula de Exponencial Dupla (DE), também conhecida como Quadratura tanh-sinh, surge apartir da regra trapezoidal primitiva \[
\int_{-\infty}^{\infty} f(\xi) \, d\xi \approx h \sum_{k=-\infty}^{\infty} f(k h)
\] sendo \(h>0\). O erro de discretização é \(\mathcal{O}(\exp(-2\pi w/h))\) quando \(h \to 0\), sendo \(w\) a distância do eixo real para a singularidade mais próxima do integrando. Como a convergência é exponencial, dividindo \(h\) pela metade faz dobrar o número de dígitos corretos no resultado para valores suficientimente pequenos de \(h\)(Takahasi e Mori 1974; Bailey, Jeyabalan, e Li 2005; Vanherck, Sorée, e Magnus 2020; Engelen 2021).
Intervalos Finitos
Para aplicar tal método a um intervalo finito, faz-se um mapa de \((-\infty,\infty)\) para \([-1,1]\) usando uma função monótona\(\varphi(t)\), tal que
onde \(\dot{\varphi} \equiv d\varphi/dt\) é a Jacobiana da transformada. Usa-se uma transformada que force a derivada do integrando a zerar nos pontos extremos (finais). Pode-se escolher
Um intervalo arbitrário \([a,b]\) pode ser mapeado em \([-1,1]\) pela transformada linear \[
\xi = \sigma\,x+\gamma \\ \sigma=(b-a)/2 \\ \gamma = (a+b)/2,
\]
a qual dá origem à regra tanh-sinh, onde \(\eta\) é um parâmetro positivo. A Jacobiana \(\dot{g}(t)\) desta transformada tem decaimento duplo-exponencial para \(t \to \pm \infty\). É recomendado escolher \(\eta=1\) por conveniência, pois a transformada é, praticamente, insensível a valores de \(\eta\) próximos da unidade. Para um \(n\) fixo, recomenda-se escolher \(h \approx \log(\pi^2 n) / n\) o qual, para \(\eta = 1\), leva a um erro total da ordem de \(\mathcal{O}(\exp(-\pi^2 n / \log(\pi^2 n)))\).
Algo que torna este método de integração estranho é que ele não é exato para nenhum polinômio, nem sequer para uma constante, contudo, ele é notavelmente acurado. Outra característica deste método é que uma amostragem uniforme no eixo \(t\) leva a um agrupamento dos nós \(\xi_k\) perto dos pontos extremos do intervalo de integração, o qual, devido ao decaimento rápido dos pesos \(w_k\), permite uma supressão de qualquer singularidade nos pontos extremos de \(f(\xi)\).
Para estimar o erro, arbitra-se um tamanho inicial de malha \(h\), dobra-se o número de pontos a cada iteração (fazendo \(h_{k+1} = h_k / 2\)), pois, assim, pode-se reutilizar os pontos calculados na iteração anterior, até que se atinja a convergência especificada \(\epsilon\), isto é, são feitas sucessivas iterações \(k\) até que a condição seguinte seja satisfeita. Tipicamente, o erro real será \(\epsilon^2\) após a convergência.
Note que, devido à falta de simetria da transformada, o somatório não pode ser “dobrado” em \(k=0\) como para os interfalos finitos. Assim sendo, as somas parciais devem ser calculadas para \(k\) positivo e negativo, separadamente.
Códigos
Para testar o método, faremos as seguintes integrações procurando usando uma tolerância de \(1 \times 10^{-6}\) como critério de convergência.
O valor de \(y_1\) falhou em convergir para a tolerância desejada.
O código para realizar a integração numérica foi organizado em um módulo, disponível no Github.
module quadde_moduleuse, intrinsic:: ieee_arithmeticuse, intrinsic:: iso_fortran_env, only: wp => real64 ! working precisionimplicitnoneprivate! All entities are now module-private by defaultpublic quadde, integrable_function abstract interfacefunction integrable_function(x) result(y) import wpreal(wp), intent(in):: xcomplex(wp):: yend function integrable_functionend interfacecontains!> Double exponential quadrature of f(x) in the interval [a, b] with at most!> n levels (more than 6 usually leads to no improvement on convergence)!> with requested error eps.function quadde(f, a, b, n, eps) result(v)procedure(integrable_function) :: finteger:: n, k, modereal(wp):: a, b, c, d, e, h, eps, tol, t, r, x, w, u, eh, signcomplex(wp):: p, q, s, v, y, fp, fmlogical:: is_finiteif (eps <0.0) stop"eps must be positive"if (n <0) stop"n must be positive" tol =10.0* eps mode =0! tanh-sinh c =0.0 d =1.0sign=1.0 h =2.0if (b < a) then! swap bounds x = b b = a a = xsign=-1.0end ifif (ieee_is_finite(a) .and. ieee_is_finite(b)) then c = (a + b) *0.5 d = (b - a) *0.5 x = celse if (ieee_is_finite(a)) then mode =1! exp-sinh c = a x = a + delse if (ieee_is_finite(b)) then mode =1! exp-sinh d =-dsign=-sign c = b x = b + delse mode =2! sinh-sinh x =0.0end if s = f(x)do k =0, n p =0.0 fp =0.0 fm =0.0 h = h *0.5 eh =exp(h) t = ehif (k >0) eh = eh * ehif (mode ==0) then! tanh-sinhdo u =exp(1.0/ t - t) ! = exp(-2*sinh(j*h)) = 1/exp(sinh(j*h))^2 r =2.0* u / (1.0+ u) ! = 1 - tanh(sinh(j*h)) w = (t +1.0/ t) * r / (1.0+ u) ! = cosh(j*h) / cosh(sinh(j*h))^2 x = d * rif (a + x > a) then! too close to a then reuse previous fp y = f(a + x) is_finite = ieee_is_finite(real(y)) .and. ieee_is_finite(aimag(y))if (is_finite) fp = yend ifif (b - x < b) then! too close to b then reuse previous fm y = f(b - x) is_finite = ieee_is_finite(real(y)) .and. ieee_is_finite(aimag(y))if (is_finite) fm = yend if q = w * (fp + fm) p = p + q t = t * ehif (abs(q) < eps *abs(p)) exitend doelse t = t *0.5do r =exp(t -0.25/ t) ! exp(sinh(j*h)) w = r q =0.0if (mode ==1) then! exp-sinh x = c + d / rif (abs(x - c) <epsilon(x)) exit! x hit the finite endpoint y = f(x) is_finite = ieee_is_finite(real(y)) .and. ieee_is_finite(aimag(y))if (is_finite) q = q + y / welse! sinh-sinh r = (r -1.0/ r) *0.5! sinh(sinh(j*h)) w = (w +1.0/ w) *0.5! cosh(sinh(j*h)) x = c - d * r y = f(x)if (is_finite) q = q + y * wend if x = c + d * r y = f(x)if (is_finite) q = q + y * w q = q * (t +0.25/ t) ! cosh(j*h) p = p + q t = t * ehif (abs(q) <= eps *abs(p)) exitend doend if v = s - p s = s + pif (abs(v) <= tol *abs(s)) exitend do e =abs(v) / (abs(s) + eps) ! TODO return or print e v =sign* d * s * h ! result with estimated relative error eend function quaddeend module
program quadde_testuse quadde_moduleuse, intrinsic:: ieee_arithmeticuse, intrinsic:: iso_fortran_env, only: wp => real64 ! working precisionimplicitnonereal(wp), parameter:: tol =1e-6real(wp), parameter:: PI =3.141592653589793238462643383279502884197169399375105820974944592307816406286198_wpreal(wp):: POS_INF, NEG_INFcomplex(wp):: vallogical:: is_nan POS_INF = ieee_value(1.0_wp, ieee_positive_inf) NEG_INF = ieee_value(-1.0_wp, ieee_negative_inf)print*, "Testing quadde..." val = quadde(f1, -1.0_wp, 1.0_wp, 6, tol)print*, "Test 1:", val is_nan = ieee_is_nan(real(val)) .or. ieee_is_nan(aimag(val))if (abs(-1.9490- val) >1e-4.or. is_nan) stop"Test 1 failed" val = quadde(f2, -1.0_wp, 1.0_wp, 6, tol)print*, "Test 2:", val is_nan = ieee_is_nan(real(val)) .or. ieee_is_nan(aimag(val))if (abs(-0.69049- val) >1e-4.or. is_nan) stop"Test 2 failed" val = quadde(f3, 0.0_wp, POS_INF, 6, tol)print*, "Test 3:", val is_nan = ieee_is_nan(real(val)) .or. ieee_is_nan(aimag(val))if (abs(0.21938- val) >1e-4.or. is_nan) stop"Test 3 failed" val = quadde(f4, NEG_INF, POS_INF, 6, tol)print*, "Test 4:", val is_nan = ieee_is_nan(real(val)) .or. ieee_is_nan(aimag(val))if (abs(2.3962- val) >1e-4.or. is_nan) stop"Test 4 failed" val = quadde(f5, NEG_INF, POS_INF, 6, tol)print*, "Test 5:", val is_nan = ieee_is_nan(real(val)) .or. ieee_is_nan(aimag(val))if (abs(2.2214- val) >1e-4.or. is_nan) stop"Test 5 failed" val = quadde(f6, 0.1_wp, 1.0_wp, 6, tol)print*, "Test 6:", val is_nan = ieee_is_nan(real(val)) .or. ieee_is_nan(aimag(val))if (abs(9.0_wp- val) >1e-4.or. is_nan) stop"Test 6 failed"print*, "All tests passed!"contains! After contains, you can only define subroutines or functions.! You cannot put assignments, if statements, or any "straight-line" code there.function f1(x) result(y)real(wp), intent(in):: xcomplex(wp):: y y =1.0/ ( (x -2.0) * (1.0- x)**0.25* (1.0+ x)**0.75 )end functionfunction f2(x) result(y)real(wp), intent(in):: xcomplex(wp):: y y =cos(PI * x) /sqrt(1.0- x)end functionfunction f3(x) result(y)real(wp), intent(in):: xcomplex(wp):: y y =exp(-1.0- x) / (1.0+ x)end functionfunction f4(x) result(y)real(wp), intent(in):: xcomplex(wp):: y y =1.0/ (1.0+ x**2.0)**(5.0/4.0)end functionfunction f5(x) result(y)real(wp), intent(in):: xcomplex(wp):: y y =1.0/ (1.0+ x**4.0)end functionfunction f6(x) result(y)real(wp), intent(in):: xcomplex(wp):: y y = x**(-2.0)end functionend program quadde_test
Compilar estes códigos e rodar pode ser feito com os seguintes comandos no terminal:
gfortran-fPIC-c quadde_module.f90gfortran-fPIC-o test quadde_test.f90 quadde_module.o./test
Cuja saída é:
Testing quadde...
Test 1: (-1.9489702845825310,0.0000000000000000)
Test 2: (-0.69049458018520249,0.0000000000000000)
Test 3: (0.21938393436009282,0.0000000000000000)
Test 4: (2.3962804694707533,0.0000000000000000)
Test 5: (2.2214414406307439,0.0000000000000000)
Test 6: (8.9999999646063316,0.0000000000000000)
All tests passed!
O código C, também no Github foi adaptado do apresentado no anexo de Engelen (2021).
#include "stdio.h"#include "float.h"#include "math.h"// integrate function f, range a..b, max levels n, error tolerance epsdouble quadde(double(*f)(double),double a,double b,int n,double eps){constdouble tol =10*eps;double c =0, d =1, s, sign =1, e, v, h =2;int k =0, mode =0;// Tanh-Sinh = 0, Exp-Sinh = 1, Sinh-Sinh = 2if(b < a){// swap bounds v = b; b = a; a = v; sign =-1;}if(isfinite(a)&& isfinite(b)){ c =(a+b)/2; d =(b-a)/2; v = c;}elseif(isfinite(a)){ mode =1;// Exp-Sinh// alternatively d = exp_sinh_opt_d(f, a, eps, d); c = a; v = a+d;}elseif(isfinite(b)){ mode =1;// Exp-Sinh d =-d;// alternatively d = exp_sinh_opt_d(f, b, eps, -d); sign =-sign; c = b; v = b+d;}else{ mode =2;// Sinh-Sinh v =0;} s = f(v);do{double p =0, q, fp =0, fm =0, t, eh; h /=2; t = eh = exp(h);if(k >0) eh *= eh;if(mode ==0){// Tanh-Sinhdo{double u = exp(1/t-t);// = exp(-2*sinh(j*h)) = 1/exp(sinh(j*h))^2double r =2*u/(1+u);// = 1 - tanh(sinh(j*h))double w =(t+1/t)*r/(1+u);// = cosh(j*h)/cosh(sinh(j*h))^2double x = d*r;if(a+x > a){// if too close to a then reuse previous fpdouble y = f(a+x);if(isfinite(y)) fp = y;// if f(x) is finite, add to local sum}if(b-x < b){// if too close to a then reuse previous fpdouble y = f(b-x);if(isfinite(y)) fm = y;// if f(x) is finite, add to local sum} q = w*(fp+fm); p += q; t *= eh;}while(fabs(q)> eps*fabs(p));}else{ t /=2;do{double r = exp(t-.25/t);// = exp(sinh(j*h))double x, y, w = r; q =0;if(mode ==1){// Exp-Sinh x = c + d/r;if(fabs(x - c)< DBL_EPSILON)// if x hit the finite endpoint then breakbreak; y = f(x);if(isfinite(y))// if f(x) is finite, add to local sum q += y/w;}else{// Sinh-Sinh r =(r-1/r)/2;// = sinh(sinh(j*h)) w =(w+1/w)/2;// = cosh(sinh(j*h)) x = c - d*r; y = f(x);if(isfinite(y))// if f(x) is finite, add to local sum q += y*w;} x = c + d*r; y = f(x);if(isfinite(y))// if f(x) is finite, add to local sum q += y*w; q *= t+.25/t;// q *= cosh(j*h) p += q; t *= eh;}while(fabs(q)> eps*fabs(p));} v = s-p; s += p;++k;}while(fabs(v)> tol*fabs(s)&& k <= n); e = fabs(v)/(fabs(s)+eps);return sign*d*s*h;// result with estimated relative error e}double f1(double x){return1.0/((x -2.0)* pow(1.0- x,0.25)* pow(1.0+ x,0.75));}double f2(double x){return cos(M_PI * x)/ sqrt(1.0- x);}double f3(double x){return exp(-1.0- x)/(1.0+ x);}double f4(double x){return1.0/ pow(1.0+ pow(x,2.0),(5.0/4.0));}double f5(double x){return1.0/(1.0+ pow(x,4.0));}double f6(double x){return1.0/ pow(x,2.0);}int main(){double tol =1e-6;double val; val = quadde(f1,-1.0,1.0,6, tol); printf("Test 1: %.17g\n", val);if(fabs(-1.9490- val)>1e-4){ printf("Test 1 failed");return1;} val = quadde(f2,-1.0,1.0,6, tol); printf("Test 2: %.17g\n", val);if(fabs(-0.69049- val)>1e-4){return2;} val = quadde(f3,0.0, INFINITY,6, tol); printf("Test 3: %.17g\n", val);if(fabs(0.21938- val)>1e-4){return3;} val = quadde(f4,-INFINITY, INFINITY,6, tol); printf("Test 4: %.17g\n", val);if(fabs(2.3962- val)>1e-4){return4;} val = quadde(f5,-INFINITY, INFINITY,6, tol); printf("Test 5: %.17g\n", val);if(fabs(2.2214- val)>1e-4){return5;} val = quadde(f6,0.1,1.0,6, tol); printf("Test 6: %.17g\n", val);if(fabs(9.0- val)>1e-4){return6;} printf("All Tests passed!\n");return0;}
Pode-se compilar e rodar com os seguintes comandos no terminal:
gcc-o a.out quadde.c -lm&&./a.out
Com saída:
Test 1: -1.948970284582531
Test 2: -0.69049458018520249
Test 3: 0.21938393436009282
Test 4: 2.3962804694707533
Test 5: 2.2214414406307439
Test 6: 8.9999999646063316
All Tests passed!
Referências
Bailey, David H., Karthik Jeyabalan, e Xiaoye S. Li. 2005. «A Comparison of Three High-Precision Quadrature Schemes». Experimental Mathematics 14 (3): 317–29.
Engelen, Robert A. van. 2021. «Improving the Double Exponential Quadrature Tanh-Sinh, Sinh-Sinh and Exp-Sinh Formulas». Genivia, Inc. https://www.genivia.com/files/qthsh.pdf.
Takahasi, Hidetosi, e Masatake Mori. 1974. «Double Exponential Formulas for Numerical Integration». Publications of the Research Institute for Mathematical Sciences 9 (3): 721–41.
Vanherck, Joren, Bart Sorée, e Wim Magnus. 2020. «Tanh-sinh Quadrature for Single and Multiple Integration Using Floating-Point Arithmetic». arXiv. https://arxiv.org/abs/2007.15057.