Scipy: Integration of Hermite function with quadrature weights - python

I want to integrate the product of two time- and frequency-shifted Hermite functions using scipy.integrate.quad.
However, since large order-polynomials are included, there are numerical errors occuring. Here's my Code:
import numpy as np
import scipy.integrate
import scipy.special as sp
from math import pi
def makeFuncs():
# Create the 0th, 4th, 8th, 12th and 16th order hermite function
return [lambda t, n=n: np.exp(-0.5*t**2)*sp.hermite(n)(t) for n in np.arange(5)*4]
def ambgfun(funcs, i, k, tau, f):
# Integrate f1(t)*f2(t+tau)*exp(-j2pift) over t from -inf to inf
f1 = funcs[i]
f2 = funcs[k]
func = lambda t: np.real(f1(t) * f2(t+tau) * np.exp(-1j*(2*pi)*f*t))
return scipy.integrate.quad(func, -np.inf, np.inf)
def main():
f = makeFuncs()
print "A00(0,0):", ambgfun(f, 0, 0, 0, 0)
print "A01(0,0):", ambgfun(f, 0, 1, 0, 0)
print "A34(0,0):", ambgfun(f, 3, 4, 0, 0)
if __name__ == '__main__':
main()
The hermite functions are orthogonal, thus all integrals should be equal to zero. However, they are not, as the output shows:
A00(0,0): (1.7724538509055159, 1.4202636805184462e-08)
A01(0,0): (8.465450562766819e-16, 8.862237123626351e-09)
A34(0,0): (-10.1875, 26.317246925873935)
How can I make this calculation more accurate? The hermite-function from scipy contain a weights variable which should be used for Gaussian Quadrature, as given in the documentation (http://docs.scipy.org/doc/scipy/reference/special.html#orthogonal-polynomials). However, I have not found a hint in the docs how to use these weights.
I hope you can help :)
Thanks, Max

The answer is that the result you get is numerically as close to zero as it gets. I don' think it's really possible to get much better results if you work with floating point numbers --- you are facing a general problem in numerical integration.
Consider this:
import numpy as np
from scipy import integrate, special
f = lambda t: np.exp(-t**2) * special.eval_hermite(12, t) * special.eval_hermite(16, t)
abs_ig, abs_err = integrate.quad(lambda t: abs(f(t)), -np.inf, np.inf)
ig, err = integrate.quad(f, -np.inf, np.inf)
print ig
# -10.203125
print abs_ig
# 2.22488114805e+15
print ig / abs_ig, err / abs_ig
# -4.58591912155e-15 1.18053770382e-14
The value of the integrand has therefore been computed to an accuracy comparable to the floating point epsilon. Because of the rounding error in subtracting values of a large-magnitude oscillating integrand, it's not really possible to get better results.
So how to proceed? In my experience, what you'd need to do now is to approach the problem not numerically, but analytically. Importantly, the Fourier transform of Hermite polynomials times the weight function is known, so you can work in the Fourier space all the time here.

Related

is there a way to retrieve the nodes automatically computed by mpmath.quad integration routine?

I am trying to calculate an integral with mpmath.quad. I basically have to calculate three moments of a distribution, call it f (pseudocode):
integrate(f(x)/x, 0, infinity)
integrate(f(x)*x, 0, infinity)
integrate(f(x)ln(x)/x, 0, infinity)
As far as I understood, the tanh-sinh algorithm from mpmath applies a coordinate transformation to the integration interval, uses it to find suitable nodes xk-s and weights wk-s and returns (pseudocode):
sum f(xk)*wk
with N the number of nodes. Since in my original problem the calculation takes a long time, I was wishing to reuse the values of f calculated by the first integration for the other integrals, i.e., computing them from discrete samples with something like scipy.integrate.trapezoid or simpson. By employing a structure Simulator I simplified from an answer on this forum, I managed to cache the xk-s and f(xk)-s, but not the weights. I checked that the xk-s from different integrations are the same.
Now, if I naively apply a standard quadrature from the scipy module to my samples, say trapezoid(fs, xs), the result I get is different from that calculated by mpmath.quad, especially if the samples are few. While this fact does not surprise me, I would like to find out how to retrieve the weights mpmath.quad uses in the first calculation, say the one for f(x)/x, so I could avoid running the time consuming algorithm thrice.
I cannot understand the documentation as for this point. mpmath documentation gives a lot of examples, but none concerning the retrieval of calculated nodes, although it states several times the nodes are "cached". Where are they? mpmath.quad only returns the integral result!
So I would like to know: is what I'm trying to achieve sensible at all? And if it is, how could I accomplish the task?
Below is a code that reproduces the behaviour. Any help is very appreciated.
import numpy as np
import mpmath as mp
import matplotlib.pyplot as plt
from scipy.integrate import trapezoid, simpson
class Simulator:
def __init__(self, func, storex:np.ndarray=np.array([]), storef:np.ndarray=np.array([])):
self.func = func
self.storex = storex
self.storef = storef
def simulate(self, x, *args):
result = self.func(x, *args)
self.storex = np.append(self.storex, x)
self.storef = np.append(self.storef, result)
return result
def lorentz(x):
x = mp.mpf(x)
return mp.mpf(1)/(mp.power(x-mp.mpf(1), 2) + mp.mpf(1))
simratio = simproduct = Simulator(lorentz)
integralratio = mp.quad(lambda x: simratio.simulate(x)/x, [0, 1, mp.inf])
integralproduct = mp.quad(lambda x: simproduct.simulate(x)*x, [0, 1, mp.inf])
ratiox, ratioy = np.transpose(sorted([(x,y) for x, y in zip(simratio.storex, simratio.storef)])) #we get the sampled xs and f(x)s
integralproduct_trap = trapezoid(ratioy*ratiox, ratiox)
print("int[f(x)/x], quad: ", integralratio)
print("int[f(x)*x], quad: ", integralproduct)
print("int[f(x)*x], scipy.trapezoid: ", integralproduct_trap)

solving multiple non linear equation (with power function)

I would like to solve this kind of equations:
a*85**b+c=100
a*90**b+c=66
a*92**b+c=33
I tried this
import scipy.optimize
def fun(variables) :
(a,b,c)= variables
eq0=a*85**b+c-100
eq1=a*90**b+c-66
eq2=a*92**b+c-33
return [eq0,eq1,eq2]
result = scipy.optimize.fsolve(fun, (1, -1, 0))
print(result)
But I get ValueError: Integers to negative integer powers are not allowed.
Then I tried the equivalent
def fun(variables) :
(a,b,c)= variables
eq0=log(a)+b*log(85)-log(100-c)
eq1=log(a)+b*log(90)-log(66-c)
eq2=log(a)+b*log(92)-log(33-c)
return [eq0,eq1,eq2]
result = scipy.optimize.fsolve(fun, (1, -1, 0))
print(result)
I get a solution but that is equal to the initial values (1, -1, 0)
Thus when I test fun(result), I get values different from zero.
I have noticed that for this example the same problem is observed
import scipy.optimize
def fun(variables) :
(x,y)= variables
eqn_1 = x**2+y-4
eqn_2 = x+y**2+3
return [eqn_1,eqn_2]
result = scipy.optimize.fsolve(fun, (0.1, 1))
print(result)
fun(result)
Does anyone would know how I could do ? Thank you
PS I have posted here about sympy last week
Resolution of multiple equations (with exponential)
When the initial condition is not well known, sometimes its best to try other methods first . For small problems, simplex minimization is useful:
import numpy as np
def func(x):
a,b,c= x
eq0=np.log(a)+b*np.log(85)-np.log(100-c)
eq1=np.log(a)+b*np.log(90)-np.log(66-c)
eq2=np.log(a)+b*np.log(92)-np.log(33-c)
return eq0**2+ eq1**2 + eq2**2
def func_vec(x):
a,b,c= x
eq0=np.log(a)+b*np.log(85)-np.log(100-c)
eq1=np.log(a)+b*np.log(90)-np.log(66-c)
eq2=np.log(a)+b*np.log(92)-np.log(33-c)
return eq0, eq1, eq2
from scipy.optimize import fsolve, minimize
out = minimize(func, [1,1,0], method="Nelder-Mead", options={"maxfev":100000})
print("roots:", out.x)
print("value at roots:", func_vec(out.x))
# roots: [ 7.87002460e+11 -1.07401055e-09 -7.87002456e+11]
# value at roots: (6.0964566728216596e-12, -1.2086331935279304e-11, 6.235012506294879e-12)
Note, I also tried [1,1,1] as an initial condition and found it converged to the wrong solution. Further increasing maxfev from 1e5 to 1e7 allowed [1,1,1] to converged to the proper solution, but then perhaps there are better methods to solve this.

integration in python: y(t,E)=-A+2A*Integral [g(t,tau)*f(Ea)]dEa

I need to carry out the integral written in the figure:
I want to plot y(t,E) for a given value of E. In my code I get this message: "divide by zero encountered in double_scalars"
I don't understand in an integration process why would there a "divide by" come? What would be a better code to do this work?
My code:
from scipy.integrate import quad
import numpy as np
def integrand(Ea,A,t,E,tau0,n,alpha,mu,sig):
tau=tau0*np.exp(-np.power(E/Ea,alpha));
g=1-np.exp(-np.power((t/tau),n));
f=np.exp(-np.power(Ea - mu, 2.) / (2 * np.power(sig, 2.)));
return -A+2*A*g*f
#
A=25;
t=1e-6; #Calculating for one t value. For plotting, t would be an array.
E=1;
tau0=0.3*1e-6;
n=2;
alpha=5
mu=2;
sig=0.3;
Y = quad(integrand, 0, np.inf, args=(A,t,E,tau0,n,alpha,mu,sig))
print(Y)
I think you can safely ignore the warning. The integral is probably diverging at some point and the warning you get tells that the number of iterations fell short in finding a converged solution. If you print the error (y[1]), you will see that it is quite small as compared to the absolute value of the integral (y[0]). The dependence of the integral with time can be computed as following
time = np.logspace(-8, -5, 100)
Ylist = []
for t in time:
Y = quad(integrand, 0, np.inf, args=(A,t,E,tau0,n,alpha,mu,sig))
Ylist.append(Y[0])
plt.semilogx(time, Ylist, '-kx')
plt.xlabel('Time (log)')
plt.ylabel(r'$y(t, E)$')

Sympy extract Fourier Series coefficients

I am using sympy to carry on some symbolic math manipulations.
Start by creating a Fourier series representation of a rectangular pulse train (duty cycle < 50%), then try to access the multiplying factors, i.e. a_n and b_n of the standard Fourier series.
In a nutshell:
import sympy as sy
from sympy import fourier_series, pi, cos, sin
from sympy.abc import t
from sympy.functions.special.delta_functions import Heaviside
T = sy.symbols('T')
s = fourier_series(Heaviside(t) - Heaviside(t-1/4), (t, 0, 1))
s.truncate(3)
1/π*sin(2πt)+1/π*sin(4πt)+1/π*cos(2πt)+0.25
I would then like to access the coefficients of the base functions. To this extent, I thought I should use as_coefficient(expr).
This produces the expected results in a simpler case:
g = 1/(pi*T)*sin(2*pi*t)
g.as_coefficient(sin(2*pi*t))
1/πT
However, with the object returned by fourier_series, this does not seem to work:
a = s.truncate(3)
a.as_coefficient(sin(2*pi*t))
returns nothing (not even a warning or message).
Other methods like s.as_Add() or s.as_Mul() return both a full expression where the a_n is tied to its sin(2*pi*n*t) term (or b_n to its dual).
The class sympy.series.fourier.FourierSeries has methods to provide sympy.series.sequences.sequence objects with the cosine and sine terms of the series: a0, an and bn.
After computing the series by
import sympy as sym
from sympy import fourier_series
from sympy.abc import t
from sympy.functions.special.delta_functions import Heaviside
s = fourier_series(Heaviside(t) - Heaviside(t-1/4), (t, 0, 1))
The cosine coefficients can be obtained by
s.a0
0.25
and
s.an
.
For the sine coefficients
s.bn
.
So in order to produce lists of the s series coefficients up to a given order, let say 4 that can be done with
def cosine_fourier_coeffs(fourierSeries, order):
### returns a list of fourier series cosine coefficients up to order
out = []
out.append(fourierSeries.a0)
for i in range(1,order):
out.append(fourierSeries.an.coeff(i).subs(t, 0 ) )
return out
def sine_fourier_coeffs(fourierSeries, order):
### returns a list of fourier series sine coefficients up to order
out = []
for i in range(1,order):
out.append(fourierSeries.bn.coeff(i).subs(t, 1/(4* i) ) )
return out
cosine_fourier_coeffs(s, 4), sine_fourier_coeffs(s, 4)
that will return
([0.250000000000000, 1/pi, 0, -1/(3*pi)], [1/pi, 1/pi, 1/(3*pi)])
The method as_coefficient can't handle a sum of terms like 2*sin(x)+3*cos(x): it picks the coefficient only if the given expression (like sin(x)) can be factored out. So, in order to use it you need to separate the series into chunks with one trig function each. This can be done, but it's easier to change the approach:
Use s.truncate(None) to get a generator for the series.
For each term produced by the generator, plug 0 to get cosine coefficient, and plug 1/(4*k) of the interval length to get sine coefficient (here k is the index)
The reason this works: at 0, sine is 0 and cosine is 1; at 1/4 of length, cosine is 0 and sine is 1.
from sympy import fourier_series, pi, cos, sin
from sympy.abc import t
from sympy.functions.special.delta_functions import Heaviside
s = fourier_series(Heaviside(t) - Heaviside(t-1/4), (t, 0, 1))
iter = s.truncate(None)
cosine_coeffs = []
sine_coeffs = [0] # there is no sine term for k = 0
for k in range(0, 4):
term = next(iter)
cosine_coeffs.append(term.subs(t, 0))
if k > 0:
sine_coeffs.append(term.subs(t, 1/(4*k)))
Result:
cosine_coeffs = [0.250000000000000, 1/pi, 0, -1/(3*pi)]
sine_coeffs = [0, 1/pi, 1/pi, 1/(3*pi)]

Use scipy.integrate.quad to integrate complex numbers

I'm using right now the scipy.integrate.quad to successfully integrate some real integrands. Now a situation appeared that I need to integrate a complex integrand. quad seems not be able to do it, as the other scipy.integrate routines, so I ask: is there any way to integrate a complex integrand using scipy.integrate, without having to separate the integral in the real and the imaginary parts?
What's wrong with just separating it out into real and imaginary parts? scipy.integrate.quad requires the integrated function return floats (aka real numbers) for the algorithm it uses.
import scipy
from scipy.integrate import quad
def complex_quadrature(func, a, b, **kwargs):
def real_func(x):
return scipy.real(func(x))
def imag_func(x):
return scipy.imag(func(x))
real_integral = quad(real_func, a, b, **kwargs)
imag_integral = quad(imag_func, a, b, **kwargs)
return (real_integral[0] + 1j*imag_integral[0], real_integral[1:], imag_integral[1:])
E.g.,
>>> complex_quadrature(lambda x: (scipy.exp(1j*x)), 0,scipy.pi/2)
((0.99999999999999989+0.99999999999999989j),
(1.1102230246251564e-14,),
(1.1102230246251564e-14,))
which is what you expect to rounding error - integral of exp(i x) from 0, pi/2 is (1/i)(e^i pi/2 - e^0) = -i(i - 1) = 1 + i ~ (0.99999999999999989+0.99999999999999989j).
And for the record in case it isn't 100% clear to everyone, integration is a linear functional, meaning that ∫ { f(x) + k g(x) } dx = ∫ f(x) dx + k ∫ g(x) dx (where k is a constant with respect to x). Or for our specific case ∫ z(x) dx = ∫ Re z(x) dx + i ∫ Im z(x) dx as z(x) = Re z(x) + i Im z(x).
If you are trying to do a integration over a path in the complex plane (other than along the real axis) or region in the complex plane, you'll need a more sophisticated algorithm.
Note: Scipy.integrate will not directly handle complex integration. Why? It does the heavy lifting in the FORTRAN QUADPACK library, specifically in qagse.f which explicitly requires the functions/variables to be real before doing its "global adaptive quadrature based on 21-point Gauss–Kronrod quadrature within each subinterval, with acceleration by Peter Wynn's epsilon algorithm." So unless you want to try and modify the underlying FORTRAN to get it to handle complex numbers, compile it into a new library, you aren't going to get it to work.
If you really want to do the Gauss-Kronrod method with complex numbers in exactly one integration, look at wikipedias page and implement directly as done below (using 15-pt, 7-pt rule). Note, I memoize'd function to repeat common calls to the common variables (assuming function calls are slow as if the function is very complex). Also only did 7-pt and 15-pt rule, since I didn't feel like calculating the nodes/weights myself and those were the ones listed on wikipedia, but getting reasonable errors for test cases (~1e-14)
import scipy
from scipy import array
def quad_routine(func, a, b, x_list, w_list):
c_1 = (b-a)/2.0
c_2 = (b+a)/2.0
eval_points = map(lambda x: c_1*x+c_2, x_list)
func_evals = map(func, eval_points)
return c_1 * sum(array(func_evals) * array(w_list))
def quad_gauss_7(func, a, b):
x_gauss = [-0.949107912342759, -0.741531185599394, -0.405845151377397, 0, 0.405845151377397, 0.741531185599394, 0.949107912342759]
w_gauss = array([0.129484966168870, 0.279705391489277, 0.381830050505119, 0.417959183673469, 0.381830050505119, 0.279705391489277,0.129484966168870])
return quad_routine(func,a,b,x_gauss, w_gauss)
def quad_kronrod_15(func, a, b):
x_kr = [-0.991455371120813,-0.949107912342759, -0.864864423359769, -0.741531185599394, -0.586087235467691,-0.405845151377397, -0.207784955007898, 0.0, 0.207784955007898,0.405845151377397, 0.586087235467691, 0.741531185599394, 0.864864423359769, 0.949107912342759, 0.991455371120813]
w_kr = [0.022935322010529, 0.063092092629979, 0.104790010322250, 0.140653259715525, 0.169004726639267, 0.190350578064785, 0.204432940075298, 0.209482141084728, 0.204432940075298, 0.190350578064785, 0.169004726639267, 0.140653259715525, 0.104790010322250, 0.063092092629979, 0.022935322010529]
return quad_routine(func,a,b,x_kr, w_kr)
class Memoize(object):
def __init__(self, func):
self.func = func
self.eval_points = {}
def __call__(self, *args):
if args not in self.eval_points:
self.eval_points[args] = self.func(*args)
return self.eval_points[args]
def quad(func,a,b):
''' Output is the 15 point estimate; and the estimated error '''
func = Memoize(func) # Memoize function to skip repeated function calls.
g7 = quad_gauss_7(func,a,b)
k15 = quad_kronrod_15(func,a,b)
# I don't have much faith in this error estimate taken from wikipedia
# without incorporating how it should scale with changing limits
return [k15, (200*scipy.absolute(g7-k15))**1.5]
Test case:
>>> quad(lambda x: scipy.exp(1j*x), 0,scipy.pi/2.0)
[(0.99999999999999711+0.99999999999999689j), 9.6120083407040365e-19]
I don't trust the error estimate -- I took something from wiki for recommended error estimate when integrating from [-1 to 1] and the values don't seem reasonable to me. E.g., the error above compared with truth is ~5e-15 not ~1e-19. I'm sure if someone consulted num recipes, you could get a more accurate estimate. (Probably have to multiple by (a-b)/2 to some power or something similar).
Recall, the python version is less accurate than just calling scipy's QUADPACK based integration twice. (You could improve upon it if desired).
I realize I'm late to the party, but perhaps quadpy (a project of mine) can help. This
import quadpy
import numpy
val, err = quadpy.quad(lambda x: numpy.exp(1j * x), 0, 1)
print(val)
correctly gives
(0.8414709848078964+0.4596976941318605j)

Categories

Resources