Skip to content

Fluid Methods

Batzle and Wang 92

Python
from digirock.fluids import bw92

Function for calculating fluid properties in a reservoir

Symbols Definitions Units Function/s
rhow Density of brine g/cm3
rhog Density of gas g/cm3
rhoo Density of oil g/cm3
rho0 Reference Density of Oil g/cm3
kwat Bulk modulus of water GPa
kgas Bulk modulus of gas GPa
koil Bulk modulus of oil GPa
g Specific gravity of gas API
p In-situ pressure MPa
t In-situ temperature degC
sal Salinity Wt.fraction
sw Water Saturation fraction
rg Gas-to-Oil ratio (GOR) Litre/litre
gas_R Gas constant m3 Pa K-1 mol-1

Notes

  • The gas specific gravity G is the ratio of the gas density to air density at 15.6 degC and at atmospheric pressure. Typically gases have G values from 0.56 (Methane) to 1.8

  • Salinity has units that can be converted as such 35 g dissolved salt / kg sea water = 35 ppt = 35 o/oo = 3.5% = 35000 ppm = 35000 mg/l

Constant Variable Name Units Value
Gas Constant GAS_R (J K-1 mol-1) (m3 Pa K-1 mol-1) 8.31441
Atmospheric Pressures STD_ATM_PRESS Pa 101325
Molecular Weight of Air AIR_M_WEIGHT g/mol 28.967
Density of air at STD AIR_RHO g/m3 AIR_M_WEIGHT * STD_ATM_PRESS / ((15.6 + 273.15) * GAS_R)

These functions are based upon the work by:

  1. Batzle and Wang, 1992, Seismic Properties of Pore Fluids
  2. Kumar, D, 2006, A Tutorial on Gassmann Fluid Substitution: Formulation, Algorithm and Matlab Code

bulkmod(rho, vel)

Single phase fluid bulk modulus

\[ \kappa_M = \rho v_p^2 \]

Parameters:

Name Type Description Default
rho Union[numpy.ndarray, float]

bulk density (g/cc)

required
vel Union[numpy.ndarray, float]

material velocity (m/s)

required

Returns:

Type Description
Union[numpy.ndarray, float]

material bulk modulus (GPa)

Source code in digirock/fluids/bw92.py
Python
def bulkmod(rho: NDArrayOrFloat, vel: NDArrayOrFloat) -> NDArrayOrFloat:
    """Single phase fluid bulk modulus

    $$
    \kappa_M = \\rho v_p^2
    $$

    Args:
        rho: bulk density (g/cc)
        vel: material velocity (m/s)

    Returns:
        material bulk modulus (GPa)
    """
    return rho * np.power(vel, 2) * 1e-6

gas_adiabatic_bulkmod(t, p, g)

Calculates the approximate adiabatic (constant pressure) bulk modulus of a gas

Parameters:

Name Type Description Default
t Union[numpy.ndarray, float]

Gas temperature (degC)

required
p Union[numpy.ndarray, float]

The gas pressure (MPa)

required
g Union[numpy.ndarray, float]

The gas specific gravity

required

Returns:

Type Description
Union[numpy.ndarray, float]

approximate gas bulk modulus in GPa

Source code in digirock/fluids/bw92.py
Python
def gas_adiabatic_bulkmod(
    t: NDArrayOrFloat, p: NDArrayOrFloat, g: NDArrayOrFloat
) -> NDArrayOrFloat:
    """Calculates the approximate adiabatic (constant pressure) bulk modulus of a gas

    Args:
        t: Gas temperature (degC)
        p: The gas pressure (MPa)
        g: The gas specific gravity

    Returns:
        approximate gas bulk modulus in GPa

    """
    ppr = gas_pseudored_pres(p, g)
    tpr = gas_pseudored_temp(t, g)
    a = 0.03 + 0.00527 * (3.5 - tpr) ** 3
    b = 0.642 * tpr - 0.007 * tpr**4 - 0.52
    c = 0.109 * (3.85 - tpr) ** 2
    d = np.exp(-1 * (0.45 + 8 * (0.56 - 1 / tpr) ** 2) * (ppr**1.2) / tpr)
    gamma = (
        0.85
        + 5.6 / (ppr + 2)
        + 27.1 / np.square(ppr + 3.5)
        - 8.7 * np.exp(-0.65 * (ppr + 1))
    )
    m = 1.2 * (-1 * (0.45 + 8 * (0.56 - 1 / tpr) ** 2) * (ppr**0.2) / tpr)
    f = c * d * m + a
    Z = a * ppr + b + c * d
    return p * gamma * 1e-3 / (1 - ppr * f / Z)

gas_adiabatic_viscosity(t, p, g)

Calculates the approximate adiabatic (constant pressure) viscoisty of a gas

Parameters:

Name Type Description Default
t Union[numpy.ndarray, float]

The gas temperature (degC)

required
p Union[numpy.ndarray, float]

The gas pressure in (MPa)

required
g Union[numpy.ndarray, float]

The gas specific gravity

required

Returns:

Type Description
Union[numpy.ndarray, float]

approximate gas bulk modulus in centipoise

Source code in digirock/fluids/bw92.py
Python
def gas_adiabatic_viscosity(
    t: NDArrayOrFloat, p: NDArrayOrFloat, g: NDArrayOrFloat
) -> NDArrayOrFloat:
    """Calculates the approximate adiabatic (constant pressure) viscoisty of a gas

    Args:
        t: The gas temperature (degC)
        p: The gas pressure in (MPa)
        g: The gas specific gravity

    Returns:
        approximate gas bulk modulus in centipoise

    """
    ppr = gas_pseudored_pres(p, g)
    tpr = gas_pseudored_temp(t, g)
    eta1 = 0.0001 * (
        tpr * (28 + 48 * g - 5 * g**2) - 6.47 * g ** (-2) + 35 / g + 1.14 * g - 15.55
    )
    eta2diveta1 = (
        0.001
        * ppr
        * (
            (1057 - 8.08 * tpr) / ppr
            + (796 * np.power(ppr, 0.5) - 704) / (np.power(tpr - 1, 0.7) * (ppr + 1))
            - 3.24 * tpr
            - 38
        )
    )
    return eta2diveta1 * eta1

gas_density(m, t, p)

Calculates the density for and ideal gas

B&W 1992 Eq 2

Parameters:

Name Type Description Default
m Union[numpy.ndarray, float]

Molecular weight of the gas.

required
t Union[numpy.ndarray, float]

Temperature of the gas (degC)

required
p Union[numpy.ndarray, float]

Pressure of the gas in (Pa)

required

Returns:

Type Description
Union[numpy.ndarray, float]

the molar weight of the gas for t and p

Source code in digirock/fluids/bw92.py
Python
def gas_density(
    m: NDArrayOrFloat, t: NDArrayOrFloat, p: NDArrayOrFloat
) -> NDArrayOrFloat:
    """Calculates the density for and ideal gas

    B&W 1992 Eq 2

    Args:
        m: Molecular weight of the gas.
        t: Temperature of the gas (degC)
        p: Pressure of the gas in (Pa)

    Returns:
        the molar weight of the gas for t and p

    """
    return np.divide(np.multiply(m, p), (t + 273.15) * GAS_R)

gas_isotherm_comp(v1, v2, p1, p2)

Calculates the isothermal compressibility of an ideal gas

Two points of corresponding molecular volume and pressure must be known to calculate Bt for a given constant temperature. B&W 1992 Eq 3

Parameters:

Name Type Description Default
v1 Union[numpy.ndarray, float]

The first molecular volume of the gas

required
v2 Union[numpy.ndarray, float]

The second molecular volume of the gas

required
p1 Union[numpy.ndarray, float]

The first pressure of the gas (Pa)

required
p2 Union[numpy.ndarray, float]

The second pressure of the gas (Pa)

required

Returns:

Type Description
Union[numpy.ndarray, float]

the isothermal compressibility [Bt]

Source code in digirock/fluids/bw92.py
Python
def gas_isotherm_comp(
    v1: NDArrayOrFloat, v2: NDArrayOrFloat, p1: NDArrayOrFloat, p2: NDArrayOrFloat
) -> NDArrayOrFloat:
    """Calculates the isothermal compressibility of an ideal gas

    Two points of corresponding molecular volume and pressure must be known to calculate Bt
    for a given constant temperature.
    B&W 1992 Eq 3

    Args:
        v1: The first molecular volume of the gas
        v2: The second molecular volume of the gas
        p1: The first pressure of the gas (Pa)
        p2: The second pressure of the gas (Pa)

    Returns:
        the isothermal compressibility [Bt]
    """
    return -1 * (v2 - v1) / (p2 - p1) / v1

gas_isotherm_vp(m, t)

Calculates the isothermal velocity of a compressional wave for an ideal gas

B&W 1992 Eq 4

Parameters:

Name Type Description Default
m Union[numpy.ndarray, float]

Molecular weight of gas

required
t Union[numpy.ndarray, float]

Temperature of gas (degC)

required

Returns:

Type Description
Union[numpy.ndarray, float]

Isothermal compressional velocity for gas (m/s)

Source code in digirock/fluids/bw92.py
Python
def gas_isotherm_vp(m: NDArrayOrFloat, t: NDArrayOrFloat) -> NDArrayOrFloat:
    """Calculates the isothermal velocity of a compressional wave for an ideal gas

    B&W 1992 Eq 4

    Args:
        m: Molecular weight of gas
        t: Temperature of gas (degC)

    Returns:
        Isothermal compressional velocity for gas (m/s)
    """
    return np.sqrt(GAS_R * (t + 273.15) / m)

gas_oga_density(t, p, g)

Calculates the approximate density of gas appropriate to the oil and gas industry

Suitable for temperatures and pressures typically encountered in the oil and gas industry. This approximation is adequate as long as ppr and tpr are not both within about 0.1 of unity. B&W 1992 Eq 10a

Parameters:

Name Type Description Default
t Union[numpy.ndarray, float]

The gas absolute temperature (degK)

required
p Union[numpy.ndarray, float]

The gas pressure (MPa)

required
g Union[numpy.ndarray, float]

The gas specific gravity

required

Returns:

Type Description
Union[numpy.ndarray, float]

the oil and gas appropriate approximate density (g/cc)

Source code in digirock/fluids/bw92.py
Python
def gas_oga_density(
    t: NDArrayOrFloat, p: NDArrayOrFloat, g: NDArrayOrFloat
) -> NDArrayOrFloat:
    """Calculates the approximate density of gas appropriate to the oil and gas industry

    Suitable for temperatures and pressures typically encountered in the oil and gas
    industry. This approximation is adequate as long as ppr and tpr are not both within
    about 0.1 of unity.
    B&W 1992 Eq 10a

    Args:
        t: The gas absolute temperature (degK)
        p: The gas pressure (MPa)
        g: The gas specific gravity

    Returns:
        the oil and gas appropriate approximate density (g/cc)

    """
    ppr = gas_pseudored_pres(p, g)
    tpr = gas_pseudored_temp(t, g)

    if np.any(np.logical_and(np.abs(ppr - 1) < 0.1, np.abs(tpr - 1) < 0.1)):
        warnings.warn(
            "Values for ppr and trp are both within 0.1 of unity, the gas \
            density assumptions will not be valid."
        )

    a = 0.03 + 0.00527 * (3.5 - tpr) ** 3
    b = 0.642 * tpr - 0.007 * tpr**4 - 0.52
    c = 0.109 * (3.85 - tpr) ** 2
    d = np.exp(-1 * (0.45 + 8 * (0.56 - 1 / tpr) ** 2) * (ppr**1.2) / tpr)

    E = c * d
    Z = a * ppr + b + E

    return 28.8 * np.divide(np.multiply(g, p), GAS_R * np.multiply(Z, (t + 273.15)))

gas_pseudocrit_pres(g)

Calculates the gas pseudo critical pressure value

B&W 1992 Eq 9a

Parameters:

Name Type Description Default
g Union[numpy.ndarray, float]

The gas specific gravity

required

Returns:

Type Description
Union[numpy.ndarray, float]

The pseudo critical pressure

Source code in digirock/fluids/bw92.py
Python
def gas_pseudocrit_pres(g: NDArrayOrFloat) -> NDArrayOrFloat:
    """Calculates the gas pseudo critical pressure value

    B&W 1992 Eq 9a

    Args:
        g: The gas specific gravity

    Returns:
        The pseudo critical pressure

    """
    return 4.892 - 0.4048 * g

gas_pseudocrit_temp(g)

Calculates the gas pseudo critical temperature value

B&W 1992 Eq 9b

Parameters:

Name Type Description Default
g Union[numpy.ndarray, float]

The gas specific gravity

required

Returns:

Type Description
Union[numpy.ndarray, float]

The pseudo critical temperature

Source code in digirock/fluids/bw92.py
Python
def gas_pseudocrit_temp(g: NDArrayOrFloat) -> NDArrayOrFloat:
    """Calculates the gas pseudo critical temperature value

    B&W 1992 Eq 9b

    Args:
        g: The gas specific gravity

    Returns:
        The pseudo critical temperature

    """
    return 94.72 + 170.75 * g

gas_pseudored_pres(p, g)

Calculates the gas pseudo reduced pressure value

B&W 1992 Eq 9a

Parameters:

Name Type Description Default
p Union[numpy.ndarray, float]

The gas pressure in (MPa)

required
g Union[numpy.ndarray, float]

The gas specific gravity

required

Returns:

Type Description
Union[numpy.ndarray, float]

the pseudo reduced or normalised pseudo critical pressure value

Source code in digirock/fluids/bw92.py
Python
def gas_pseudored_pres(p: NDArrayOrFloat, g: NDArrayOrFloat) -> NDArrayOrFloat:
    """Calculates the gas pseudo reduced pressure value

    B&W 1992 Eq 9a

    Args:
        p: The gas pressure in (MPa)
        g: The gas specific gravity

    Returns:
        the pseudo reduced or normalised pseudo critical pressure value

    """
    return p / gas_pseudocrit_pres(g)

gas_pseudored_temp(t, g)

Calculates the gas pseudo reduced pressure value

B&W 1992 Eq 9b

Parameters:

Name Type Description Default
t Union[numpy.ndarray, float]

The gas absolute temperature (degK)

required
g Union[numpy.ndarray, float]

The gas specific gravity

required

Returns:

Type Description
Union[numpy.ndarray, float]

the pseudo reduced or normalised pseudo critical temperature value

Source code in digirock/fluids/bw92.py
Python
def gas_pseudored_temp(t: NDArrayOrFloat, g: NDArrayOrFloat) -> NDArrayOrFloat:
    """Calculates the gas pseudo reduced pressure value

    B&W 1992 Eq 9b

    Args:
        t: The gas absolute temperature (degK)
        g: The gas specific gravity

    Returns:
        the pseudo reduced or normalised pseudo critical temperature value

    """
    return (t + 273.15) / gas_pseudocrit_temp(g)

gas_vmol(t, p)

Calculates molar volume for and ideal gas

B&W 1992 Eq 1

Parameters:

Name Type Description Default
t Union[numpy.ndarray, float]

Temperature of the gas (degC)

required
p Union[numpy.ndarray, float]

The pressure of the gas (Pa)

required

Returns:

Type Description
Union[numpy.ndarray, float]

the molar volume of the gas for t and p

Source code in digirock/fluids/bw92.py
Python
def gas_vmol(t: NDArrayOrFloat, p: NDArrayOrFloat) -> NDArrayOrFloat:
    """Calculates molar volume for and ideal gas

    B&W 1992 Eq 1

    Args:
        t: Temperature of the gas (degC)
        p: The pressure of the gas (Pa)

    Returns:
        the molar volume of the gas for t and p

    """
    return GAS_R * (t + 273.15) / p

mixed_density(den, frac, *argv)

Mixed fluid density \(\rho_M\) based upon volume fractions

\[ \rho_M = \sum_{i=1}^N \phi_i \rho_i \]

Can take an arbitrary number of fluid denisties \(\rho_i\) and volume fractions \(\phi_i\). Volume fractions must sum to one. If the number of arguments is odd, the final volume fraction is assumed to be the ones complement of all other fractions.

Inputs must be broadcastable.

argv as the form (component_1, vfrac_1, component_2, vfrac2, ...) or to use the complement for the final component (component_1, vfrac_1, component_2)

Parameters:

Name Type Description Default
den Union[numpy.ndarray, float]

first fluid density

required
frac Union[numpy.ndarray, float]

first fluid volume fraction

required
argv Union[numpy.ndarray, float]

additional fluid and volume fraction pairs.

()

Returns:

Type Description
Union[numpy.ndarray, float]

mixed density for combined fluid

Source code in digirock/fluids/bw92.py
Python
def mixed_density(
    den: NDArrayOrFloat, frac: NDArrayOrFloat, *argv: NDArrayOrFloat
) -> NDArrayOrFloat:
    """Mixed fluid density $\\rho_M$ based upon volume fractions

    $$
    \\rho_M = \sum_{i=1}^N \\phi_i \\rho_i
    $$

    Can take an arbitrary number of fluid denisties $\\rho_i$ and volume fractions $\\phi_i$.
    Volume fractions must sum to one. If the number of arguments is odd, the final
    volume fraction is assumed to be the ones complement of all other fractions.

    Inputs must be [broadcastable](https://numpy.org/doc/stable/user/basics.broadcasting.html).

    `argv` as the form `(component_1, vfrac_1, component_2, vfrac2, ...)` or to use the complement for the final
    component `(component_1, vfrac_1, component_2)`

    Args:
        den: first fluid density
        frac: first fluid volume fraction
        argv: additional fluid and volume fraction pairs.

    Returns:
        mixed density for combined fluid
    """
    args = _process_vfrac(*((den, frac) + argv))
    den_sum = 0
    for deni, vfraci in chunked(args, 2):
        den_sum = den_sum + np.array(vfraci) * np.array(deni)
    return den_sum

oil_bulkmod(rho, vel)

Oil bulk modulus GPa

Uses bulkmod.

Parameters:

Name Type Description Default
rho Union[numpy.ndarray, float]

In-situ oil density (g/cc)

required
vel Union[numpy.ndarray, float]

In-situ compressional velocity (g/cc)

required

Returns:

Type Description
Union[numpy.ndarray, float]

Oil bulk modulus in (GPa)

Source code in digirock/fluids/bw92.py
Python
def oil_bulkmod(rho: NDArrayOrFloat, vel: NDArrayOrFloat) -> NDArrayOrFloat:
    """Oil bulk modulus GPa

    Uses [bulkmod][digirock.fluids.bw92.bulkmod].

    Args:
        rho: In-situ oil density (g/cc)
        vel: In-situ compressional velocity (g/cc)

    Returns:
        Oil bulk modulus in (GPa)
    """
    return bulkmod(rho, vel)

oil_density(rho, p, t)

Calculates the oil density for given pressure and temperature

For a live oil rho should be the output of oil_rho_sat to compensate for disolved gas.

B&W 1992 Eq 19

Parameters:

Name Type Description Default
rho Union[numpy.ndarray, float]

The oil density (g/cc)

required
p Union[numpy.ndarray, float]

Pressure (MPa)

required
t Union[numpy.ndarray, float]

Absolute temperature (degC)

required

Returns:

Type Description
Union[numpy.ndarray, float]

The oil density (g/cc) at pressure p and temperature t

Source code in digirock/fluids/bw92.py
Python
def oil_density(
    rho: NDArrayOrFloat, p: NDArrayOrFloat, t: NDArrayOrFloat
) -> NDArrayOrFloat:
    """Calculates the oil density for given pressure and temperature

    For a live oil rho should be the output of `oil_rho_sat` to compensate
    for disolved gas.

    B&W 1992 Eq 19

    Args:
        rho: The oil density (g/cc)
        p: Pressure (MPa)
        t: Absolute temperature (degC)

    Returns:
        The oil density (g/cc) at pressure p and temperature t
    """
    return oil_isothermal_density(rho, p) / (
        0.972 + 3.81e-4 * np.power(t + 17.78, 1.175)
    )

oil_fvf(rho0, g, rs, t)

Calculate the oil formation volume factor b0

Equation 23 from Batzle and Wang 1992 All inputs must be broadcastable to an equivalent shape.

Parameters:

Name Type Description Default
rho0 Union[numpy.ndarray, float]

The oil reference density (g/cc) at 15.6 degC

required
g Union[numpy.ndarray, float]

The gas specific gravity

required
rs Union[numpy.ndarray, float]

The Gas-to-Oil ratio (L/L)

required
t Union[numpy.ndarray, float]

In-situ temperature (degC)

required

Returns:

Type Description
Union[numpy.ndarray, float]

The oil formation volume factor (FVF/b0)

Source code in digirock/fluids/bw92.py
Python
def oil_fvf(
    rho0: NDArrayOrFloat, g: NDArrayOrFloat, rs: NDArrayOrFloat, t: NDArrayOrFloat
) -> NDArrayOrFloat:
    """Calculate the oil formation volume factor b0

    Equation 23 from Batzle and Wang 1992
    All inputs must be broadcastable to an equivalent shape.

    Args:
        rho0: The oil reference density (g/cc) at 15.6 degC
        g: The gas specific gravity
        rs: The Gas-to-Oil ratio (L/L)
        t: In-situ temperature (degC)

    Returns:
       The oil formation volume factor (FVF/b0)

    """
    # note: Dhannanjay 2006 shows 2.495 instead of 2.4 inside power bracket.
    return 0.972 + 0.00038 * np.power(2.4 * rs * np.sqrt(g / rho0) + t + 17.8, 1.175)

oil_isothermal_density(rho, p)

Calculates the oil density for a given pressure at 15.6 degC

B&W 1992 Equation 18

Parameters:

Name Type Description Default
rho Union[numpy.ndarray, float]

The oil reference density (g/cc) at 15.6 degC can be compensated for disovled gases by running oil_rho_sat first.

required
p Union[numpy.ndarray, float]

Pressure (MPa)

required

Returns:

Type Description
Union[numpy.ndarray, float]

The oil density (g/cc) at pressure p

Source code in digirock/fluids/bw92.py
Python
def oil_isothermal_density(rho: NDArrayOrFloat, p: NDArrayOrFloat) -> NDArrayOrFloat:
    """Calculates the oil density for a given pressure at 15.6 degC

    B&W 1992 Equation 18

    Args:
        rho: The oil reference density (g/cc) at 15.6 degC
            can be compensated for disovled gases by running `oil_rho_sat` first.
        p: Pressure (MPa)

    Returns:
        The oil density (g/cc) at pressure p
    """
    return (
        rho
        + (0.00277 * p - 1.71e-7 * np.power(p, 3)) * np.power(rho - 1.15, 2)
        + 3.49e-4 * p
    )

oil_rg(oil, g, pres, temp, mode='rho')

Calculate the Rg for oil with given paramters.

Rg is the volume ratio of liberated gas to remaining oil at atmospheric pressure and 15.6degC. This covers equatsion 21a and 21b of B & W 1992.

One input can be an array but if multiple inputs are arrays they must all have length n.

Parameters:

Name Type Description Default
oil Union[numpy.ndarray, float]

The density of the oil at 15.6degC or the api of the oil if mode = 'api'

required
g Union[numpy.ndarray, float]

gas specific gravity

required
pres Union[numpy.ndarray, float]

reservoir pressure (MPa)

required
temp Union[numpy.ndarray, float]

temperature (degC)

required
mode str

One of; rho' - oil input as density (g/cc), 'api' - oil (API)

'rho'

Returns:

Type Description
Union[numpy.ndarray, float]

rg (L/L)

Source code in digirock/fluids/bw92.py
Python
def oil_rg(
    oil: NDArrayOrFloat,
    g: NDArrayOrFloat,
    pres: NDArrayOrFloat,
    temp: NDArrayOrFloat,
    mode: str = "rho",
) -> NDArrayOrFloat:
    """Calculate the Rg for oil with given paramters.

    Rg is the volume ratio of liberated gas to remaining oil at atmospheric
    pressure and 15.6degC. This covers equatsion 21a and 21b of B & W 1992.

    One input can be an array but if multiple inputs are arrays they must
    all have length n.

    Args:
        oil: The density of the oil at 15.6degC or the api of
            the oil if mode = 'api'
        g: gas specific gravity
        pres: reservoir pressure (MPa)
        temp: temperature (degC)
        mode: One of; rho' - oil input as density (g/cc), 'api' - oil (API)

    Returns:
        rg (L/L)
    """
    if mode == "rho":
        a = 0.02123
        b = 4.072 / oil
    elif mode == "api":
        a = 2.03
        b = 0.02878 * oil
    else:
        raise ValueError("'mode must be either 'rho' or 'api'")
    return a * g * np.power(pres * np.exp(b - 0.00377 * temp), 1.205)

oil_rho_pseudo(rho0, rg, b0)

Calculates the oil pseudo density

B&W 1992 Eq 22

Parameters:

Name Type Description Default
rho0 Union[numpy.ndarray, float]

The oil reference density (g/cc) at 15.6 degC

required
rg Union[numpy.ndarray, float]

Gas-to-Oil ratio (L/L))

required
b0 Union[numpy.ndarray, float]

Oil formation volume factor FVF

required

Returns:

Type Description
Union[numpy.ndarray, float]

The pseudo-density (g/cc) of oil due to expansion from gas

Source code in digirock/fluids/bw92.py
Python
def oil_rho_pseudo(
    rho0: NDArrayOrFloat, rg: NDArrayOrFloat, b0: NDArrayOrFloat
) -> NDArrayOrFloat:
    """Calculates the oil pseudo density

    B&W 1992 Eq 22

    Args:
        rho0: The oil reference density (g/cc) at 15.6 degC
        rg: Gas-to-Oil ratio (L/L))
        b0: Oil formation volume factor FVF

    Returns:
        The pseudo-density (g/cc) of oil due to expansion from gas
    """
    return safe_divide(rho0 / (1 + 0.001 * rg), b0)

oil_rho_sat(rho0, g, rg, b0)

Calculate the gas saturated oil density

B&W Eq 24

Parameters:

Name Type Description Default
rho0 Union[numpy.ndarray, float]

The oil reference density (g/cc) at 15.6 degC

required
g Union[numpy.ndarray, float]

The gas specific gravity

required
rg Union[numpy.ndarray, float]

The Gas-to-Oil ratio (L/L)

required
b0 Union[numpy.ndarray, float]

Oil formation volume factor FVF

required

Returns:

Type Description
Union[numpy.ndarray, float]

The gas saturated oil density (g/cc) at 15.6 degC

Source code in digirock/fluids/bw92.py
Python
def oil_rho_sat(
    rho0: NDArrayOrFloat, g: NDArrayOrFloat, rg: NDArrayOrFloat, b0: NDArrayOrFloat
) -> NDArrayOrFloat:
    """Calculate the gas saturated oil density

    B&W Eq 24

    Args:
        rho0: The oil reference density (g/cc) at 15.6 degC
        g: The gas specific gravity
        rg: The Gas-to-Oil ratio (L/L)
        b0: Oil formation volume factor FVF

    Returns:
        The gas saturated oil density (g/cc) at 15.6 degC
    """
    return safe_divide((rho0 + 0.0012 * rg * g), b0)

oil_velocity(rho0, p, t, g, rg, b0=None)

Oil compressional-wave velocity

B&W 1992 Eq 20a

If b0 is None the B&W approximation will be used. The pseudo density for live oil is calculated intrinsically.

Parameters:

Name Type Description Default
rho0 Union[numpy.ndarray, float]

The oil reference density (g/cc) at 15.6 degC

required
p Union[numpy.ndarray, float]

In-situ pressure MPa

required
t Union[numpy.ndarray, float]

In-situ temperature degC

required
g Union[numpy.ndarray, float]

gas specific gravity

required
rg array-like

Gas-to-Oil ratio Litre/litre

required

Returns:

Type Description
(array-like)

The velocity of oil in m/s

Source code in digirock/fluids/bw92.py
Python
def oil_velocity(
    rho0: NDArrayOrFloat,
    p: NDArrayOrFloat,
    t: NDArrayOrFloat,
    g: NDArrayOrFloat,
    rg: NDArrayOrFloat,
    b0: NDArrayOrFloat = None,
):
    """Oil compressional-wave velocity

    B&W 1992 Eq 20a

    If b0 is None the B&W approximation will be used.
    The pseudo density for live oil is calculated intrinsically.

    Args:
        rho0: The oil reference density (g/cc) at 15.6 degC
        p: In-situ pressure MPa
        t: In-situ temperature degC
        g: gas specific gravity
        rg (array-like): Gas-to-Oil ratio Litre/litre

    Returns:
        (array-like): The velocity of oil in m/s
    """
    if b0 is None:
        b0 = oil_fvf(rho0, g, rg, t)
    rhops = oil_rho_pseudo(rho0, rg, b0)
    return (
        2096 * np.sqrt(rhops / (2.6 - rhops))
        - 3.7 * t
        + 4.64 * p
        + 0.0115 * t * p * (4.12 * np.sqrt(1.08 / rhops - 1) - 1)
    )

wat_bulkmod(rho, vel)

Brine bulk modulus K

Uses bulkmod.

Parameters:

Name Type Description Default
rho Union[numpy.ndarray, float]

water fluid density (g/cc)

required
vel Union[numpy.ndarray, float]

water fluid compressional velocity (m/s)

required

Returns:

Type Description
Union[numpy.ndarray, float]

bulk modulus (GPa)

Source code in digirock/fluids/bw92.py
Python
def wat_bulkmod(rho: NDArrayOrFloat, vel: NDArrayOrFloat) -> NDArrayOrFloat:
    """Brine bulk modulus K

    Uses [bulkmod][digirock.fluids.bw92.bulkmod].

    Args:
        rho: water fluid density (g/cc)
        vel: water fluid compressional velocity (m/s)

    Returns:
        bulk modulus (GPa)
    """
    return bulkmod(rho, vel)

wat_density_brine(t, p, sal)

Returns the density of brine at t and p

B&W 1992 Eq 27b

Note

Salinity (ppm) = fractional weight * 1E6

Parameters:

Name Type Description Default
t Union[numpy.ndarray, float]

temperature (degC)

required
p Union[numpy.ndarray, float]

pressure (MPa)

required
sal Union[numpy.ndarray, float]

weight fraction of salt

required

Returns:

Type Description
Union[numpy.ndarray, float]

density of brine (g/cc)

Source code in digirock/fluids/bw92.py
Python
def wat_density_brine(
    t: NDArrayOrFloat, p: NDArrayOrFloat, sal: NDArrayOrFloat
) -> NDArrayOrFloat:
    """Returns the density of brine at t and p

    B&W 1992 Eq 27b

    Note:
        Salinity (ppm) = fractional weight * 1E6

    Args:
        t: temperature (degC)
        p: pressure (MPa)
        sal: weight fraction of salt

    Returns:
        density of brine (g/cc)
    """
    return wat_density_pure(t, p) + sal * (
        0.668
        + 0.44 * sal
        + (1e-6)
        * (
            300 * p
            - 2400 * p * sal
            + t * (80 + 3 * t - 3300 * sal - 13 * p + 47 * p * sal)
        )
    )

wat_density_pure(t, p)

Returns the density of pure water at t and p

B&W 1992 Eq 27a

Parameters:

Name Type Description Default
t Union[numpy.ndarray, float]

temperature (degC)

required
p Union[numpy.ndarray, float]

pressure (MPa)

required

Returns:

Type Description
Union[numpy.ndarray, float]

density of pure water (g/cc)

Source code in digirock/fluids/bw92.py
Python
def wat_density_pure(t: NDArrayOrFloat, p: NDArrayOrFloat) -> NDArrayOrFloat:
    """Returns the density of pure water at t and p

    B&W 1992 Eq 27a

    Args:
        t: temperature (degC)
        p: pressure (MPa)

    Returns:
        density of pure water (g/cc)
    """
    return 1 + 1e-6 * (
        -80 * t
        - 3.3 * np.power(t, 2)
        + 0.00175 * np.power(t, 3)
        + 489 * p
        - 2 * t * p
        + 0.016 * np.power(t, 2) * p
        - 1.3e-5 * np.power(t, 3) * p
        - 0.333 * np.power(p, 2)
        - 0.002 * t * np.power(p, 2)
    )

wat_salinity_brine(t, p, density)

Back out the salinity of a brine form it's density using root finding.

This is not efficient on arrays so limited to floats.

Reverse B&W 1992 Eq 27b

Parameters:

Name Type Description Default
t float

temperature degC

required
p float

pressure MPa

required
density float

density of brine (g/cc)

required

Returns:

Type Description
(float)

weight fraction of salt; note: Salinity (ppm) = fractional weight * 1E6

Source code in digirock/fluids/bw92.py
Python
def wat_salinity_brine(t: float, p: float, density: float) -> float:
    """Back out the salinity of a brine form it's density using root finding.

    This is not efficient on arrays so limited to floats.

    Reverse B&W 1992 Eq 27b

    Args:
        t: temperature degC
        p: pressure MPa
        density: density of brine (g/cc)

    Returns:
        (float): weight fraction of salt; note: Salinity (ppm) = fractional weight * 1E6
    """

    def solve_density_for_salinity(sal):
        return density - wat_density_brine(t, p, sal)

    sol = root_scalar(
        solve_density_for_salinity, bracket=(0, 1), xtol=0.001, method="brentq"
    )
    if sol.converged:
        return sol.root
    else:
        raise ValueError("Could not find solution for density to salinity")

wat_velocity_brine(t, p, sal)

Velocity of a brine

Salinity (ppm) = fractional weight * 1E6

Parameters:

Name Type Description Default
t Union[numpy.ndarray, float]

temperature (degC)

required
p Union[numpy.ndarray, float]

pressure (MPa)

required
sal Union[numpy.ndarray, float]

weight fraction of salt

required

Returns:

Type Description
Union[numpy.ndarray, float]

compressional velocity of brine (m/s)

Source code in digirock/fluids/bw92.py
Python
def wat_velocity_brine(
    t: NDArrayOrFloat, p: NDArrayOrFloat, sal: NDArrayOrFloat
) -> NDArrayOrFloat:
    """Velocity of a brine

    Salinity (ppm) = fractional weight * 1E6

    Args:
        t: temperature (degC)
        p: pressure (MPa)
        sal: weight fraction of salt

    Returns:
        compressional velocity of brine (m/s)
    """
    return wat_velocity_pure(t, p) + (
        sal
        * (
            1170
            - 9.6 * t
            + 0.055 * np.power(t, 2)
            - 8.5e-5 * np.power(t, 3)
            + 2.6 * p
            + 0.0029 * t * p
            - 0.0476 * np.power(p, 2)
        )
        + np.power(sal, 1.5) * (780 - 10 * p + 0.16 * np.power(p, 2))
        - 1820 * np.power(sal, 2)
    )

wat_velocity_pure(t, p)

Returns the velocity of pure water at t and p t and p must have equal shape

Parameters:

Name Type Description Default
t Union[numpy.ndarray, float]

temperature (degC)

required
p Union[numpy.ndarray, float]

pressure (MPa)

required

Returns:

Type Description
Union[numpy.ndarray, float]

compressional velocity of pure water (m/s)

Notes

This approximation breaks down above pressures of 100MPa

Source code in digirock/fluids/bw92.py
Python
def wat_velocity_pure(t: NDArrayOrFloat, p: NDArrayOrFloat) -> NDArrayOrFloat:
    """Returns the velocity of pure water at t and p
    t and p must have equal shape

    Args:
        t: temperature (degC)
        p: pressure (MPa)

    Returns:
        compressional velocity of pure water (m/s)

    Notes:
        This approximation breaks down above pressures of 100MPa
    """
    to_shp = check_broadcastable(t=t, p=p)
    tl = np.broadcast_to(t, to_shp)
    pl = np.broadcast_to(p, to_shp)
    vl = np.zeros(to_shp, dtype=np.float64)
    dims = vl.shape
    _wat_velocity_pure_sum(
        tl.astype(np.float64).ravel(), pl.astype(np.float64).ravel(), vl.ravel()
    )
    vl.reshape(dims)
    return vl

woods_bulkmod(mod, frac, *argv)

Mixed fluid bulk modulus \(\kappa_M\) (Wood's Equation)

\[ \frac{1}{\kappa_M} = \sum_{i=1}^N \frac{\phi_i}{\kappa_i} \]

Can take an arbitrary number of fluids moduli \(\kappa_i\) and volume fractions \(\phi_i\). Volume fractions must sum to one. If the number of arguments is odd, the final volume fraction is assumed to be the ones complement of all other fractions.

Inputs must be broadcastable.

argv as the form (component_1, vfrac_1, component_2, vfrac2, ...) or to use the complement for the final component (component_1, vfrac_1, component_2)

Parameters:

Name Type Description Default
mod Union[numpy.ndarray, float]

first fluid modulus

required
frac Union[numpy.ndarray, float]

first fluid volume fraction

required
argv Union[numpy.ndarray, float]

additional fluid and volume fraction pairs.

()

Returns:

Type Description
Union[numpy.ndarray, float]

Wood's bulk modulus for a fluid mix

Source code in digirock/fluids/bw92.py
Python
def woods_bulkmod(
    mod: NDArrayOrFloat, frac: NDArrayOrFloat, *argv: NDArrayOrFloat
) -> NDArrayOrFloat:
    """Mixed fluid bulk modulus $\\kappa_M$ (Wood's Equation)

    $$
    \\frac{1}{\kappa_M} = \sum_{i=1}^N \\frac{\\phi_i}{\\kappa_i}
    $$

    Can take an arbitrary number of fluids moduli $\\kappa_i$ and volume fractions $\\phi_i$.
    Volume fractions must sum to one. If the number of arguments is odd, the final
    volume fraction is assumed to be the ones complement of all other fractions.

    Inputs must be [broadcastable](https://numpy.org/doc/stable/user/basics.broadcasting.html).

    `argv` as the form `(component_1, vfrac_1, component_2, vfrac2, ...)` or to use the complement for the final
    component `(component_1, vfrac_1, component_2)`

    Args:
        mod: first fluid modulus
        frac: first fluid volume fraction
        argv: additional fluid and volume fraction pairs.

    Returns:
        Wood's bulk modulus for a fluid mix
    """
    args = _process_vfrac(*((mod, frac) + argv))
    mod_sum = 0
    for modi, vfraci in chunked(args, 2):
        mod_sum = mod_sum + safe_divide(np.array(vfraci), np.array(modi))
    return safe_divide(1, mod_sum)

Eclipse

Python
from digirock.fluids import ecl

e100_bw(pres, ref_pres, bw, comp, visc, cvisc)

Eclipse 100 method for calculating Bw

Parameters:

Name Type Description Default
pres Union[numpy.ndarray, float]

Pressure to calculate Bw at.

required
ref_pres float

Reference pressure of bw, should be close to in-situ pressure (MPa).

required
bw float

Water formation volume factor at ref_pres (frac).

required
comp float

Compressibility of water at ref_pres (1/MPa)

required
visc float

Water viscosity at ref_pres (cP)

required
cvisc float

Water viscosibility (1/MPa)

required

Returns:

Type Description
Union[numpy.ndarray, float]

The formation volume factor for pres

Source code in digirock/fluids/ecl.py
Python
def e100_bw(
    pres: NDArrayOrFloat,
    ref_pres: float,
    bw: float,
    comp: float,
    visc: float,
    cvisc: float,
) -> NDArrayOrFloat:
    """Eclipse 100 method for calculating Bw

    Args:
        pres: Pressure to calculate Bw at.
        ref_pres: Reference pressure of bw, should be close to in-situ pressure (MPa).
        bw: Water formation volume factor at ref_pres (frac).
        comp: Compressibility of water at ref_pres (1/MPa)
        visc: Water viscosity at ref_pres (cP)
        cvisc: Water viscosibility (1/MPa)

    Returns:
        The formation volume factor for pres
    """
    x = comp * (pres - ref_pres)
    return bw / (1 + x + x * x / 2)

e100_oil_density(api)

Calculate the oil density from API using Eclipse formula.

\[ \rho_{API} = \frac{141.5}{l_g} - 131.5; l_g = \fracd{\rho_{oil}}{\rho_{wat}} \]

Returns:

Type Description
Union[numpy.ndarray, float]

Oil density \(\rho_{oil}\) at surface conditions (g/cc)

Source code in digirock/fluids/ecl.py
Python
def e100_oil_density(api: NDArrayOrFloat) -> NDArrayOrFloat:
    """Calculate the oil density from API using Eclipse formula.

    $$
    \\rho_{API} = \\frac{141.5}{l_g} - 131.5;
    l_g = \\fracd{\\rho_{oil}}{\\rho_{wat}}
    $$

    Args:
        api

    Returns:
        Oil density $\\rho_{oil}$ at surface conditions (g/cc)
    """
    return (
        E100MetricConst.RHO_WAT.value
        * (141.5 / (api + 131.5))
        * EclUnitScaler.METRIC.value["density"]
    )

oil_fvf_table(pres, bo, p, extrap='pchip')

Calculate the oil formation volume factor from a b0 or PVT table

The PVT table can be obtained from most ECLIPSE simulation data files. PVT tables are assumed to be constant for approximately reservoir temperature (isothermal).

Within the defined table the interpolation is linear. Outside of the table

Parameters:

Name Type Description Default
pres array-like

pressure array

required
bo array-like

oil FVF function vs pres

required
p array-like

the pressure at which to calculate the FVF

required
extrap str)

['pchip', 'const'] extrapolate outside of the defined table pchip uses PCHIP type extrapolation (honors gradient) const uses the closest value to the extrapolated point

'pchip'

Returns:

Type Description
(array-like)

the FVF for each value in p

Source code in digirock/fluids/ecl.py
Python
def oil_fvf_table(pres, bo, p, extrap="pchip"):
    """Calculate the oil formation volume factor from a b0 or PVT table

    The PVT table can be obtained from most ECLIPSE simulation data files.
    PVT tables are assumed to be constant for approximately reservoir temperature
    (isothermal).

    Within the defined table the interpolation is linear. Outside of the table

    Args:
        pres (array-like): pressure array
        bo (array-like): oil FVF function vs pres
        p (array-like): the pressure at which to calculate the FVF
        extrap (str) : ['pchip', 'const'] extrapolate outside of the defined table
                        pchip uses PCHIP type extrapolation (honors gradient)
                        const uses the closest value to the extrapolated point

    Returns:
        (array-like): the FVF for each value in p
    """
    linear = np.interp(p, pres, bo)
    if extrap == "const":
        return linear
    elif extrap == "pchip":
        tmin = min(pres)
        tmax = max(pres)
        pchipf = PchipInterpolator(pres, bo)
        pchip = pchipf(p)
        return np.where((p > tmin) & (p < tmax), linear, pchip)
    else:
        raise ValueError(
            f"Unknown `extrap` {extrap}, expected one of `pchip`, `const`."
        )