Well Log Fluid Substitution¶
In [1]:
Copied!
import numpy as np
import pandas as pd
import os
import pathlib
from digirock.datasets import fetch_example_data
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import os
import pathlib
from digirock.datasets import fetch_example_data
import matplotlib.pyplot as plt
Fetch and setup the example data, which includes well F-4 from the Volve field.
In [2]:
Copied!
data = fetch_example_data()
data = fetch_example_data()
In [3]:
Copied!
logs = pd.read_csv(data["volve_f4.csv"], index_col=0)
logs["VELP"] = 304800.0 / logs["DT"] # get to meters per second
logs["VELS"] = 304800.0 / logs["DTS"] # get to meters per second
# load some formation volume factor tables for the oil
bo_tab = pd.read_csv(data["PVT_BO.inc"], delim_whitespace=True, header=None, names=["pres", "bo"])
rs_tab = pd.read_csv(data["PVT_RS.inc"], delim_whitespace=True, header=None, names=["pres", "rs"])
# pres is in bar -> convert to MPa for digirock
bo_tab["pres"] = bo_tab["pres"] * 0.1
rs_tab["pres"] = rs_tab["pres"] * 0.1
logs = pd.read_csv(data["volve_f4.csv"], index_col=0)
logs["VELP"] = 304800.0 / logs["DT"] # get to meters per second
logs["VELS"] = 304800.0 / logs["DTS"] # get to meters per second
# load some formation volume factor tables for the oil
bo_tab = pd.read_csv(data["PVT_BO.inc"], delim_whitespace=True, header=None, names=["pres", "bo"])
rs_tab = pd.read_csv(data["PVT_RS.inc"], delim_whitespace=True, header=None, names=["pres", "rs"])
# pres is in bar -> convert to MPa for digirock
bo_tab["pres"] = bo_tab["pres"] * 0.1
rs_tab["pres"] = rs_tab["pres"] * 0.1
In [4]:
Copied!
logs.describe()
logs.describe()
Out[4]:
| DT | DTS | TVDSS | GR | NPHI | DENS | XLOC | YLOC | SW_SURVEY1 | SW_SURVEY2 | PERMZ | PORO | VSH | OWT | VELP | VELS | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| count | 4815.000000 | 4246.000000 | 5988.000000 | 5988.000000 | 4926.000000 | 4964.000000 | 5988.000000 | 5.988000e+03 | 1138.000000 | 1138.000000 | 1070.000000 | 1070.000000 | 5988.000000 | 5988.000000 | 4815.000000 | 4246.000000 |
| mean | 78.140018 | 142.814872 | 2771.805207 | 46.109442 | 0.180302 | 2.441509 | 435690.865810 | 6.477987e+06 | 0.295872 | 0.646379 | 548.386215 | 0.229878 | 0.260368 | 1262.285207 | 4008.790939 | 2159.695890 |
| std | 13.415549 | 15.671214 | 200.146933 | 44.143014 | 0.097218 | 0.166057 | 33.141649 | 1.578144e+02 | 0.295049 | 0.259817 | 531.485748 | 0.025175 | 0.217152 | 57.942543 | 644.440103 | 233.760768 |
| min | 53.358002 | 108.222000 | 2400.089355 | 1.000000 | 0.045700 | 1.334100 | 435601.125000 | 6.477678e+06 | 0.042300 | 0.142800 | 0.491576 | 0.130000 | 0.020500 | 1140.923828 | 2331.025434 | 1601.995596 |
| 25% | 67.192200 | 130.147629 | 2599.324951 | 15.153900 | 0.107500 | 2.300000 | 435670.031250 | 6.477854e+06 | 0.131600 | 0.684475 | 62.688911 | 0.216640 | 0.080800 | 1217.474945 | 3499.622661 | 1978.556591 |
| 50% | 76.447197 | 142.112801 | 2788.841186 | 35.016998 | 0.157000 | 2.496600 | 435699.500000 | 6.478012e+06 | 0.167800 | 0.764000 | 513.006897 | 0.230275 | 0.209550 | 1262.712891 | 3987.065741 | 2144.775166 |
| 75% | 87.095104 | 154.051697 | 2951.317078 | 56.746299 | 0.227675 | 2.560025 | 435719.468750 | 6.478132e+06 | 0.248900 | 0.779200 | 813.894226 | 0.248776 | 0.359550 | 1315.449036 | 4536.240833 | 2341.955849 |
| max | 130.757904 | 190.262695 | 3083.119385 | 469.738007 | 0.732400 | 3.201300 | 435727.593750 | 6.478198e+06 | 1.000000 | 1.000000 | 2000.000000 | 0.281082 | 0.799800 | 1350.738525 | 5712.357851 | 2816.432885 |
Setup the PEM¶
We need to first build the basic blocks of our rock model including an oil/water WoodsFluid and Voigt-Reuss-Hill grain matrix.
In [5]:
Copied!
from digirock import WaterBW92, OilPVT, WoodsFluid, Mineral, VRHAvg
# FLUID PROPS
temp = 110 # reservoir temperature in degC
sal = 151200 # reservoir brine salinity ppm
pres = 32 # reseroir insitu pressure MPa
volve_brine = WaterBW92(salinity=sal)
volve_oil = OilPVT(std_density=0.885, gas_sg=1.09956)
volve_oil.set_pvt(140, bo_tab["bo"].values, bo_tab["pres"].values)
fl = WoodsFluid(["sw", "so"], [volve_brine, volve_oil])
# MATRIX PROPS
volve_sand = Mineral(2.634, 31.55, 29.69, 'sand')
volve_clay = Mineral(2.78, 28.45, 7.95, 'clay')
vrh_avg = VRHAvg(["vsand", "vclay"], [volve_sand, volve_clay])
from digirock import WaterBW92, OilPVT, WoodsFluid, Mineral, VRHAvg
# FLUID PROPS
temp = 110 # reservoir temperature in degC
sal = 151200 # reservoir brine salinity ppm
pres = 32 # reseroir insitu pressure MPa
volve_brine = WaterBW92(salinity=sal)
volve_oil = OilPVT(std_density=0.885, gas_sg=1.09956)
volve_oil.set_pvt(140, bo_tab["bo"].values, bo_tab["pres"].values)
fl = WoodsFluid(["sw", "so"], [volve_brine, volve_oil])
# MATRIX PROPS
volve_sand = Mineral(2.634, 31.55, 29.69, 'sand')
volve_clay = Mineral(2.78, 28.45, 7.95, 'clay')
vrh_avg = VRHAvg(["vsand", "vclay"], [volve_sand, volve_clay])
Create the Gassmann substitution Blender¶
To perform the fluid substitution, a class which blends the props data is needed. Here we build a simple example of a GassmannSub class but it could be arbitrarilly more complex.
In [6]:
Copied!
from digirock import Blend
from digirock.models import dryframe_acoustic, gassmann_fluidsub
from digirock.elastic import acoustic_bulk_moduli, acoustic_shear_moduli, acoustic_velp, acoustic_vels
class GassmannSub(Blend):
_methods = ["vp", "vs", "density", "bulk_modulus", "shear_modulus"]
def __init__(
self,
zero_porosity_model,
fluid_model,
sw_key="sw",
vp_key="vp",
vs_key="vs",
rho_key="rho",
poro_key="poro",
name=None
):
elements = [zero_porosity_model, fluid_model]
blend_keys = [sw_key, vp_key, vs_key, rho_key, poro_key]
super().__init__(blend_keys, elements, self._methods, name=name)
def _get_elastic_keys(self):
return self.blend_keys[1:4]
def dry_bulk_modulus(self, props, **kwargs):
kfl = self.elements[1].bulk_modulus(props, **kwargs)
k0 = self.elements[0].bulk_modulus(props, **kwargs)
vpk, vsk, rk = self._get_elastic_keys()
porok = self.blend_keys[4]
ksat = acoustic_bulk_moduli(props[vpk], props[vsk], props[rk])
kdry = dryframe_acoustic(ksat, kfl, k0, props[porok])
return kdry
def bulk_modulus(self, props, props2, **kwargs):
kdry = self.dry_bulk_modulus(props)
kfl = self.elements[1].bulk_modulus(props2, **kwargs)
k0 = self.elements[0].bulk_modulus(props2, **kwargs)
porok = self.blend_keys[4]
return gassmann_fluidsub(kdry, kfl, k0, props2[porok])
def shear_modulus(self, props, **kwargs):
return acoustic_shear_moduli(props[self.blend_keys[2]], props[self.blend_keys[3]])
def density(self, props, props2, **kwargs):
porok = self.blend_keys[4]
vpk, vsk, rk = self._get_elastic_keys()
kfl1 = self.elements[1].density(props, **kwargs)
den_min = props[rk] - props[porok] * kfl1
kfl2 = self.elements[1].density(props2, **kwargs)
return den_min + props2[porok] * kfl2
def vp(self, props, props2, **kwargs):
k = self.bulk_modulus(props, props2, **kwargs)
mu = self.shear_modulus(props, **kwargs)
den = self.density(props, props2, **kwargs)
return acoustic_velp(k, mu, den)
def vs(self, props, props2, **kwargs):
mu = self.shear_modulus(props, **kwargs)
den = self.density(props, props2, **kwargs)
return acoustic_vels(mu, den)
test_props = {"sw":0.5, "pres":pres, "temp":temp, "vclay":0.5, "poro":0.2, "vp":3500, "vs":2500, "rho":2.2}
test_props2 = {"sw":1.0, "pres":pres, "temp":temp, "vclay":0.5, "poro":0.2}
gassub = GassmannSub(vrh_avg, fl)
print("Dry Bulk Mod: ", gassub.dry_bulk_modulus(test_props))
print("Subbed Bulk Mod: ", gassub.bulk_modulus(test_props, test_props2))
print("Subbed Shear Mod: ", gassub.shear_modulus(test_props))
print("Subbed Density: ", gassub.density(test_props, test_props2))
print("Subbed VP: ", gassub.vp(test_props, test_props2))
print("Subbed VS: ", gassub.vs(test_props, test_props2))
from digirock import Blend
from digirock.models import dryframe_acoustic, gassmann_fluidsub
from digirock.elastic import acoustic_bulk_moduli, acoustic_shear_moduli, acoustic_velp, acoustic_vels
class GassmannSub(Blend):
_methods = ["vp", "vs", "density", "bulk_modulus", "shear_modulus"]
def __init__(
self,
zero_porosity_model,
fluid_model,
sw_key="sw",
vp_key="vp",
vs_key="vs",
rho_key="rho",
poro_key="poro",
name=None
):
elements = [zero_porosity_model, fluid_model]
blend_keys = [sw_key, vp_key, vs_key, rho_key, poro_key]
super().__init__(blend_keys, elements, self._methods, name=name)
def _get_elastic_keys(self):
return self.blend_keys[1:4]
def dry_bulk_modulus(self, props, **kwargs):
kfl = self.elements[1].bulk_modulus(props, **kwargs)
k0 = self.elements[0].bulk_modulus(props, **kwargs)
vpk, vsk, rk = self._get_elastic_keys()
porok = self.blend_keys[4]
ksat = acoustic_bulk_moduli(props[vpk], props[vsk], props[rk])
kdry = dryframe_acoustic(ksat, kfl, k0, props[porok])
return kdry
def bulk_modulus(self, props, props2, **kwargs):
kdry = self.dry_bulk_modulus(props)
kfl = self.elements[1].bulk_modulus(props2, **kwargs)
k0 = self.elements[0].bulk_modulus(props2, **kwargs)
porok = self.blend_keys[4]
return gassmann_fluidsub(kdry, kfl, k0, props2[porok])
def shear_modulus(self, props, **kwargs):
return acoustic_shear_moduli(props[self.blend_keys[2]], props[self.blend_keys[3]])
def density(self, props, props2, **kwargs):
porok = self.blend_keys[4]
vpk, vsk, rk = self._get_elastic_keys()
kfl1 = self.elements[1].density(props, **kwargs)
den_min = props[rk] - props[porok] * kfl1
kfl2 = self.elements[1].density(props2, **kwargs)
return den_min + props2[porok] * kfl2
def vp(self, props, props2, **kwargs):
k = self.bulk_modulus(props, props2, **kwargs)
mu = self.shear_modulus(props, **kwargs)
den = self.density(props, props2, **kwargs)
return acoustic_velp(k, mu, den)
def vs(self, props, props2, **kwargs):
mu = self.shear_modulus(props, **kwargs)
den = self.density(props, props2, **kwargs)
return acoustic_vels(mu, den)
test_props = {"sw":0.5, "pres":pres, "temp":temp, "vclay":0.5, "poro":0.2, "vp":3500, "vs":2500, "rho":2.2}
test_props2 = {"sw":1.0, "pres":pres, "temp":temp, "vclay":0.5, "poro":0.2}
gassub = GassmannSub(vrh_avg, fl)
print("Dry Bulk Mod: ", gassub.dry_bulk_modulus(test_props))
print("Subbed Bulk Mod: ", gassub.bulk_modulus(test_props, test_props2))
print("Subbed Shear Mod: ", gassub.shear_modulus(test_props))
print("Subbed Density: ", gassub.density(test_props, test_props2))
print("Subbed VP: ", gassub.vp(test_props, test_props2))
print("Subbed VS: ", gassub.vs(test_props, test_props2))
Dry Bulk Mod: [6.15444211] Subbed Bulk Mod: [9.21680372] Subbed Shear Mod: 13.750000000000002 Subbed Density: [2.23761226] Subbed VP: [3508.88766769] Subbed VS: [2478.89957096]
In [7]:
Copied!
gassub.tree
gassub.tree
Out[7]:
GassmannSub_140699708089200
├── class : <class '__main__.GassmannSub'>
├── props_keys : ['sw', 'vp', 'vs', 'rho', 'poro']
├── blend_keys : ['sw', 'vp', 'vs', 'rho', 'poro']
├── methods : ('vp', 'vs', 'density', 'bulk_modulus', 'shear_modulus')
├── n_elements : 2
├── VRHAvg_140699708275440
│ ├── class : <class 'digirock._frames._frames.VRHAvg'>
│ ├── props_keys : ['vsand', 'vclay']
│ ├── blend_keys : ['vsand', 'vclay']
│ ├── methods : ('density', 'vp', 'vs', 'shear_modulus', 'bulk_modulus')
│ ├── n_elements : 2
│ └── elements
│ ├── sand
│ │ ├── class : <class 'digirock._frames._minerals.Mineral'>
│ │ ├── props_keys : []
│ │ ├── bulk_modulus : 31.55
│ │ ├── shear_modulus : 29.69
│ │ ├── dens : 2.634
│ │ ├── vp : 5196.834306908865
│ │ └── vs : 3357.3546009435527
│ └── clay
│ ├── class : <class 'digirock._frames._minerals.Mineral'>
│ ├── props_keys : []
│ ├── bulk_modulus : 28.45
│ ├── shear_modulus : 7.95
│ ├── dens : 2.78
│ ├── vp : 3747.9010912680255
│ └── vs : 1691.0683694681975
└── WoodsFluid_140699708274480
├── class : <class 'digirock._fluids._fluid_mod.WoodsFluid'>
├── props_keys : ['sw', 'so']
├── blend_keys : ['sw', 'so']
├── methods : ('density', 'bulk_modulus', 'shear_modulus', 'velocity')
├── n_elements : 2
└── elements
├── WaterBW92_140699708272752
│ ├── class : <class 'digirock._fluids._water.WaterBW92'>
│ ├── props_keys : ['temp', 'pres']
│ └── salinity : 0.1512
└── OilPVT_140699708274528
├── class : <class 'digirock._fluids._oil.OilPVT'>
├── props_keys : ['bo']
├── api : 28.38700564971751
├── std_density : 0.885
├── gas_sg : 1.09956
└── pvt
├── type : fixed_rs
├── rs : 140
├── bo : table
├── pres : table
├── bo_table : <xarray.DataArray (rs: 1, pres: 14)>
│ array([[1.17 , 1.248, 1.322, 1.398, 1.483, 1.477, 1.467, 1.456, 1.445,
│ 1.435, 1.427, 1.419, 1.411, 1.405]])
│ Coordinates:
│ * rs (rs) int64 140
│ * pres (pres) float64 5.0 10.0 15.0 20.0 25.0 ... 45.0 50.0 55.0
│ 60.0 65.0
├── bo_min : 1.17
├── bo_max : 1.483
├── pres_min : 5.0
└── pres_max : 65.0
Performing fluid substitution on the logs¶
We simply need to define the insitu state props1 and the state at which we wish to calculate the new logs props2. And these can be passed to gassub, our model class, which handles the details of the fluid substitution.
In [8]:
Copied!
fig, axs = plt.subplots(ncols=5, figsize=(10,6), sharey=True)
sub = logs[np.logical_and(logs.index > 3250, logs.index < 3420)]
axs[0].plot(sub["VSH"], -sub.index, color="green")
axs[1].plot(sub["SW_SURVEY1"], -sub.index)
axs[1].plot(sub["SW_SURVEY2"], -sub.index)
axs[2].plot(sub["VELP"], -sub.index)
axs[3].plot(sub["VELS"], -sub.index)
axs[4].plot(sub["DENS"], -sub.index)
props1 = {
"pres":pres,
"temp":temp,
"poro":sub["PORO"].values,
"vclay":sub["VSH"].values,
"vp":sub["VELP"].values,
"vs":sub["VELS"].values,
"rho":sub["DENS"].values,
"sw":sub["SW_SURVEY1"].values
}
props2 = props1.copy()
props2["sw"] = sub["SW_SURVEY2"].values
vp2 = gassub.vp(props1, props2)
vs2 = gassub.vs(props1, props2)
rho2 = gassub.density(props1, props2)
axs[2].plot(vp2, -sub.index)
axs[3].plot(vs2, -sub.index)
axs[4].plot(rho2, -sub.index)
axs[0].set_title("VSH")
axs[1].set_title("SW1, SW2")
axs[2].set_title("VP1, VP2")
axs[3].set_title("VS1, VS2")
axs[4].set_title("RHOB1, RHOB2")
fig, axs = plt.subplots(ncols=5, figsize=(10,6), sharey=True)
sub = logs[np.logical_and(logs.index > 3250, logs.index < 3420)]
axs[0].plot(sub["VSH"], -sub.index, color="green")
axs[1].plot(sub["SW_SURVEY1"], -sub.index)
axs[1].plot(sub["SW_SURVEY2"], -sub.index)
axs[2].plot(sub["VELP"], -sub.index)
axs[3].plot(sub["VELS"], -sub.index)
axs[4].plot(sub["DENS"], -sub.index)
props1 = {
"pres":pres,
"temp":temp,
"poro":sub["PORO"].values,
"vclay":sub["VSH"].values,
"vp":sub["VELP"].values,
"vs":sub["VELS"].values,
"rho":sub["DENS"].values,
"sw":sub["SW_SURVEY1"].values
}
props2 = props1.copy()
props2["sw"] = sub["SW_SURVEY2"].values
vp2 = gassub.vp(props1, props2)
vs2 = gassub.vs(props1, props2)
rho2 = gassub.density(props1, props2)
axs[2].plot(vp2, -sub.index)
axs[3].plot(vs2, -sub.index)
axs[4].plot(rho2, -sub.index)
axs[0].set_title("VSH")
axs[1].set_title("SW1, SW2")
axs[2].set_title("VP1, VP2")
axs[3].set_title("VS1, VS2")
axs[4].set_title("RHOB1, RHOB2")
Out[8]:
Text(0.5, 1.0, 'RHOB1, RHOB2')