pythonscipynumerical-integration

Offset in using trapezoid method for numerical integration


I am using the trapezoidal method to integrate the squared modulus of this Gaussian function:

enter image description here

In fact, the squared modulus of this function can be integrated analytically, resulting in:

enter image description here

When I try to solve the integral both analytically and numerically, I get the following result (I simplify by setting ( t_0 = 0 ) and ( \sigma_t = 1 )):

enter image description here

Essentially, there is an offset on the y-axis. This concerns me because I then have to subtract the obtained values from another function, and this offset propagates throughout the code.

I can safely use the analytical solution for one case, but in the other, since the function comes from a frequency-domain reflection relationship of this Gaussian pulse, I am afraid that even in that case, when I integrate the squared modulus, there might be an offset as well.

Do you know what could be causing this and could you possibly suggest what to do? This is my first time dealing with numerical methods.

from scipy.integrate import trapezoid
from scipy.interpolate import CubicSpline
import numpy as np
from scipy.special import erf
import matplotlib.pyplot as plt

def u_time(t_0):
    prefactor = 1 / (np.pi**0.25)
    def func(time):
        return prefactor * np.exp(-(time - t_0) ** 2 / 2)
    return func

def u_integral(t_0, times):
    u_values = u_time(t_0)(times)
    u_abs_2 = np.abs(u_values) ** 2
    u_abs_2_int = np.zeros_like(times, dtype=np.complex128)
    for k in range(1, len(times)):
        u_abs_2_int[k] = trapezoid(u_abs_2[:k], times[:k])
    return CubicSpline(times, u_abs_2_int)


def gaussian_squared_integral(t_0):
    return lambda time: 0.5 * (erf(time - t_0) + erf(t_0))
    
t_0 = 0
time_arr = np.linspace(-20, 100, 1000)
analytic = u_integral(t_0, time_arr)(time_arr)
numeric = gaussian_squared_integral(t_0)(time_arr)

plt.plot(time_arr, analytic, label="Analytic")
plt.plot(time_arr, numeric, label="Numeric")
plt.legend()
plt.show()

Solution

  • In fact, the squared modulus of this function can be integrated analytically, resulting in:

    It's that plus C. When you take the indefinite integral of this, you get that value, plus some arbitrary constant.

    If you want to use this indefinite integral / antiderivative to find the definite integral, you need to evaluate it twice: once at the start of your bounds and once at the ends. More information.

    One more note is that I believe you swapped the variables analytic and numeric, as the analytic solution is evaluating the function at many points and summing the area, and the numeric solution is based on the integral. Generally people would call the first thing numeric integration and the second thing analytic integration. I've fixed that below.

    from scipy.integrate import trapezoid
    from scipy.interpolate import CubicSpline
    import numpy as np
    from scipy.special import erf
    import matplotlib.pyplot as plt
    
    def u_time(t_0):
        prefactor = 1 / (np.pi**0.25)
        def func(time):
            return prefactor * np.exp(-(time - t_0) ** 2 / 2)
        return func
    
    def u_integral(t_0, times):
        u_values = u_time(t_0)(times)
        u_abs_2 = np.abs(u_values) ** 2
        u_abs_2_int = np.zeros_like(times, dtype=np.complex128)
        for k in range(1, len(times)):
            u_abs_2_int[k] = trapezoid(u_abs_2[:k], times[:k])
        return CubicSpline(times, u_abs_2_int)
    
    
    def gaussian_squared_integral(t_0):
        return lambda time: 0.5 * (erf(time - t_0) + erf(t_0))
    
    
    t_0 = 0
    a = -20
    b = 100
    time_arr = np.linspace(a, b, 1000)
    numeric = u_integral(t_0, time_arr)(time_arr)
    F_of_x_at_start_point = gaussian_squared_integral(t_0)(a)
    analytic = gaussian_squared_integral(t_0)(time_arr) - F_of_x_at_start_point
    
    plt.plot(time_arr, analytic, label="Analytic")
    plt.plot(time_arr, numeric, label="Numeric")
    plt.legend()
    plt.show()
    

    Lastly, I will note that in this loop:

        for k in range(1, len(times)):
            u_abs_2_int[k] = trapezoid(u_abs_2[:k], times[:k])
    

    If you need to know the value of the integral at each point along the trapezoidal integration, SciPy has a faster alternative here called cumulative_trapezoid, which would let you avoid this loop.