Theories#

The goal of all existing z-factor correlation models is to numerically represent the famous Standing-Katz (SK) chart, correlating the pseudo-critical properties, reduced pressure (\(P_{r}\)) and reduced temperature (\(T_{r}\)), to the real gas compressibility factor \(Z\). In the other words, calculation of the z-factor requires values of \(P_{r}\) and \(T_{r}\).

In real life applications, no one knows the exact \(P_{r}\) and \(T_{r}\) values of his gas mixture. This is where pseudo-critical property models, such as Sutton (1985) [1] and Piper et al (1993) [2], comes in handy by approximating them from gas specific gravity (\(\gamma_g\)), which is relatively easy to obtain from lab sample analysis.

This section explains the basic theories behind \(P_{r}\) and \(T_{r}\) correlation from specific gravity and the subsequent z-factor correlation from the computed \(P_{r}\) and \(T_{r}\).

_images/SK_chart_comparison.png

Figure 1: Left is the original SK chart, and the right is the numerical representation of the SK chart using the Dranchuk and Abu-Kassem (DAK) model [3].

1. Pseudo-Critical Property Models#

The z-factor can be derived from \(P_{r}\) and \(T_{r}\) through visual inspection of the SK chart or through numerical computation using various z-factor correlation models. In the other words, z-factor is a function of pseudo-reduced pressure and temperature:

\[Z = f(P_{r}, T_{r})\]

\(P_{r}\) and \(T_{r}\) are defined as the pressure and temperature divided by the mixture’s pseudo-critical pressure (\(P_{pc}\)) and and temperature (\(T_{pc}\)):

\[P_{r} = \frac{P}{P_{pc}}, ~~~~~~~ T_{r} = \frac{T}{T_{pc}}\]

Kay (1936) [4] stated that \(P_{pc}\) and \(T_{pc}\) of a gas mixture can be expressed as the mole fraction (\(x\)) weighted average of the critical pressure (\(P_c\)) and temperature (\(T_c\)) of the mixture’s individual component (\(i\)):

\[P_{pc}=\sum x_{i} P_{c_{i}}, ~~~~~~~T_{pc}=\sum x_{i} T_{c_{i}}\]

However, this method is too inconvenient because you have to manually input individual component’s \(P_c\), \(T_c\), and \(x\), which can be time-consuming. Furthermore, this isn’t practical for oil field applications in which the lab analysis of the “heavy-ends” are often lumped up together and reported as \(C_{6}^{+}\) or \(C_{7}^{+}\). This makes it impossible to know the mole fractions of the components heavier than \(C_{6}\) or \(C_{7}\).

This section introduces various pseudo-critical property models that correlates a gas mixture’s specific gravity (\(\gamma_{g}\)) to its corresponding \(P_{pc}\) and \(T_{pc}\).

1.1. Sutton (1985)#

Sutton (1985) [1] fitted the following regression model for a gas mixture with unknown component composition that that take \(\gamma_{g}\) as input:

\[P_{pc} = 756.8 - 131.07\gamma_{g} - 3.6\gamma^{2}_{g}\]
\[T_{pc} = 169.2 - 349.5\gamma_{g} - 74.0\gamma^{2}_{g}\]

The above correlations are valid over the ranges of specific gravities with which Sutton worked: \(0.57 < \gamma_{g} < 1.68\). He also recommends to apply Wichert-Aziz [5] correction for significant \(H_2S\) and \(CO_2\) fractions:

\[\epsilon = 120 (A^{0.9} - A^{1.6}) + 15(B^{0.5} - B^{4})\]
\[T_{pc}^{'} = T_{pc} - \epsilon\]
\[P_{pc}^{'} = \frac{P_{pc}T_{pc}^{'}}{T_{pc} - B(1 - B)\epsilon}\]

where:

\(\epsilon\) = temperature-correction factor for acid gases [°R]

\(A\) = sum of the mole fractions of \(CO_2\) and \(H_2S\) in the gas mixture [dimensionless]

\(B\) = mole fraction of \(H_2S\) in the gas mixture [dimensionless]

\(T^{'}_{pc}\) = corrected pseudo-critical temperature [°R]

\(P^{'}_{pc}\) = corrected pseudo-critical pressure [psia]

The correction correlation is applicable to concentration ranges of \(CO_2 < 54.4 \space mol\)% and \(H_2S < 73.8 \space mol\)%. Using the Dranchuk and Abu-Kassem (DAK) method [3] as a z-factor correlation model, Sutton’s correlation model reported an average absolute error of 1.418%. The regression coefficients were fitted with 289 points.

1.2. Piper et al. (1993)#

Piper et al. (1993) [2] adapted the method of Stewart et al. (1959) [6] to calculate the pseudo-critical properties of gas mixtures with nitrogen (\(N_2\)), \(CO_2\), and \(H_2S\) fractions:

\[T_{pc} = \frac{K^{2}}{J}, ~~~~~~~P_{pc} = \frac{T_{pc}}{J}\]

and

\[\begin{split}\begin{align} J &= 0.11582 - 0.45820 x_{H_2S}\left(\frac{T_c}{P_c}\right)\_{H_2S} - 0.90348 x_{CO_2}\left(\frac{T_c}{P_c}\right)\_{CO_2} - 0.66026 x_{N_2}\left(\frac{T_c}{P_c}\right)\_{N_2} \\ \\ &~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 0.70729\gamma_{g} - 0.099397 \gamma^{2}\_{g}\\ \\ K &= 3.8216 -0.06534 x_{H_2S}\left(\frac{T_c}{\sqrt{P_c}}\right)\_{H_2S} - 0.42113 x_{CO_2}\left(\frac{T_c}{\sqrt{P_c}}\right)\_{CO_2} - 0.91249 x_{N_2}\left(\frac{T_c}{\sqrt{P_c}}\right)\_{N_2} \\ \\ &~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 17.438\gamma_{g} - 3.2191 \gamma^{2}\_{g}\\ \end{align}\end{split}\]

where:

\(J\) = Steward, Burkhardt, and Voo (SBV) parameter [°R/psia]

\(K\) = SBV parameter [°R/psia^0.5]

\(x_{H_2S}\) = mole fraction of \(H_2S\) [dimensionless]

\(x_{CO_2}\) = mole fraction of \(CO_2\) [dimensionless]

\(x_{N2}\) = mole fraction of \(N_2\) [dimensionless]

Piper’s correction for non-hydrocarbon impurities have working ranges of \(H_2S < 51.37 \space mol\)%, \(CO_2 < 67.16 \space mol\)%, and \(N_2 < 15.68 \space mol\)%. Using the DAK method [3] as a z-factor correlation model, Piper’s crrelation model reported an average absolute error of 1.304%. The regression coefficients were fitted with 896 points.

1.3. Caveats#

1) The models work only for “naturally occurring” hydrocarbon gases

The models implemented in this library correlates \(\gamma_{g}\) to the corresponding \(P_{pc}\) and \(T_{pc}\) by using the fitted regression coefficients. This means that the working range of the models will be limited by the range of the data points used to fit the coefficients. All pseudo-critical models (that I know of) are developed using only the naturally occurring gas samples. Therefore, it is not recommended to use these models for synthetic gases. If you are dealing with synthetic gases, I recommend using Kay’s (1936) [4] method.

2) Correction is necessary in presence of significant impurities fractions

Sutton’s method (1985) [1] can apply correction for \(H_{2}S\) and \(CO_2\):

>>> from gascompressibility.pseudocritical import Sutton
>>>
>>> Sutton().calc_Tr(sg=0.7, T=75, CO2=0.1, H2S=0.07)
1.5005661019949397

Piper’s method (1993) [2] can apply correction for \(H_{2}S\), \(CO_2\), and \(N_2\):

>>> from gascompressibility.pseudocritical import Piper
>>>
>>> Piper().calc_Tr(sg=0.7, T=75, CO2=0.1, H2S=0.07, N2=0.1)
1.5483056093175225

2. Z-Factor Correlation Models#

There are two kinds of models for z-factor correlation: Implicit vs. Explicit models

Implicit models require iterative convergence to find the root of non-linear equations. From the Python point of view, this means that they use scipy.optimize.newton() method. These models are computationally much more expensive than explicit models. However, providing a good initial guess for the z-factor can significantly reduce computational cost. Initial guess of \(Z = 0.9\) is a good starting point for most applications in the oil field. This can be done by setting calc_z(guess=0.9) in this library (however, this is unnecessary if you set smart_guess=True).

Models implemented:

  • DAK (1975) [3]

  • Hall-Yarborough (1973) [7]

  • Londono (2005) [8]

Explicit models require only 1 iteration. They are fast. These models tend to be restricted by smaller applicable \(P_{r}\) and \(T_{r}\) ranges and be less accurate than implicit models. It is important to check the working ranges of the parameters before implementing these models.

Models implemented:

  • Kareem, Iwalewa, and Marhoun (2016) [9]

2.1. DAK (1975)#

This method requires iterative converge. The z-factor is computed by setting \(z\) as the root of the following non-linear equations:

\[\begin{split}\begin{align} 0 &= 1 + \left( A_{1} + \frac{A_{2}}{T_{r}} + \frac{A_{3}}{T_{r}^{3}} + \frac{A_{4}}{T_{r}^{4}} + \frac{A_{5}}{T_{r}^{5}}\right)\rho_{r} + \left( A_{6} + \frac{A_{7}}{T_{r}} + \frac{A_{8}}{T_{r}^{2}}\right)\rho_{r}^{2} \\ & ~~~~-A_{9}\left( \frac{A_{7}}{T_{r}} + \frac{A_{8}}{T_{r}^{2}}\right) \rho_{r}^{5} + A_{10}\left( 1 + A_{11}\rho_{r}^{2}\right)\left( \frac{\rho_{r}^{2}}{T_{r}^{3}}\right)exp(-A_{11}\rho_{r}^{2}) - z \\ \end{align}\end{split}\]

and

\[\rho_{r} = \frac{0.27P_{r}}{zT_{r}}\]

where:

\(A_{1} = 0.3265 ~~~~~~~~~ A_{2} = -1.0700 ~~~~~~~~~ A_{3} = -0.5339\)

\(A_{4}= 0.01569 ~~~~~~~ A_{5} = -0.05165 ~~~~~~~ A_{6} = 0.5475\)

\(A_{7} = -0.7361 ~~~~~~ A_{8} = 0.1844 ~~~~~~~~~~~~ A_{9} = 0.1056\)

\(A_{10} = 0.6134 ~~~~~~~~ A_{11} = 0.7210\)

The model’s tested working ranges are: \(1 \leq T_{r} \leq 3\) and \(0.2 \leq P_{r} \leq 30\). The regression coefficients were fitted on 1500 points. An average absolute error of 0.468% is reported in the original paper.

This method is widely used in the petroleum industry [10].

Code usage example:

>>> import gascompressibility as gc
>>> gc.calc_z(Pr=3.1995, Tr=1.5006)  # default: model='DAK'
0.7730934971021096

2.2. Hall-Yarborough (1973)#

This method requires iterative converge. The z-factor is computed by setting \(z\) as the root of the following non-linear equations:

\[0 = -A_{1}P_{r} + \frac{\rho_{r} + \rho_{r}^{2} + \rho_{r}^{3} - \rho_{r}^{4}}{(1 - \rho_{r})^{3}} - A_{2}\rho_{r}^{2} + A_{3}\rho_{r}^{A_{4}}\]

and

\[\rho_{r} = \frac{A_{1}P_{r}}{z}\]

where:

\(A_{1} = 0.06125te^{-1.2(1-t)^{2}}\)

\(A_{2}=14.76t - 9.76t^{2} + 4.58t^{3}\)

\(A_{3} = 90.7t - 242.2t^{2} + 42.4t^{3}\)

\(A_{4} = 2.18 + 2.82t,\)

\(t = 1 / T_{r}\),

The model’s tested working ranges are: \(1.15 < T_{r} \leq 3\) and \(0 < P_{r} \leq 20.5\). The regression coefficients were fitted with 289 points. An average absolute error of 1.21% is reported in the original paper.

This method has received great application in the natural gas industry [11].

Code usage example:

>>> import gascompressibility as gc
>>> gc.calc_z(zmodel='hall_yarborough', Pr=3.1995, Tr=1.5006)
0.77140002684377

2.3. Londono (2005)#

Londono’s method is exactly the same as the DAK method, and requires iterative converge. The only difference is that Londono further optimized the eleven regression coefficients by using more data points. DAK used 1500 points. Londono used 5960 points. The new regression coefficients are as follows:

\(A_{1} = 0.3024696 ~~~~~~~~~~ A_{2} = -1.046964 ~~~~~~~~~~ A_{3} = -0.1078916\)

\(A_{4}= -0.7694186 ~~~~~~~ A_{5} = 0.1965439 ~~~~~~~~~~~ A_{6} =0.6527819\)

\(A_{7} = -1.118884 ~~~~~~~~~ A_{8} = 0.3951957 ~~~~~~~~~~~ A_{9} = 0.09313593\)

\(A_{10} = 0.8483081 ~~~~~~~~~ A_{11} = 0.7880011\)

The original paper does not mention any tested working ranges of \(P_{r}\) and \(T_{r}\). However, it is logical to assume it’s working ranges to be the same as the those of the DAK method, \(1 \leq T_{r} \leq 3\) and \(0.2 \leq P_{r} \leq 30\), since the underlying math is the same. An average absolute error of 0.412% is reported in the original paper.

Code usage example:

>>> import gascompressibility as gc
>>> gc.calc_z(zmodel='londono', Pr=3.19, Tr=1.5)
0.7752626795793716

2.4. Kareem, Iwalewa, and Marhoun (2016)#

This method is an adapted form of the Hall-Yarborough method. This method DOES NOT require iterative convergence. The z-factor can be calculated by:

\[z = \frac{DP_{r}(1 + \rho_{r} + \rho_{r}^{2} - \rho_{r}^{3})}{(DP_{r} + E \rho_{r}^{2} - F \rho_{r}^{G})(1- \rho_{r})^{3}}\]

and

\[\rho_{r} = \frac{DP_{r}}{\left(\frac{1 + A^{2}}{C} -\frac{A^{2}B}{C^{3}} \right)}\]

where:

\(A = a_{1}te^{a_{2}(1-t)^{2}}P_{r}\)

\(B = a_{3}t + a_{4}t^{2} + a_{5}t^{6}P_{r}^{6}\)

\(C = a_{9} + a_{8}tP_{r} + a_{7}t^{2}P_{r}^{2} + a_{6}t^{3}P_{r}^{3}\)

\(D = a_{10}te^{a_{11}(1-t)^{2}}\)

\(E = a_{12}t + a_{13}t^{2} + a_{14}t^{3}\)

\(F = a_{15}t + a_{16}t^{2} + a_{17}t^{3}\)

\(G = a_{18} + a_{19}t\)

\(t = \frac{1}{T_{r}}\)

\(A_{1} = 0.317842 ~~~~~~~~~~~~~~ A_{2} = 0.382216 ~~~~~~~~~~ A_{3} = -7.76835 ~~~~~~~~~A_{4}= 14.2905 ~~~~~~~~~ A_{5} = 0.00000218363\)

\(A_{6} = -0.00469257 ~~~~~~~ A_{7} = 0.0962541 ~~~~~~~~ A_{8} = 0.16672 ~~~~~~~~~~~~A_{9}= 0.96691 ~~~~~~~~~ A_{10} = 0.063069\)

\(A_{11} = -1.966847 ~~~~~~~~~ A_{12} = 21.0581 ~~~~~~~~~~~ A_{13} = -27.0246 ~~~~~~~~A_{14}= 16.23 ~~~~~~~~~~~ A_{15} = 207.783\)

\(A_{16} = -488.161 ~~~~~~~~~~~ A_{17} = 176.29 ~~~~~~~~~~~~~ A_{18} = 1.88453 ~~~~~~~~~~~A_{19}= 3.05921\)

The model’s tested working ranges are: \(1.15 < T_{r} \leq 3\) and \(0.2 \leq P_{r} \leq 15\). The regression coefficients were fitted with 5346 points. An average absolute error of 0.4379% is reported in the original paper. For the range outside the coverage of this correlation, the authors recommend using implicit correlations. However, this explicit correlation can be used to provide an initial guess to speed up the iteration process.

Code usage example:

>>> import gascompressibility as gc
>>> gc.calc_z(zmodel='kareem', Pr=3.1995, Tr=1.5006)
0.7667583024871576

2.5. Output Comparison#

For 0.2 < \(P_{r}\) < 15:

_images/DAK-15.png
import gascompressibility as gc

results, fig, ax = gc.quickstart(zmodel='hall_yarborough', prmin=0.2, prmax=15, figsize=(8, 5))
ax.set_ylim(0.2, 1.8)
_images/londono-15.png
import gascompressibility as gc

results, fig, ax = gc.quickstart(zmodel='londono', prmin=0.2, prmax=15, figsize=(8, 5))
ax.set_ylim(0.2, 1.8)
_images/hall_yarborough-15.png
import gascompressibility as gc

results, fig, ax = gc.quickstart(zmodel='hall_yarborough', prmin=0.2, prmax=15, figsize=(8, 5))
ax.set_ylim(0.2, 1.8)
_images/kareem-15.png
import gascompressibility as gc

results, fig, ax = gc.quickstart(zmodel='kareem', prmin=0.2, prmax=15, figsize=(8, 5))
ax.set_ylim(0.2, 1.8)

For 0.2 < \(P_{r}\) < 30:

_images/DAK-30.png
import gascompressibility as gc

results, fig, ax = gc.quickstart(zmodel='DAK', prmin=0.2, prmax=30, figsize=(8, 5))
ax.set_ylim(0.2, 3.5)
_images/londono-30.png
import gascompressibility as gc

results, fig, ax = gc.quickstart(zmodel='londono', prmin=0.2, prmax=30, figsize=(8, 5))
ax.set_ylim(0.2, 3.5)
_images/hall_yarborough-30.png
import gascompressibility as gc

results, fig, ax = gc.quickstart(zmodel='hall_yarborough', prmin=0.2, prmax=30, figsize=(8, 5), guess=2)
ax.set_ylim(0.2, 3.5)
_images/kareem-30.png
import gascompressibility as gc

results, fig, ax = gc.quickstart(zmodel=kareem, prmin=0.2, prmax=30, figsize=(8, 5), disable_tr_annotation=True)
ax.set_ylim(0.2, 3.5)

2.6. Caveats#

Z-factor correlation models that rely on iterative convergence share issues of their optimization methods - the result depends on the quality of the initial guess. Most times this issues is restricted only to the speed, but sometimes it has direct impact on the final values computed.

Consider the following example when Pr=2.8 and Tr=1.1:

>>> import gascompressibility as gc
>>>
>>> gc.calc_z(Pr=2.8, Tr=1.1, zmodel='hall_yarborough', smart_guess=False, guess=0.9)
0.6001600275325583
>>>
>>> gc.calc_z(Pr=2.8, Tr=1.1, zmodel='hall_yarborough', smart_guess=False, guess=0.1)
0.44138121739974145

When smart_guess is turned off, the computed z value for the hall_yarborough model returns different results despite the same Pr and Tr input values. The only differences are the guess values. Comparing with the other z-models, we know that the true solution sits around ~0.44.

>>> gc.calc_z(Pr=2.8, Tr=1.1, zmodel='DAK', smart_guess=False, guess=0.9)
0.44245159219674585
>>>
>>> gc.calc_z(Pr=2.8, Tr=1.1, zmodel='kareem')
0.42052851684415665

This discrepancy happens because the iterative z-models implemented in GasCompressibility-py ('DAK' | 'hall_yarborough' | 'londono') rely on scipy.optimize.newton method to find a root, and as the official scipy documentation states, “there is no guarantee that a root has been found. Consequently, the result should be verified.”

Fortunately, GasCompressibility-py has a built-in feature that prevents these rare corner cases by setting smart_guess=True (default). Observe that the 'hall_yarborough' model is able to converge to the ~0.44 z-factor solution when smart guess is activated:

>>> gc.calc_z(Pr=2.8, Tr=1.1, zmodel='hall_yarborough', smart_guess=True)
0.44138121739974157

The below figure demonstrates some corner cases in which scipy fails converge to the right solution when smart_guess=False.

_images/corner_cases_example_1.png
Figure source code
import gascompressibility as gc

results, fig, ax, = gc.quickstart(zmodel='londono', prmin=0.2, prmax=10, smart_guess=False)
_images/corner_cases_example_2.png
Figure source code
import gascompressibility as gc

results, fig, ax = gc.quickstart(zmodel='hall_yarborough', prmin=0.2, prmax=10, smart_guess=False)
_images/corner_cases_example_3.png
Figure source code
import gascompressibility as gc

results, fig, ax = gc.quickstart(zmodel='hall_yarborough', prmin=15, prmax=30, smart_guess=False)

About smart_guess

smart_guess is a built-in feature activated by default (True) in the gascompressibility.calc_z function. It provides “smart” initial guess for the z-factor by using Kareem’s z-model for \(P_r < 15\) (working range of the model). This has two advantages.

  1. Faster because less iterations are needed. Kareem’s model uses an explicit method that doesn’t need iterative convergence.

  2. It’s guaranteed to converge to the correct solution since the guess provided by Kareem’s method should be VERY close to the true solution

For \(P_r > 15\), I found that a guess value of guess=2 (default when Pr > 15) works very well. So far I did not observe any cases in which the function converges to a false solution.

3. What models should I use?#

Short Answer: For z-correlation model, use zmodel='londono'. But if computational cost is a big concern, use zmodel='kareem' for \(P_r < 15\). For pseudo-critical property model, use pmodel='sutton'. If you have significant nitrogen fractions, use pmodel='piper'.

3.1. Working Ranges of Z-Models#

The below table summarizes the working \(P_r\) and \(T_r\) ranges of each model, according to it’s own original paper.

Model

\(T_r\)

\(P_r\)

DAK

[1, 3]

[0.2, 30]

Hall-Yarborough

[1.15, 3]

(0, 20.5]

Londono

[1, 3]

[0.2, 30]

Kareem

[1.15, 3]

[0.2, 15]

something

However, normally we don’t know the \(P_r\) and \(T_r\) values of a given mixture. The below figure summarizes the corresponding \(P_r\) and \(T_r\) (computed with Sutton’s method) for each of specific gravity, temperature, and pressure ranges. For example, assuming \(\gamma_{g}\) = 0.9 (green lines), z-factor correlation can’t be used for extreme conditions like \(P\) > 19,000 psia, or \(T\) > 800 °F. If Kareem’s method (zmodel='kareem') is used for speed, you can’t use it for \(P\) > 11,500 psia.

_images/zmodels_working_ranges.png
Figure source code
import matplotlib.pyplot as plt
import numpy as np
from gascompressibility.pseudocritical import Sutton

pmin = 0
pmax = 25000
Ps = np.linspace(pmin, pmax, 100)
Ps = np.array([round(P, 1) for P in Ps])

tmin = -459
tmax = 1500
Ts = np.linspace(tmin, tmax, 100)
Ts = np.array([round(T, 1) for T in Ts])

sgs = np.arange(0.1, 2.6, 0.4)
sgs = np.array([round(sg, 1) for sg in sgs])

results = {sg: {
    'Pr': np.array([]),
    'P': np.array([]),
    'Tr': np.array([]),
    'T': np.array([]),
} for sg in sgs}

for sg in sgs:
    for P in Ps:
        Pr = Sutton().calc_Pr(sg=sg, P=P)
        results[sg]['P'] = np.append(results[sg]['P'], [P], axis=0)
        results[sg]['Pr'] = np.append(results[sg]['Pr'], [Pr], axis=0)
    for T in Ts:
        Tr = Sutton().calc_Tr(sg=sg, T=T)
        results[sg]['T'] = np.append(results[sg]['T'], [T], axis=0)
        results[sg]['Tr'] = np.append(results[sg]['Tr'], [Tr], axis=0)

fig, axes = plt.subplots(1, 2, figsize=(9, 4))
for i, ax in enumerate(axes):
    if i == 0:
        for sg in sgs:
            Prs = results[sg]['Pr']
            Ps = results[sg]['P']

            p = ax.plot(Ps, Prs, label=sg)

            t = ax.text(Ps[-10], max(Prs) - 3, 'sg = ' + str(sg), color=p[0].get_color())
            t.set_bbox(dict(facecolor='white', alpha=0.7, edgecolor='white', pad=1))

        ax.text(0.06, 0.9, '$P_{r}$  approximation', fontsize=9, transform=ax.transAxes,
            bbox=dict(facecolor='white'))
        ax.set_ylabel('$P_r$', fontsize=11)
        ax.set_xlabel('Pressure (psia)')
        ymax = 60
        ax.hlines(y=30, xmin=pmin, xmax=pmax, color='k', linestyle='--', linewidth=0.8, alpha=0.7)
        ax.text(100, 31.3, '$P_r$ = 30.0', alpha=0.7)
        ax.hlines(y=15, xmin=pmin, xmax=pmax, color='k', linestyle='--', linewidth=0.8, alpha=0.7)
        ax.text(100, 16, '$P_r$ = 15.0', alpha=0.7)
        ax.hlines(y=1, xmin=pmin, xmax=pmax, color='k', linestyle='--', linewidth=0.8, alpha=0.7)
        ax.text(100, 2, '$P_r$ = 1.0', alpha=0.7)
        ax.fill_between(x=Ps, y1=1, y2=30, color='green', interpolate=True, alpha=0.1, zorder=-99)

    else:
        for sg in sgs:
            Trs = results[sg]['Tr']
            Ts = results[sg]['T']

            p = ax.plot(Ts, Trs, label=sg)

            t = ax.text(Ts[-1], max(Trs), 'sg = ' + str(sg), color=p[0].get_color())
            t.set_bbox(dict(facecolor='white', alpha=0.7, edgecolor='white', pad=1))

        ax.text(0.06, 0.9, '$T_{r}$  approximation', fontsize=9, transform=ax.transAxes,
            bbox=dict(facecolor='white'))
        ax.set_ylabel('$T_r$', fontsize=11)
        ax.set_xlabel('Temperature (°F)')
        ymax = 10
        ax.hlines(y=3, xmin=tmin, xmax=tmax, color='k', linestyle='--', linewidth=0.8, alpha=0.7)
        ax.text(tmin, 3.2, '$T_r$ = 3.0', alpha=0.7)
        ax.hlines(y=0.2, xmin=tmin, xmax=tmax, color='k', linestyle='--', linewidth=0.8, alpha=0.7)
        ax.text(tmin, 0.5, '$T_r$ = 0.2', alpha=0.7)
        ax.fill_between(x=Ts, y1=0.2, y2=3, color='green', interpolate=True, alpha=0.1, zorder=-99)


    ymin = 0 - 0.05 * ymax
    ax.set_ylim(ymin, ymax)

    ax.minorticks_on()
    ax.grid(alpha=0.5)
    ax.grid(visible=True, which='minor', alpha=0.1)
    ax.spines.top.set_visible(False)
    ax.spines.right.set_visible(False)


    def setbold(txt):
        return ' '.join([r"$\bf{" + item + "}$" for item in txt.split(' ')])

    bold_txt = setbold('Working Ranges of Z Models')
    plain_txt = ',  for each of specific gravity, pressure, and  temperature ranges'

    fig.suptitle(bold_txt + plain_txt,
                 verticalalignment='top', x=0, horizontalalignment='left', fontsize=11)
    yloc = 0.9
    ax.annotate('', xy=(0.01, yloc), xycoords='figure fraction', xytext=(1.02, yloc),
                arrowprops=dict(arrowstyle="-", color='k', lw=0.7))
    ax.text(0.95, 0.1, 'GasCompressibility-Py', fontsize=9, ha='right', va='center',
            transform=ax.transAxes, color='grey', alpha=0.5)

fig.tight_layout()

3.2. Compatibilities#

GasCompressibility-py currently supports two pseudo-critical models ('sutton' | 'piper') and four z-factor correlation models ('DAK' | 'hall_yarborough' | 'londono' | 'kareem'). Which combination of pseudo-critical model should you use with which z-factor correlation model? The below table presented in Elsharkawy and Elsharkawy (2020) [12] may shed light on determining which combination should be used:

_images/elsharkawy_table.png

The table dictates that Sutton’s pseudo-critical property model with Londono’s z-factor correlation model yields the highest coefficient of determination (\(R^2\)) of 0.974. However, so long as the models implemented in this package are concerned, you can use any combination you want. They all have \(R^2 \geq 0.957\), which is more than good enough for practical usage in real life applications.

Notes

Unfortunately this performance evaluation table does not include any explicit (fast) z-factor correlation models. Therefore, my recommendation is to avoid Kareem’s method unless computation speed is very important, since there’s no 3rd party paper (that I know of) that evaluates Kareem’s method other than himself.

3.3. Brief Of Each Models#

Pseudo-critical models:

  • Sutton (1985): Makes corrections for acid fractions: \(H_2S\) and \(CO_2\)

  • Piper (1993): Improved version of Piper. Additionally supports corrections for \(N_2\) along with \(H_2S\) and \(CO_2\)

Z-factor models:

  • DAK (1975): The most widely used z-factor model in the oil and gas industry for the past 40 years. You can’t go wrong with this model

  • Hall-Yarborough (1973): Not recommended.

  • Londono (2005): Improved version of DAK. Math is exactly the same, but regression coefficients are fitted with 4x more data points.

  • Kareem (2016): Fast, but have shorter working ranges (\(P_r < 15\))

4. References#