2
$\begingroup$

I'm trying to estimate the percentage error in computing the integral of a Gaussian via composite trapezoidal rule versus via an exact formula. To do this I've generated a gaussian with mean 0, standard deviation $\sigma = 1$ and amplitude $A = 1$ in Python (plotted below), on a grid of $N$ points in the range $[-2\sigma,2\sigma]$, and used scipy.integrate.trapz to compute the approximate integral $A$ for a range of different $N$.

enter image description here

For the exact integral I used the formula $I = \int_{-x}^{x}e^{\frac{-t^2}{2\sigma^2}} dt = \sqrt{2\pi}\sigma\text{erf}(\frac{x}{\sqrt{2}\sigma})$ to compute the exact integral between $\pm$ two standard deviations of the mean as $I = \int_{-2\sigma}^{2\sigma}e^{\frac{-t^2}{2\sigma^2}} dt = \sqrt{2\pi}\sigma\text{erf}(\sqrt{2})$, or

$I = \sqrt{2\pi}\text{erf}(\sqrt{2})$

in this $\sigma = 1$ case. The % error is then $E = \frac{|I - A|}{I}\cdot 100$. Below is a plot of $\frac{1}{E}$ versus $N$

enter image description here

and for reference a plot of $E$ verus $N$.

enter image description here

Now I would expect $E \propto \frac{1}{N^2}$ but this is not seen here. Would anyone be able to help me figure out why?

I've included my code below. Thank you in advance!

from scipy.integrate import trapz
from scipy.special import erf
import matplotlib.pyplot as plt
import numpy as np

errs = np.array([]) # Errors in integral estimates
Ns = np.array([]) # Sample sizes tried

def get_percent_err(N, errs, Ns):
    A = 1 # Amplitude of Gaussian
    std = 1 # Standard deviation of Gaussian
    r = 2*std # Extent of domain
    dt = 2*r/N # Step
    t = np.linspace(-r,r,N) # Domain
    gauss = A*np.exp(-((t)**2)/(2*(std**2))) # Gaussian function
    
    actual_area = np.sqrt(2*np.pi)*A*std*erf(np.sqrt(2)) # Exact gaussian integral from -2*std to +2*std
    est_area = trapz(gauss, dx=dt) # Estimated integral via trapezium rule
    percent_err = (abs(actual_area - est_area)/actual_area)*100 # Percentage error
    
    errs = np.append(errs, percent_err)
    Ns = np.append(Ns, N)
    
    return errs, Ns

# Sweep through a range of sample sizes and obtain errors for each
min_samples = 100
max_samples = 100100
sample_step = 100
samples = np.arange(min_samples,max_samples,sample_step)

for N in samples:
    errs, Ns = get_percent_err(N, errs, Ns)

# Plot reciprocal of error in integral against sample size
plt.figure()
plt.plot(1/Ns,errs)
plt.xlabel("1 / Samples (1 / N)")
plt.ylabel("% Error in Integral (E)")
plt.title("Plot of % Error (E) in Integral versus 1 / Samples (1 / N)")

UPDATE:

For reasons that are unknown to me (though I believe it has something to do with numerical precision), this code, adapted from this link, gives an expected result.

Code:

import numpy as np
import matplotlib.pyplot as plt
from scipy.special import erf
from scipy.integrate import trapz as trapz_scipy
from pandas import DataFrame

def trapz(f,a,b,N=50):
    x = np.linspace(a,b,N+1) # N+1 points make N subintervals
    y = f(x)
    dx = (b - a)/N
    T = trapz_scipy(y, dx=dx)
    return T

def get_err(N, errs):
    A = 1
    std = 1
    f = lambda x : A*np.exp(-x**2/(2*std**2))
    a = -2*std; b = 2*std;
    
    I = np.sqrt(2*np.pi)*A*std*erf(np.sqrt(2))
    T = trapz(f,a,b,N)
    return np.append(errs, 100*np.abs(I - T)/T)

min_samples = 10
max_samples = 1000
sample_step = 10
sample_sizes = np.arange(min_samples,max_samples+sample_step,sample_step)

errs = np.array([], dtype=np.float64)

for s in sample_sizes:
    errs = get_err(s, errs)
    
err_vs_sample_size = DataFrame({'% Error' : errs, 'Samples' : sample_sizes})

plt.figure()
plt.plot(1/sample_sizes**2,errs)
plt.xlabel("1 / Samples (1 / N)")
plt.ylabel("% Error in Integral (E)")
plt.title("Plot of % Error (E) in Integral versus 1 / Samples (1 / N)")

Error plot:

enter image description here

$\endgroup$
4
  • 1
    $\begingroup$ Perhaps your error is that you have only N-1 subintervals but your dt is set as if you had N subintervals $\endgroup$ Commented May 11, 2021 at 5:58
  • 1
    $\begingroup$ Not totally sure I understand what you are saying in the above, but is people.maths.ox.ac.uk/trefethen/sirev56-3_385.pdf of interest, especially around page 400? $\endgroup$
    – Ian Bush
    Commented May 11, 2021 at 13:44
  • 2
    $\begingroup$ It looks to me like a simple bug: in the second version, the one that works, linspace is called with N+1, which means there are N subintervals. But in the first version, that doesn’t work, linspace is called with N, which means there are N-1 subintervals. The subinterval size, dt, is assumed in both cases to be 2r/N, consistent only with the linspace in the second version. $\endgroup$ Commented May 12, 2021 at 0:44
  • $\begingroup$ Yep that turned out to be the problem, oops! Thanks guys! $\endgroup$
    – Cazador
    Commented May 12, 2021 at 11:41

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.