Introducing Copula in Monte Carlo Simulation | by Rina Buoy | Towards Data Science

In the oil and gas industry, uncertainties are everywhere from the surface to the sub-surface. To embed the uncertainties in any estimation, probabilistic approaches are required.
One of the simple cases is a volumetric estimation. The formulas to estimate hydrocarbon (oil/gas) initially in place (HCIIP) are given below:
To probabilistically estimate the HCIIP, a Monte Carlo method is used and it follows the following steps (source):
Define a domain of possible inputsGenerate inputs randomly from a probability distribution over the domainPerform a deterministic computation on the inputsAggregate the results
Define a domain of possible inputs
Generate inputs randomly from a probability distribution over the domain
Perform a deterministic computation on the inputs
Aggregate the results
A Ms Excel implementation of probabilistic oil and gas prospect evaluation can be obtained here.
If all inputs are independent random variables or normally distributed, random sampling is rather straightforward. However, the inputs, for example, porosity and HC saturation are correlated to some degree and have different distributions. In such a case, random sampling is difficult. This is when Copula come to our rescue.
According to Wikipedia, a copula is a multivariate cumulative distribution function for which the marginal probability distribution of each variable is uniform.
The above definition is abstract and hard to comprehend. However, the implementation is rather easy. Thomas Wiecki wrote an inspiring post which gives a intuitive illustration of copula.
To fully grab the concept of copula, understanding of random variable transformation is required.
Let’s start by sampling from a uniform distribution between 0 and 1.
x = stats.uniform(0, 1).rvs(10000)
Instead of being uniform, these samples can be transformed to any probability distribution of interest via the inverse of the cumulative density function (CDF). Let’s say we want these samples to be normally distributed. We can pass these samples to inverse CDF function of a normal distribution ( ex. ppf function in scipy.stats). We will get the following samples which are normally distributed.
norm = stats.distributions.norm()x_trans = norm.ppf(x)
If we plot the uniform samples, the transformed samples, and the inverse CDF curve on the same plot, we get:
When we want to draw samples from a given distribution, the computer does this transformation under the hood.
Importantly, the process is reversible; that means that we can transform samples of any distribution back to unform distribution via the same CDF.
The steps of Gaussian copula are as follows:
Draw samples from a correlated multivariate normal distribution. Variable correlations are specified via the covariance matrix.Transform the correlated samples so that marginals (each input) are uniform.Transform the uniform marginals to any distribution of interest. For example, porosity to a truncated normal distribution, HC saturation to a triangle distribution etc.
Draw samples from a correlated multivariate normal distribution. Variable correlations are specified via the covariance matrix.
Transform the correlated samples so that marginals (each input) are uniform.
Transform the uniform marginals to any distribution of interest. For example, porosity to a truncated normal distribution, HC saturation to a triangle distribution etc.
Let’s start by drawing samples from a correlated multivariate normal distribution.
mvnorm = stats.multivariate_normal([0, 0], [[1., 0.5], [0.5, 1.]])x = mvnorm.rvs((10000,))
Next, we transform the marginals to a uniform distribution.
norm = stats.norm([0],[1])x_unif = norm.cdf(x)
As we can see, the joint distribution of X1 and X2 are correlated while their marginals are uniform.
We can, now, transform the marginals to any distribution of interest while preserving the correlation. For example, we want to draw X1 from a triangle distribution and X2 from a normal distribution.
x1_tri  = stats.triang.ppf(x_unif[:, 0],  c=0.158 , loc=36, scale=21)x2_norm =stats.norm(525, 112).ppf(x_unif[:, 1])
Now we obtain the desired joint distribution of X1 and X2 which are drawn from different distributions.
Armed with copula, we are ready to introduce variable correlations to the sampling phase of Monte Carlo. Here is the complete python codes of calculating OIIP:
import seaborn as snsfrom scipy import statsimport numpy as npimport matplotlib.pyplot as plt# HCIIP = GRV*NTG*POR*SHC/FVFmeans = [0.]*5cov = [[1., 0., 0., 0., 0.],[0., 1., 0., 0., 0.],[0., 0., 1., 0., 0.],[0., 0., 0., 1., 0.],[0., 0., 0., 0., 1.]]mvnorm_std = stats.multivariate_normal(means,cov)x = mvnorm_std.rvs(10000,random_state=42)norm_std = stats.norm()x_unif = norm_std.cdf(x)#create individual distr.grv = stats.triang(c=0.1 , loc=10000, scale=300).ppf(x_unif[:, 0])ntg = stats.triang(c=0.2 , loc=0.5, scale=0.5).ppf(x_unif[:, 1])phi = stats.truncnorm(-2*1.96,1.96,0.2,0.05).ppf(x_unif[:, 2])shc = stats.norm(0.6,0.05).ppf(x_unif[:, 3])fvf= stats.truncnorm(-1.96,2*1.96,1.3,0.1).ppf(x_unif[:, 4])stoiip = 7758*grv*ntg*phi*shc/fvf/1e6sns.distplot(stoiip  , kde=False, norm_hist=True)plt.figure()sns.distplot(stoiip ,hist_kws=dict(cumulative=True),kde_kws=dict(cumulative=True))plt.show()
For the case of no correlation, the covariance matrix is a diagonal matrix (zeros everywhere except the diagonal cells).
cov = [[1., 0., 0., 0., 0.],[0., 1., 0., 0., 0.],[0., 0., 1., 0., 0.],[0., 0., 0., 1., 0.],[0., 0., 0., 0., 1.]]
Running the above codes, we obtain the following OIIP histogram and cumulative probability distribution.
To illustrate the case of variable correlations, we arbitrarily assign positive correlations among NTG, POR and SHC.
cov = [[1., 0., 0., 0., 0.],[0., 1., 0.7, 0.6, 0.],[0., 0.7, 1., 0.8, 0.],[0., 0.6, 0.8, 1., 0.],[0., 0., 0., 0., 1.]]
By observing the OIIP histogram and cumulative distribution, we can see that including variable correlations or dependencies expands the P10-P90 range of the calculated OIIP. This is reasonable since by including correlations, sampling can explore extreme space which may, otherwise, be missed in the case of no correlation. In the next post, we will look at Bayesian decline curve analysis (DCA).