%%javascript
IPython.OutputArea.prototype._should_scroll = function(lines) {
return false;
}
import numpy as np
import pandas as pd
import sys
import os
import matplotlib.pyplot as plt
import plotly.graph_objects as go
import urllib.request
import gstlearn as gl
import gstlearn.plot as gp
import gstlearn.plot3D as gop
The well information corresponds to the coordinates of their intercepts with the top and bottom surfaces of the target reservoir. It is contained in the ASCII file Wells.dat. In addition, a selection is provided which distinguishes the appraisal wells from the other ones.
The ASCII file is loaded. The file is composed of 189 wells covering a field with an extension of 10 by 15 kilometers. The depth is oriented downwards with a top varying from -983m to -1030m and the bottom from -1036m to -1067m.
url = 'https://soft.minesparis.psl.eu/gstlearn/data/Chamaya/Wells.dat'
filepath, head = urllib.request.urlretrieve(url)
mydb = gl.Db.createFromCSV(filepath,gl.CSVformat())
mydb.setLocators(["X","Y"],gl.ELoc.X)
mydb
Data Base Characteristics ========================= Data Base Summary ----------------- File is organized as a set of isolated points Space dimension = 2 Number of Columns = 6 Total number of samples = 189 Variables --------- Column = 0 - Name = rank - Locator = NA Column = 1 - Name = X - Locator = x1 Column = 2 - Name = Y - Locator = x2 Column = 3 - Name = top - Locator = NA Column = 4 - Name = bot - Locator = NA Column = 5 - Name = appraisal - Locator = NA
dbfmt = gl.DbStringFormat.createFromFlags(True,True,True,True)
mydb.display(dbfmt)
Data Base Characteristics ========================= Data Base Summary ----------------- File is organized as a set of isolated points Space dimension = 2 Number of Columns = 6 Total number of samples = 189 Data Base Extension ------------------- Coor #1 - Min = 50.000 - Max = 9950.000 - Ext = 9900 Coor #2 - Min = 150.000 - Max = 14950.000 - Ext = 14800 Data Base Statistics -------------------- 1 - Name rank - Locator NA Nb of data = 189 Nb of active values = 189 Minimum value = 1.000 Maximum value = 189.000 Mean value = 95.000 Standard Deviation = 54.559 Variance = 2976.667 2 - Name X - Locator x1 Nb of data = 189 Nb of active values = 189 Minimum value = 50.000 Maximum value = 9950.000 Mean value = 4893.386 Standard Deviation = 2909.303 Variance = 8464043.560 3 - Name Y - Locator x2 Nb of data = 189 Nb of active values = 189 Minimum value = 150.000 Maximum value = 14950.000 Mean value = 8163.757 Standard Deviation = 4490.427 Variance = 20163937.740 4 - Name top - Locator NA Nb of data = 189 Nb of active values = 189 Minimum value = -1030.760 Maximum value = -983.260 Mean value = -1007.963 Standard Deviation = 10.246 Variance = 104.980 5 - Name bot - Locator NA Nb of data = 189 Nb of active values = 189 Minimum value = -1067.370 Maximum value = -1036.050 Mean value = -1050.542 Standard Deviation = 6.903 Variance = 47.646 6 - Name appraisal - Locator NA Nb of data = 189 Nb of active values = 189 Minimum value = 0.000 Maximum value = 1.000 Mean value = 0.265 Standard Deviation = 0.441 Variance = 0.195 Variables --------- Column = 0 - Name = rank - Locator = NA Column = 1 - Name = X - Locator = x1 Column = 2 - Name = Y - Locator = x2 Column = 3 - Name = top - Locator = NA Column = 4 - Name = bot - Locator = NA Column = 5 - Name = appraisal - Locator = NA
The data set is represented where the well intercepts with the top surface are displayed either in black or in yellow for the appraisal wells, whereas the intercepts with the bottom surface are displayed in purple.
fig, ax = gp.initGeographic()
ax.symbol(mydb,nameColor="appraisal")
ax.decoration(title="Geometry Information")
plt.show()
Obviously, all wells have penetrated the whole reservoir layer and therefore we can derive the layer thickness at each vertical well location. The thickness varies from 12.21m to 64.25m, as represented in the following histogram.
mydb["thick"] = mydb["top"] - mydb["bot"]
fig, ax = gp.init()
ax.histogram(mydb, name="thick", bins=30)
plt.show()
This step requires the prior definition of a grid used for mapping the surfaces whose extension and cell dimensions must be chosen carefully: it results from a trade-off between the time consumption used for mapping the surfaces and an unnecessary finesse of the grid mesh.
For this study, the optimal choice is a grid with a square mesh of 100m edge, with 101 nodes along X and 151 nodes along Y, covering a surface of 10km along X and 15km along Y. The origin of the grid (lower left corner) is located at 50m along X and 50m along Y.
mygrid = gl.DbGrid.create(nx=[101,151],dx=[100,100],x0=[50,50])
mygrid
Data Base Grid Characteristics ============================== Data Base Summary ----------------- File is organized as a regular grid Space dimension = 2 Number of Columns = 3 Total number of samples = 15251 Grid characteristics: --------------------- Origin : 50.000 50.000 Mesh : 100.000 100.000 Number : 101 151 Variables --------- Column = 0 - Name = rank - Locator = NA Column = 1 - Name = x1 - Locator = x1 Column = 2 - Name = x2 - Locator = x2
The grid (only one cell out of 5 for better legibility) and the well headers are displayed.
fig, ax = gp.initGeographic()
ax.cell(mygrid,color='black',step=5)
ax.symbol(mydb)
plt.show()
In this phase, we are facing the usual problem of estimating linked variables, such as the top, the bottom and the thickness of a layer. The principle consists in processing jointly the set of correlated variables and the uncorrelated one separately. Usually the top and the bottom are non correlated. Then we recommend estimating either the top (respectively the bottom) and the thickness separately and then simply deriving the bottom (respectively the top). Another solution would be to jointly estimate the top and the bottom using a multivariate procedure: this may become difficult if both variables are non-stationary.
Here, and for illustration sake, we will process the three variables (top, bottom and thickness) independently.
We compute the experimental variogram of the top variable, calculated in 4 conventional directions (N0, N45, N90 and N135), for 15 lags of 500m each.
mydb.setLocator("top", gl.ELoc.Z)
varioparam = gl.VarioParam.createMultiple(ndir=4, npas=15, dpas=500)
vario_top = gl.Vario.computeFromDb(varioparam, mydb)
The variogram is displayed for all calculation directions
fig, ax = gp.init()
ax.variogram(vario_top,idir=-1,flagLegend=True)
plt.show()
We check that the top variable is anisotropic with calculation directions overlapping two by two: 90 and 135 on one hand for the higher variability and 0 and 45 on the other hand for lower variability. As the curves are grouped two by two, this prooves that the main anisotropy axis does not coincide with an already calculated directions, but rather with an intermediate one.
Another possibility is to calculate the variogram map. We obtain the following figure:
vmap_top = gl.db_vmap_compute(mydb,gl.ECalcVario.VARIOGRAM,[20,20])
fig, ax = gp.initGeographic()
ax.raster(vmap_top, "*Var")
ax.decoration(title="Variogram Map for Top")
plt.show()
This clearly shows the low variability direction around 20 degrees.
The directional experimental variogram is then used for fitting the model.
model_top = gl.Model()
err = model_top.fit(vario_top)
model_top
Model characteristics ===================== Space dimension = 2 Number of variable(s) = 1 Number of basic structure(s) = 1 Number of drift function(s) = 0 Number of drift equation(s) = 0 Covariance Part --------------- Spherical - Sill = 157.461 - Ranges = 24037.055 5003.649 - Angles = 21.202 0.000 - Rotation Matrix [, 0] [, 1] [ 0,] 0.932 -0.362 [ 1,] 0.362 0.932 Total Sill = 157.461
The model is composed of a single spherical basic anisotropic structure: the anisotropy ellipsoid is rotated (its major axis is oriented 21 degrees) with its ranges are respectively equal to 24km and 5km, as shown in the next figure.
fig, ax = gp.init()
ax = gp.varmod(vario_top, model_top)
plt.show()
We compute the experimental variogram of the thickness variables, calculated in 4 conventional directions for 15 lags of 500m each.
mydb.setLocator("bot", gl.ELoc.Z)
varioparam = gl.VarioParam.createMultiple(ndir=4, npas=15, dpas=500)
vario_bot = gl.Vario.computeFromDb(varioparam, mydb)
fig, ax = gp.init()
ax.variogram(vario_bot,idir=-1,flagLegend=True)
plt.show()
The corresponding variogram map is represented next.
vmap_bot = gl.db_vmap_compute(mydb,gl.ECalcVario.VARIOGRAM,[20,20])
fig, ax = gp.initGeographic()
ax.raster(vmap_bot, "*Var")
ax.decoration(title="Variogram Map for Bottom")
plt.show()
This directional experimental variogram is used for fitting the model.
types = gl.ECov.fromKeys(["SPHERICAL","EXPONENTIAL"])
model_bot = gl.Model()
err = model_bot.fit(vario_bot, types)
model_bot
Model characteristics ===================== Space dimension = 2 Number of variable(s) = 1 Number of basic structure(s) = 2 Number of drift function(s) = 0 Number of drift equation(s) = 0 Covariance Part --------------- Spherical - Sill = 11.914 - Ranges = 2811.249 986.432 - Angles = 25.340 0.000 - Rotation Matrix [, 0] [, 1] [ 0,] 0.904 -0.428 [ 1,] 0.428 0.904 Exponential - Sill = 103.277 - Ranges = 95677.717 24735.006 - Theo. Ranges = 31938.010 8256.749 - Angles = 353.264 0.000 - Rotation Matrix [, 0] [, 1] [ 0,] 0.993 0.117 [ 1,] -0.117 0.993 Total Sill = 115.190
The nested model is composed of two anisotropic basic structures with (different) rotations of the anisotropy ellipsoids. The fitting is shown in the next figure.
fig, ax = gp.init()
ax = gp.varmod(vario_bot, model_bot)
plt.show()
We compute the experimental variogram of the thickness variables, calculated in 4 conventional directions for 15 lags of 500m each.
mydb.setLocator("thick", gl.ELoc.Z)
varioparam = gl.VarioParam.createMultiple(ndir=4, npas=15, dpas=500)
vario_thick = gl.Vario.computeFromDb(varioparam, mydb)
fig, ax = gp.init()
ax.variogram(vario_thick,idir=-1,flagLegend=True)
plt.show()
Next comes the calculation of the Variogram map
vmap_thick = gl.db_vmap_compute(mydb,gl.ECalcVario.VARIOGRAM,[20,20])
fig, ax = gp.initGeographic()
ax.raster(vmap_thick, "*Var")
ax.decoration(title="Variogram Map for Thickness")
plt.show()
The directional experimental variogral is used for fitting a Model
types = gl.ECov.fromKeys(["SPHERICAL"])
model_thick = gl.Model()
err = model_thick.fit(vario_thick, types)
model_thick
Model characteristics ===================== Space dimension = 2 Number of variable(s) = 1 Number of basic structure(s) = 1 Number of drift function(s) = 0 Number of drift equation(s) = 0 Covariance Part --------------- Spherical - Sill = 124.911 - Ranges = 9102.815 4618.492 - Angles = 23.011 0.000 - Rotation Matrix [, 0] [, 1] [ 0,] 0.920 -0.391 [ 1,] 0.391 0.920 Total Sill = 124.911
The experimental variogram and the model are represented
fig, ax = gp.init()
ax = gp.varmod(vario_thick, model_thick)
plt.show()
The estimation of the different variables will be performed using several interpolation methods to estimate the value of the variables on the node of the grid created beforehand. We will focus on the thickness variable only. Before kriging, we will use some traditional techniques, such as:
• the nearest neighbor
• moving average
• moving median
All these methods are based on linear techniques: for each target site, the estimated value is obtained as the linear combination of the data values at the surrounding information data points.
Note that all these method require the definition of a neighborhood (selection of data used for the estimation of each grid node). For a better understanding of the resulting map, the data information is overlaid using a proportional representation.
Moreover, if a Model is defined, we can also evaluate the variance of the estimation error for each method (in fact, we display the standard deviation instead). This requires the definition of a Model.
model = gl.Model.createFromParam(type = gl.ECov.CUBIC, range=2000)
The principle is to set the value of each grid node at the value of the closest data point.
err = gl.nearestNeighbor(mydb, mygrid, flag_std=True, model=model)
fig, ax = gp.initGeographic()
ax.raster(mygrid, name="Nearest.thick.estim",flagLegend=True)
ax.symbol(mydb, c='black')
ax.decoration(title="Nearest Neighbor")
plt.show()