%load_ext autoreload
%autoreload 2
Fluids¶
All fluids have four basic methods:
get_summary
which returns details of the fluiddensity
which returns the density of the fluid with minimum arguments of temperature and pressure.velocity
which returns the acoustic velocity of the fluid with minimum arguments of temperature and pressure.modulus
which returns the modulus of the fluid with minimum arguments of temperature and pressure.
Additional keyword arguments to each method can be added as differentiators when fluids become more complex, for example when there are PVT zones.
Water Types¶
Classes for Water are based upon the Batzle and Wang 92 equations or with PVTW
can be modified to use a table for the formation volume factor (FVF).
import numpy as np
import os
import pathlib
from digirock.datasets import fetch_example_data
cur_dir = os.path.abspath("")
parent_path = pathlib.Path(cur_dir)
print(parent_path)
# Presure in MPa
p = 50
pres = np.linspace(10, 100, 10)
# Temperature in degC
t = 110
temp = np.linspace(80, 150, 10)
# fetch all the example data
example_data = fetch_example_data()
/home/runner/work/digirock/digirock
from digirock import WaterBW92, WaterECL, load_pvtw
# Initialisation of BW92 Water requires the salinity in PPM.
wat = WaterBW92(name="water", salinity=0)
# check the summary - note the input salinity has been converted from PPM to ...
print(wat.get_summary())
wat.tree
{'class': <class 'digirock._fluids._water.WaterBW92'>, 'name': 'water', 'props_keys': ['temp', 'pres'], 'salinity': 0.0}
water ├── class : <class 'digirock._fluids._water.WaterBW92'> ├── props_keys : ['temp', 'pres'] └── salinity : 0.0
Then let's check the elastic properties of this water with a mixture of constants and arrays or both.
props = dict(temp=t, pres=p)
props_ar1 = dict(temp=t, pres=pres)
props_ar2 = dict(temp=temp, pres=pres)
props_ar3 = dict(temp=temp.reshape(2, -1), pres=pres.reshape(2, -1))
# density
print("Density single values (g/cc):", wat.density(props))
print("Density 1 array values (g/cc):", wat.density(props_ar1))
# arrays can be used, but they must be the same shape
print("Density 2 array values (g/cc):", wat.density(props_ar2))
print("Density 2 array values (g/cc):", wat.density(props_ar3), "\n")
# velocity
print("Velocity single values (m/s):", wat.density(props))
print("Velocity 1 array values (m/s):", wat.density(props_ar1))
# arrays can be used, but they must be the same shape
print("Velocity 2 array values (m/s):", wat.density(props_ar2))
print("Velocity 2 array values (m/s):", wat.density(props_ar3), "\n")
# modulus
print("Modulus single values (GPa):", wat.density(props))
print("Modulus 1 array values (GPa):", wat.density(props_ar1))
# arrays can be used, but they must be the same shape
print("Modulus 2 array values (GPa):", wat.density(props_ar2))
print("Modulus 2 array values (GPa):", wat.density(props_ar3))
Density single values (g/cc): 0.9744816 Density 1 array values (g/cc): [0.95799692 0.96228399 0.96646046 0.97052633 0.9744816 0.97832627 0.98206034 0.98568381 0.98919668 0.99259895] Density 2 array values (g/cc): [0.97757414 0.97709025 0.97625809 0.97511746 0.97370666 0.97206256 0.97022054 0.96821451 0.96607692 0.96383875] Density 2 array values (g/cc): [[0.97757414 0.97709025 0.97625809 0.97511746 0.97370666] [0.97206256 0.97022054 0.96821451 0.96607692 0.96383875]] Velocity single values (m/s): 0.9744816 Velocity 1 array values (m/s): [0.95799692 0.96228399 0.96646046 0.97052633 0.9744816 0.97832627 0.98206034 0.98568381 0.98919668 0.99259895] Velocity 2 array values (m/s): [0.97757414 0.97709025 0.97625809 0.97511746 0.97370666 0.97206256 0.97022054 0.96821451 0.96607692 0.96383875] Velocity 2 array values (m/s): [[0.97757414 0.97709025 0.97625809 0.97511746 0.97370666] [0.97206256 0.97022054 0.96821451 0.96607692 0.96383875]] Modulus single values (GPa): 0.9744816 Modulus 1 array values (GPa): [0.95799692 0.96228399 0.96646046 0.97052633 0.9744816 0.97832627 0.98206034 0.98568381 0.98919668 0.99259895] Modulus 2 array values (GPa): [0.97757414 0.97709025 0.97625809 0.97511746 0.97370666 0.97206256 0.97022054 0.96821451 0.96607692 0.96383875] Modulus 2 array values (GPa): [[0.97757414 0.97709025 0.97625809 0.97511746 0.97370666] [0.97206256 0.97022054 0.96821451 0.96607692 0.96383875]]
An inbuilt class exists for using PVTW tables from Eclipse include files. The density is then calculated using the Eclipse formula.
# load the Eclipse table directly from a text file
wat_pvtw = load_pvtw(example_data["COMPLEX_PVT.inc"], salinity=0)
# look at the first value of the loaded table - there is one value for each of the 13 PVT zones in this example
print(wat_pvtw["pvtw0"].get_summary())
wat_pvtw["pvtw0"].tree
{'class': <class 'digirock._fluids._water.WaterECL'>, 'name': 'pvtw0', 'props_keys': ['temp', 'pres'], 'salinity': 0.1423666294070789, 'ref_pres': 26.85, 'bw': 1.03382, 'comp': 0.00031288999999999997, 'visc': 0.38509, 'cvisc': 0.0009780099999999999, 'density_asc': 1.1012673958726442}
pvtw0 ├── class : <class 'digirock._fluids._water.WaterECL'> ├── props_keys : ['temp', 'pres'] ├── salinity : 0.1423666294070789 ├── ref_pres : 26.85 ├── bw : 1.03382 ├── comp : 0.00031288999999999997 ├── visc : 0.38509 ├── cvisc : 0.0009780099999999999 └── density_asc : 1.1012673958726442
Let's look at the denisty for the first table entry that was loaded for this PVTW.
pvtw0 = wat_pvtw["pvtw0"]
# we need to tell the fluid which pvt table to use either with each call to a method
print("Density single values (g/cc):", pvtw0.density(props))
# with arrays
print("Density array values (g/cc):", pvtw0.density(props_ar1), "\n")
print("Bulk Modulus array values (g/cc):", pvtw0.bulk_modulus(props_ar2), "\n")
Density single values (g/cc): 1.1185960024862598 Density array values (g/cc): [1.10468344 1.10814528 1.11161798 1.11510155 1.118596 1.12210132 1.12561751 1.12914458 1.13268251 1.13623132] Bulk Modulus array values (g/cc): [3.11532512 3.18369362 3.25225255 3.31788512 3.37855127 3.4341293 3.48720719 3.54377924 3.61384577 3.71198247]
Oil Types¶
from digirock import DeadOil, OilBW92, OilPVT, load_pvto
import numpy as np
DeadOil
is a class for fluids with no dissolved gas and it is initialised by either specifying an oil API or standard density.
doil_api = DeadOil(api=35)
print(doil_api.get_summary())
doil_sd = DeadOil(std_density=0.84985)
print(doil_sd.get_summary())
doil_sd.tree
{'class': <class 'digirock._fluids._oil.DeadOil'>, 'name': 'DeadOil_140592200243184', 'props_keys': ['bo'], 'api': 35, 'std_density': 0.8498498498498499, 'pvt': None} {'class': <class 'digirock._fluids._oil.DeadOil'>, 'name': 'DeadOil_140593213050304', 'props_keys': ['bo'], 'api': 34.999970583044075, 'std_density': 0.84985, 'pvt': None}
DeadOil_140593213050304 ├── class : <class 'digirock._fluids._oil.DeadOil'> ├── props_keys : ['bo'] ├── api : 34.999970583044075 ├── std_density : 0.84985 └── pvt : None
Note that pvt
is mentioned in the summary but isn't yet set. Default behaviour for DeadOil
is to calculate the formation volume factor (fvf) using a constant or a presure and FVF table.
# set a constant bo
doil_api.set_pvt(1.1)
doil_api.tree
DeadOil_140592200243184 ├── class : <class 'digirock._fluids._oil.DeadOil'> ├── props_keys : ['bo'] ├── api : 35 ├── std_density : 0.8498498498498499 └── pvt : {'type': 'constant', 'bo': 1.1}
# set a bo pres table
doil_sd.set_pvt(np.linspace(1, 1.1, 11), pres=np.linspace(0, 50, 11))
doil_sd.tree
DeadOil_140593213050304 ├── class : <class 'digirock._fluids._oil.DeadOil'> ├── props_keys : ['bo'] ├── api : 34.999970583044075 ├── std_density : 0.84985 └── pvt : {'type': 'table', 'bo': 'table', 'pres': 'table', 'bo_table': <xarray.DataArray (pres: 11)> array([1. , 1.01, 1.02, 1.03, 1.04, 1.05, 1.06, 1.07, 1.08, 1.09, 1.1 ]) Coordinates: * pres (pres) float64 0.0 5.0 10.0 15.0 20.0 25.0 30.0 35.0 40.0 45.0 50.0}
An oil with gas in it can be created using the OilBW92
class. This class uses the BW92 equations to calculate elastic properties. There are many different ways to calculate the elastic properties with this class.
- Explicit values for FVF
bo
and solution gasrs
can be passed in theprops
keyword to the BW92 elastic equations. - An RS to Pressure table can be set using the
set_rst
method. This allows the solution gas to be pressure dependent. Thers
andbo
props are then not required.bo
is calculated per BW92. - A constatnt RS can be set using the
set_rst
method. Thers
andbo
props are then not required.bo
is calculated per BW92 for constantrs
.
An example with no rs
table.
bwoil_norst = OilBW92("norst", api=35, gas_sg=0.6)
display(bwoil_norst.tree)
display(bwoil_norst.density(dict(temp=110, pres=50, rs=110, bo=1.1)))
norst ├── class : <class 'digirock._fluids._oil.OilBW92'> ├── props_keys : ['bo', 'rs'] ├── api : 35 ├── std_density : 0.8498498498498499 └── rst : None
array([0.80400771])
An example with a constant rs
. Note, that bo
and rs
are no longer required in props
, but if we pass them they will overwrite the use of the table.
bwoil_rsc = OilBW92("norst", api=35, gas_sg=0.6)
bwoil_rsc.set_rst(120)
display(bwoil_rsc.tree)
print("using set table: ", bwoil_rsc.density(dict(temp=110, pres=50)))
print(
"using properties: ", bwoil_rsc.density(dict(temp=110, pres=50, rs=110, bo=1.1))
) # overwrite table rs and bw92 bo
norst ├── class : <class 'digirock._fluids._oil.OilBW92'> ├── props_keys : ['bo', 'rs'] ├── api : 35 ├── std_density : 0.8498498498498499 └── rst : {'type': 'constant', 'rs': 120, 'pres': None}
using set table: [0.66999975] using properties: [0.80400771]
Finally, we can make rs
pressure dependent by passing a table to set_rst
.
bwoil_rst = OilBW92("norst", api=35, gas_sg=0.6)
bwoil_rst.set_rst([80, 100, 120], pres=[10, 40, 100])
display(bwoil_rst.tree)
print("using set table: ", bwoil_rst.density(dict(temp=110, pres=50)))
norst ├── class : <class 'digirock._fluids._oil.OilBW92'> ├── props_keys : ['bo', 'rs'] ├── api : 35 ├── std_density : 0.8498498498498499 └── rst : {'type': 'table', 'rs': 'table', 'pres': 'table', 'rs_table': <xarray.DataArray (pres: 3)> array([ 80, 100, 120]) Coordinates: * pres (pres) int64 10 40 100}
using set table: [0.68034298]
When the rst
has been set in some manner it is possible to query rs
and bo
directly. Note, rs
only needs the pressure.
print("Bo: ", bwoil_rst.bo({"temp": 110, "pres": 50}))
print("Rs: ", bwoil_rst.rs({"pres": 50}))
Bo: 1.3255946390999833 Rs: 103.33333333333333
If you have an Eclipse PVTO table you can load those oil properties using load_pvto
.
# load the Eclipse table directly from a text file
pvtos = load_pvto(example_data["COMPLEX_PVT.inc"], api=40)
pvtos
is a dictionary, one for each pvto table. The returned fluid has the OilPVT
class. This uses the BW92 equations for elastic properties, but eclusively uses rs
and bo
calculated from the tables loaded into the class.
print(pvtos.keys())
dict_keys(['pvto0', 'pvto1', 'pvto2', 'pvto3', 'pvto4', 'pvto5', 'pvto6', 'pvto7', 'pvto8', 'pvto9', 'pvto10', 'pvto11'])
Access the individual pvt fluids using the appropriate key.
ecloil0 = pvtos["pvto0"]
ecloil0.tree
pvto0 ├── class : <class 'digirock._fluids._oil.OilPVT'> ├── props_keys : ['bo'] ├── api : 40 ├── std_density : 0.8250728862973761 ├── pvt : {'type': 'full', 'rs': 'table', 'bo': 'table', 'pres': 'table', 'bo_table': │ <xarray.DataArray (rs: 6, pres: 14)> │ array([[1.17014, 1.16296, 1.15664, 1.15103, 1.14598, 1.14425, 1.14143, │ 1.13728, 1.13349, 1.13001, 1.12679, 1.12381, 1.12103, 1.11844], │ [ nan, 1.24802, 1.23902, 1.23114, 1.22415, 1.22175, 1.21789, │ 1.21225, 1.20713, 1.20245, 1.19815, 1.19419, 1.19052, 1.18712], │ [ nan, nan, 1.32163, 1.31096, 1.30163, 1.29846, 1.29336, │ 1.28597, 1.27932, 1.27328, 1.26776, 1.26271, 1.25804, 1.25373], │ [ nan, nan, nan, 1.39808, 1.38574, 1.38158, 1.37494, │ 1.36539, 1.35685, 1.34916, 1.34218, 1.33581, 1.32997, 1.32459], │ [ nan, nan, nan, nan, 1.48274, 1.47728, 1.4686 , │ 1.45623, 1.44528, 1.43549, 1.42667, 1.41867, 1.41136, 1.40466], │ [ nan, nan, nan, nan, nan, 1.51746, 1.50785, │ 1.49419, 1.48215, 1.47142, 1.46178, 1.45306, 1.44511, 1.43783]]) │ Coordinates: │ * pres (pres) float64 5.0 10.0 15.0 20.0 25.0 ... 45.0 50.0 55.0 60.0 65.0 │ * rs (rs) float64 27.4 54.3 81.5 110.7 143.5 156.9, 'bo_min': array(1.11844), │ 'bo_max': array(1.51746), 'pres_min': array(5.), 'pres_max': array(65.), 'rs_min': │ array(27.4), 'rs_max': array(156.9)} └── gas_sg : 0.9012786885245903
DataArrays are easy to plot
ecloil0.pvt["bo_table"].plot()
<matplotlib.collections.QuadMesh at 0x7fde2920d430>
We can get the value of bo for any pres
and rs
combination. Eclipse100 PVT models are not temperature dependent for bo
.
print("Bo: ", ecloil0.bo({"pres": 50, "rs": np.array([[100, 120], [100, 120]])}))
Bo: [[1.31490966 1.36613601] [1.31490966 1.36613601]]
Getting elastic properties
props = dict(temp=np.r_[100, 110], pres=np.r_[50, 60], rs=np.r_[100, 150])
print("Density: ", ecloil0.density(props))
props = dict(temp=110, pres=50, rs=100)
print("Velocity: ", ecloil0.velocity(props))
print("Bulk Modulus: ", ecloil0.bulk_modulus(props))
Density: [0.69731684 0.68120537] Velocity: [1119.2434647] Bulk Modulus: [0.86517527]
Gas Types¶