Monte Carlo methods are numerical techniques which rely on random sampling to approximate their results. Thus, in Monte Carlo integration, the process of numerical estimation of integrals is applied.

Considering we want to integrate a one-dimensional function $f(x)$ from $\textit{a}$ to $\textit{b}$: $$F=\int_a^b f(x),\mathrm{d}x$$. Since the integral of a function $f(x)$ can be interpreted as the area below the function's curve, we can pick up a random value $x \in [a,b]$ and evaluate the function at $\textit{x}$ and multiply the result by $(b-a)$. If we plot the function's curve and the result we will obtain a rectangle where f(x) is the height and (b-a) is the width, basically a crude approximation of the area under the curve. If we continue to evaluate the function at different random points between a and b, adding up the area of the rectangles and averaging the sum, the resulting number gets closer to the actual results of the integral.

Let's consider an example in which the function we want to integrate is $f(x)=\sin{x}$ on interval $[0,\pi]$ and write some OCaml code. The function plot can be seen in Fig. 1.

Fig. 1:sin(x) curve

We will create a new module MonteCarlo with the interface defined in the below snippet:

module type MonteCarlo_type=sig
  val initializePRNG:unit->unit
  val getRandomNumber:unit->float
  val approximate:(float->float)->float->float->int->int->float->float  
end

We have a function initializePRNG which will initialize the pseudorandom number generator, let initializePRNG ()=Random.self_init(). let getRandomNumber ()=Random.float 1.0 function will only generate a pseudorandom number distributed on $[0,1]$. Finally, the most interesting function, approximate receives 6 parameters as follow: *. First parameter is the function $f(x)$ *. Second and third parameters are the limits a and b. *. Fourth parameter is the number of iterations. *. Fifth parameter is the current iteration *. Sixth parameter is the sumed value.

let rec approximate f a b n index value=
    if index=(n-1) then
      ((b-.a)/.(float_of_int index))*.value
    else
      let randVal=getRandomNumber() in
      let inInterval=a+.randVal*.(b-.a) in
      let fVal=f inInterval in
      approximate f a b n (index+1) (value+.fVal)

The complete MonteCarlo module is implemented bellow:

open Random
open MonteCarlo_type

module MonteCarlo:MonteCarlo_type=struct
  let initializePRNG ()=Random.self_init()
  let getRandomNumber ()=Random.float 1.0
  
  let rec approximate f a b n index value=
    if index=(n-1) then
      ((b-.a)/.(float_of_int index))*.value
    else
      let randVal=getRandomNumber() in
      let inInterval=a+.randVal*.(b-.a) in
      let fVal=f inInterval in
      approximate f a b n (index+1) (value+.fVal)
end

Finally, to use the code we just need to define the function that we want to integrate and call approximate, as bellow:

open MonteCarlo

let pi = 4.0 *. atan 1.0;;
let func (x:float)=sin x
let () = 
  MonteCarlo.initializePRNG();
  print_float (MonteCarlo.approximate func 0.0 pi 100000 0 0.0)