Monte Carlo Integration

A fun, yet robust, way to integrate numerically.

What is Monte Carlo simulation?

The term "Monte Carlo" basically associates itself with "randomness" (though, it is a city in Monaco). So, Monte Carlo simulations are actually simulations evolving randomly.

Though, this seems very counter-intuitive as to how something random can generate something useful. However, things will get clear very soon.

A close relation- Law of Large Numbers

Monte Carlo simulation is somewhat closely related to the Law of Large numbers. It basically says,

According to the law, the average of the results obtained from a large number of trials should be close to the expected value and tends to become closer to the expected value as more trials are performed

This basically means that as we go on taking more and more samples, the mean of the experiments tends to be what we expected the average to be. Obviously, we can't select an unbiased sample as it would produce a bias in our experiment. So, the best way to choose an unbiased sample is to choose randomly from a large population of samples and choose as many samples as possible.

Mathematical background

Let us have a one-dimensional function

$$f(x)$$

and a uniform distribution function

$$\rho (x)$$

The uniform distribution function has some properties

  • It is constant over its defined region.

  • The area the distribution encloses under it is equal to 1.

    $$\int_a^b\rho(x)\;dx = 1$$

where a, b are the limits where the distribution function is defined.

$$\begin{align} Let, \; I = \int_{x = a}^{x = b} f(x) \;dx \ \ \therefore I = \int_a^b \frac{f(x) \rho(x)}{\rho(x)} \;dx ...(1)\ \ \end{align}$$

$$ \begin{align} Now, \;\; \int_a^b\rho(x) \;dx = 1. \ \ As, \rho(x) \;\; \text{is constant, so} \ \ \rho(x) \int_a^bdx = 1 \ \ \therefore \rho(x) = \frac{1}{b-a} \;\;\;...(2) \end{align} $$

$$\begin{align} From \;\; 1, \;\; I = (b-a) \int_a^b f(x) \rho(x)\;dx \ \ Now, \;\; \int_a^b f(x) \rho(x) = mean \;\; of \;\; f(x) = E(f(x)) \end{align}$$

Now, using the Law of Large Numbers and Random sampling, we can say that

$$\begin{align} E(f(x)) = \frac{1}{N}\sum_{i=1}^{i = N} f(x_i) \end{align}$$

Here are the 'i'th x elements are randomly selected and do not have any trend or pattern in their selection. So, our integral becomes

$$I = \int_a^b f(x) dx \approx (b-a) \frac{1}{N} \sum_i f(x_i)$$

Choosing the 'i'th x element randomly and continuing this for N such elements gives us our integral.

A modified form of this approach

The summation in this integral can be easily taken care of by a modified form. The basic scheme is

  • We draw the curve on a grid

  • We take random points on the grid and draw them on the graph

  • If the point falls within the curve, we call it a success and if it doesn't, it is a failure.

  • Let there be x successes and y failures. Then, the integral is given as

    $$I \approx \frac{x}{x+y} (b-a)$$

    Here (b-a) are the integration limits.

Code

Here, we will solve the integral

$$\int_0^{\pi/2} cos(x) \; dx = 1$$

Step 1: Import packages

Imported packages are pretty simple and very standard.

import numpy as np
import matplotlib.pyplot as plt
plt.style.use(["science", "notebook", "grid"])

Step 2: Write the logic

def decision(f, xlim:tuple, ylim:tuple, N:int) -> float:
    # Success and failure initialisation
    success, failure = 0, 0

    # Get random points for the Monte Carlo simulation
    random_x = np.random.uniform(low = xlim[0], high = xlim[1], size = N)
    random_y = np.random.uniform(low = ylim[0], high = ylim[1], size = N)

    # Logically find out about the boundaries
    for x, y in zip(random_x, random_y):
        if (y <= f(x)):
            success += 1 # success increases
        else:
            failure += 1 # Failure increases
    integral = success/(success + failure) * (xlim[1] - xlim[0]) # final ans
    return success, failure, integral

Step 3: Test our function

xlim, ylim = (0, np.pi/2), (0, 1)
N = 100
f = np.cos

final_ans = np.zeros(N) # Initialise solution array

for epoch in range(N):
    succesess, failures, final_integral = decision(f = f, xlim=xlim, ylim=ylim, N= epoch)
    final_ans[epoch] = final_integral # append final answer to array

Step 5: Visualize everything

plt.figure(figsize = (12, 8))
plt.axhline(y = 1, color = "red", linewidth = 3)
plt.ylabel("Integral", fontsize = 15, fontweight = "bold")
plt.xlabel("Iterations", fontsize = 15, fontweight = "bold")
plt.title("Monte carlo Integration", fontsize = 20, fontweight = "bold")
plt.plot(final_ans, "o-", markerfacecolor = "w", label = "No of iterations = 100, Integral = 1.06306407")
plt.legend()

As we see, after just 100 iterations, out integral almost converges to 1. The rate at which our integral converges is actually related to the spacing in our grid.

Obviously, in this demonstration, I have taken (b-a) to be equal to

$$\frac{\pi}{2}$$