问题
I'm trying to write a Python program to use Tanh-sinh quadrature to compute the value of:
but although the program converges to a sensible value with no errors in every case, it's not converging to the correct value (which is pi for this particular integral) and I can't find the problem.
Instead of asking for a desired level of accuracy, the program asks for the number of function evaluations wanted, to make comparisons of convergence with simpler integration methods easier. The number of evaluations needs to be an odd number as the approximation used is
Can anyone suggest what I might have done wrong?
import math
def func(x):
# Function to be integrated, with singular points set = 0
if x == 1 or x == -1 :
return 0
else:
return 1 / math.sqrt(1 - x ** 2)
# Input number of evaluations
N = input("Please enter number of evaluations \n")
if N % 2 == 0:
print "The number of evaluations must be odd"
else:
print "N =", N
# Set step size
h = 2.0 / (N - 1)
print "h =", h
# k ranges from -(N-1)/2 to +(N-1)/2
k = -1 * ((N - 1) / 2.0)
k_max = ((N - 1) / 2.0)
sum = 0
# Loop across integration interval
while k < k_max + 1:
# Compute abscissa
x_k = math.tanh(math.pi * 0.5 * math.sinh(k * h))
# Compute weight
numerator = 0.5 * h * math.pi * math.cosh(k * h)
denominator = math.pow(math.cosh(0.5 * math.pi * math.sinh(k * h)),2)
w_k = numerator / denominator
sum += w_k * func(x_k)
k += 1
print "Integral =", sum
回答1:
For what it's worth, Scipy has numerical integration functions
For instance,
from scipy import integrate
check = integrate.quad(lambda x: 1 / math.sqrt(1 - x ** 2), -1, 1)
print 'Scipy quad integral = ', check
gives the result
Scipy quad integral = (3.141592653589591, 6.200897573194197e-10)
where the second number in the tuple is the absolute error.
That said, I was able to get your program to work with some tuning (although this is just an initial attempt):
1) Set the step size h to 0.0002 (roughly 1/2^12) as suggested by this paper
But note - the paper actually suggests altering the step size iteratively - with a fixed step size you will reach a point where the sinh or cosh grow too large for large enough values of kh. It would probably be better to attempt an implementation based on that paper's approach.
But sticking to the question at hand,
2) Make sure you set enough iterations for the integration to really converge , i.e. enough iterations that math.fabs(w_k * func(x_k)) < 1.0e-9
With these tunings, I was able to get the integration to converge to the correct value of pi to 4 significant figures using > 30000 iterations.
For instance with 31111 iterations, the value of pi computed was 3.14159256208
Example code with modifications (note I replaced sum with thesum, sum is the name of a Python built-in function):
import math
def func(x):
# Function to be integrated, with singular points set = 0
if x == 1 or x == -1 :
return 0
else:
return 1 / math.sqrt(1 - x ** 2)
# Input number of evaluations
N = input("Please enter number of evaluations \n")
if N % 2 == 0:
print "The number of evaluations must be odd"
else:
print "N =", N
# Set step size
#h = 2.0 / (N - 1)
h=0.0002 #(1/2^12)
print "h =", h
# k ranges from -(N-1)/2 to +(N-1)/2
k = -1 * ((N - 1) / 2.0)
k_max = ((N - 1) / 2.0)
thesum = 0
# Loop across integration interval
actual_iter =0
while k < k_max + 1:
# Compute abscissa
x_k = math.tanh(math.pi * 0.5 * math.sinh(k * h))
# Compute weight
numerator = 0.5 * h * math.pi * math.cosh(k * h)
dcosh = math.cosh(0.5 * math.pi * math.sinh(k * h))
denominator = dcosh*dcosh
#denominator = math.pow(math.cosh(0.5 * math.pi * math.sinh(k * h)),2)
w_k = numerator / denominator
thesum += w_k * func(x_k)
myepsilon = math.fabs(w_k * func(x_k))
if actual_iter%2000 ==0 and actual_iter > k_max/2:
print "Iteration = %d , myepsilon = %g"%(actual_iter,myepsilon)
k += 1
actual_iter += 1
print 'Actual iterations = ',actual_iter
print "Integral =", thesum
回答2:
Using the multiprecision library mpmath
:
from mpmath import *
mp.dps = 100
h = mpf(2**-12);
def weights(k):
num = mpf(0.5)*h*pi*cosh(k*h)
den = cosh(mpf(0.5)*pi*sinh(k*h))**2
return (num/den)
def abscissas(k):
return tanh(mpf(0.5)*pi*sinh(k*h))
def f(x):
return 1/sqrt(1 - mpf(x)**2)
N = 20000
result = 0
for k in range(-N, N+1):
result = result + weights(k)*f(abscissas(k))
print result - pi
gives for the error
-3.751800610920472803216259350430460844457732874052618682441090144344372471319795201134275503228835472e-45
回答3:
I think part of the problem might be due to range and step size. I've modified the code so you can put in the range and step size separately and rewritten some of the maths. It seems to give the right answers. Try for example 5 and 0.1 as inputs.
A particular problem is calculating math.cosh(0.5 * math.pi * math.sinh(k * h))
as k * h
get large math.sinh(k * h)
grows exponentially and calculating math.cosh of that can be hard.
import math
def func(x):
# return 1 # very simple test function
# Function to be integrated, with singular points set = 0
if x == 1 or x == -1 :
return 0
else:
return 1 / math.sqrt(1 - x ** 2)
# Input number of evaluations
N = input("Please enter max value for range \n")
print "N =", N
h = input("Please the step size \n")
print "h =", h
k = -N
k_max = N
sum = 0
count = 0
print "k ", k , " " , k_max
# Loop across integration interval
while k < k_max + 1:
# Compute abscissa
v = k
u = math.pi * 0.5 * math.sinh(v)
x_k = math.tanh(u)
#print u
# Compute weight
numerator = 0.5 * math.pi * math.cosh(v)
csh = math.cosh(u)
denominator = csh*csh
w_k = numerator / denominator
print k, v, x_k , w_k
sum += w_k * func(x_k)
count += 1
k += h # note changed
res = sum * h
print "Integral =", sum * h
回答4:
You have to realise that +1 and -1 are singular points of your integrand, f(x)-->+infinity
as x-->+1,-1
. As such, you can use your favourite quadrature formula away from the boundary points, but you have to work out a special quadrature based on a local expansion of f(x)
in a neighbourhood of them.
Sketch of the approach:
Pick some
epsilon<<1
.Decompose the integral
I
into smooth and singular parts:I_smooth
is the integral inside[-1+epsilon, 1-epsilon]
I_singular
are the integrals from[-1, -1+epsilon]
and[1-epsilon, 1]
.
Apply your standard quadrature rule inside the interval
[-1+epsilon, 1-epsilon]
to getI_smooth
.Perform a local expansion around the singular points (e.g. x=1):
f(x) = 1/sqrt(1-x) * (a0 + a1*(1-x) + a2*(1-x)^2 + ...) = f0(x-1) + f1(x-1) + f2(x-1) + ..
which is just a Taylor expansion about
x=1
off(x)*sqrt(1-x)
premultiplied by1/sqrt(1-x)
. (Unfortunately you have to do some math and work out the Taylor expansion unless you have Mathematica or you can find it tabulated somewhere.)Each single term
fn(x-1) = an*(1-x)^n/sqrt(1-x
) can be integrated exactly (it's just a power function). LetFn
be the integral offn
from1-epsilon
to1
. ApproximateI_singular = F0 + F1 + F2 + ...
up to the order you want.Finally:
I = I_smooth + I_singular
Note: to push accuracy you should not make epsilon
too small because the blow-up of the integral makes the problem numerically ill-conditioned, but rather increase the order of the Taylor expansion.
回答5:
There are a bunch of pitfalls when it comes to tanh-sinh quadrature, one being that the integrand needs to be evaluated very closely to the interval boundaries, at distances less than machine precision, e.g., 1.0 - 1.0e-20
in the original example. When this point is evaluated, it rounds to 1.0
at which f
has a singularity, and anything can happen. That's why you have you'll first have to transform the function such that the singularities sit at 0.
In the case of 1 / sqrt(1 - x**2)
, this is 1 / numpy.sqrt(-x**2 + 2*x)
for both the left and the right singularity. With quadpy (a project of mine), one then gets
import numpy
import quadpy
# def f(x):
# return 1 / numpy.sqrt(1 - x ** 2)
val, error_estimate = quadpy.line_segment.tanh_sinh_lr(
[lambda x: 1 / numpy.sqrt(-x**2 + 2*x)], # = 1 / sqrt(1 - (x-1)**2)
[lambda x: 1 / numpy.sqrt(-x**2 + 2*x)], # = 1 / sqrt(1 - (-(x-1))**2)
2, # length of the interval
1.0e-10
)
print(val, val - numpy.pi)
3.1415926533203944 -2.693987255497632e-10
来源:https://stackoverflow.com/questions/24986588/tanh-sinh-quadrature-numerical-integration-method-converging-to-wrong-value