pyrestoolbox 2.2.3__tar.gz → 2.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyrestoolbox-2.2.3/pyrestoolbox.egg-info → pyrestoolbox-2.5.0}/PKG-INFO +1 -1
- pyrestoolbox-2.5.0/pyrestoolbox/brine/_lib_salting_library.py +693 -0
- pyrestoolbox-2.5.0/pyrestoolbox/brine/_lib_vle_engine.py +2629 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/brine/brine.py +556 -41
- pyrestoolbox-2.5.0/pyrestoolbox/docs/brine.rst +481 -0
- pyrestoolbox-2.5.0/pyrestoolbox/docs/examples.ipynb +1458 -0
- pyrestoolbox-2.5.0/pyrestoolbox/plyasunov/__init__.py +12 -0
- pyrestoolbox-2.5.0/pyrestoolbox/plyasunov/iapws_if97.py +143 -0
- pyrestoolbox-2.5.0/pyrestoolbox/plyasunov/plyasunov_model.py +353 -0
- pyrestoolbox-2.5.0/pyrestoolbox/plyasunov/water_properties.py +47 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/tests/test_brine.py +47 -6
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/tests/test_doc_examples.py +37 -8
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/tests/test_gas.py +27 -0
- pyrestoolbox-2.5.0/pyrestoolbox/tests/test_unified_brine_design.py +351 -0
- pyrestoolbox-2.5.0/pyrestoolbox/tests/test_viscosity_scaling.py +464 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0/pyrestoolbox.egg-info}/PKG-INFO +1 -1
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox.egg-info/SOURCES.txt +8 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/setup.cfg +1 -1
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/setup.py +1 -1
- pyrestoolbox-2.2.3/pyrestoolbox/docs/brine.rst +0 -266
- pyrestoolbox-2.2.3/pyrestoolbox/docs/examples.ipynb +0 -3284
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/LICENSE +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/MANIFEST.in +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/README.md +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/README.rst +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyproject.toml +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/__init__.py +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/brine/__init__.py +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/classes/__init__.py +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/classes/classes.py +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/constants/__init__.py +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/constants/constants.py +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/docs/.ipynb_checkpoints/examples-checkpoint.ipynb +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/docs/changelist.rst +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/docs/gas.rst +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/docs/img/bot.png +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/docs/img/bot_PVTO.png +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/docs/img/bot_img.png +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/docs/img/dry_gas.png +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/docs/img/grid_sat_df.png +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/docs/img/influence.png +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/docs/img/properties_df.png +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/docs/img/sgof.png +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/docs/img/swof.png +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/docs/layer.rst +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/docs/library.rst +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/docs/oil.rst +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/docs/simtools.rst +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/gas/__init__.py +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/gas/gas.py +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/layer/__init__.py +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/layer/layer.py +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/library/__init__.py +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/library/component_library.xlsx +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/library/library.py +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/oil/__init__.py +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/oil/oil.py +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/shared_fns/__init__.py +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/shared_fns/shared_fns.py +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/simtools/__init__.py +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/simtools/simtools.py +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/tests/__init__.py +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/tests/run_all_tests.py +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/tests/test_layer.py +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/tests/test_oil.py +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/tests/test_simtools.py +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/validate/__init__.py +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox/validate/validate.py +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox.egg-info/dependency_links.txt +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox.egg-info/requires.txt +0 -0
- {pyrestoolbox-2.2.3 → pyrestoolbox-2.5.0}/pyrestoolbox.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,693 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Library of Sechenov (salting-out) correlations for gases in NaCl brine.
|
|
3
|
+
|
|
4
|
+
All functions return ks on a LOG10 basis with molality (mol/kg H2O) scale:
|
|
5
|
+
|
|
6
|
+
log10(S_water / S_brine) = ks * m_NaCl
|
|
7
|
+
|
|
8
|
+
Sources implemented:
|
|
9
|
+
1. S&W Eq 8 — Soreide & Whitson 1992, generic (Tb-based), all gases
|
|
10
|
+
2. Dubessy — Dubessy et al. 2005, extended Sechenov for CO2 and H2S
|
|
11
|
+
3. Akinfiev — Akinfiev et al. 2016, Pitzer model for H2S (-> effective ks)
|
|
12
|
+
4. Li et al. — Li, Zhang, Luo & Li 2015, Pitzer for CH4, C2H6, C3H8, nC4H10
|
|
13
|
+
5. Mao & Duan — Mao & Duan 2006, Pitzer model for N2
|
|
14
|
+
6. Duan & Sun — Duan & Sun 2003, Pitzer model for CO2
|
|
15
|
+
|
|
16
|
+
Not implemented (too complex for Sechenov extraction):
|
|
17
|
+
- Springer et al. 2014 (OTC-25295-MS): MSE framework for H2S/CO2.
|
|
18
|
+
Requires speciation + Debye-Huckel + virial + UNIQUAC simultaneously.
|
|
19
|
+
Cannot be reduced to an analytical Sechenov form.
|
|
20
|
+
|
|
21
|
+
Convention: ks > 0 means salting-out (lower solubility in brine).
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import numpy as np
|
|
25
|
+
|
|
26
|
+
LN10 = np.log(10.0) # 2.302585...
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# ====================================================================
|
|
30
|
+
# 1. Soreide & Whitson 1992, Equation 8 (generic, Tb-based)
|
|
31
|
+
# ====================================================================
|
|
32
|
+
# Boiling points (K) for S&W gases
|
|
33
|
+
TB_K = {
|
|
34
|
+
'H2': 20.3, 'N2': 77.4, 'CO2': 194.7, 'H2S': 213.6,
|
|
35
|
+
'CH4': 111.6, 'C2H6': 184.6, 'C3H8': 231.1,
|
|
36
|
+
'iC4H10': 261.4, 'nC4H10': 272.7, 'iC5H12': 301.0,
|
|
37
|
+
'nC5H12': 309.2, 'nC6H14': 341.9, 'nC7H16': 371.6,
|
|
38
|
+
'nC8H18': 398.8, 'nC10H22': 447.3,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def ks_sw_eq8(T_K, gas_or_Tb):
|
|
43
|
+
"""Soreide-Whitson 1992 Equation 8 Sechenov coefficient.
|
|
44
|
+
|
|
45
|
+
Parameters
|
|
46
|
+
----------
|
|
47
|
+
T_K : float or array
|
|
48
|
+
Temperature in Kelvin.
|
|
49
|
+
gas_or_Tb : str or float
|
|
50
|
+
Gas name (e.g. 'H2', 'CH4') or boiling point in K.
|
|
51
|
+
|
|
52
|
+
Returns
|
|
53
|
+
-------
|
|
54
|
+
ks : float or array
|
|
55
|
+
Sechenov coefficient (log10 basis, molality scale).
|
|
56
|
+
"""
|
|
57
|
+
if isinstance(gas_or_Tb, str):
|
|
58
|
+
Tb = TB_K[gas_or_Tb]
|
|
59
|
+
else:
|
|
60
|
+
Tb = float(gas_or_Tb)
|
|
61
|
+
T_F = (np.asarray(T_K, dtype=float) - 273.15) * 9.0 / 5.0 + 32.0
|
|
62
|
+
return (0.13163 + 4.45e-4 * Tb - 7.692e-4 * T_F
|
|
63
|
+
+ 2.6614e-6 * T_F**2 - 2.612e-9 * T_F**3)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# ====================================================================
|
|
67
|
+
# 2. Dubessy et al. (2005) — Extended Sechenov for CO2 and H2S
|
|
68
|
+
# Oil & Gas Sci. Technol. - Rev. IFP, Vol. 60, No. 2, pp. 339-355
|
|
69
|
+
# Table 9 coefficients.
|
|
70
|
+
#
|
|
71
|
+
# log10(K_brine/K_water) = m*b1(T) + m^2*b2(T) + m^3*b3(T)
|
|
72
|
+
# b_i(T) = sum(B_ij * T^j, j=0..4)
|
|
73
|
+
#
|
|
74
|
+
# Effective ks = b1(T) + m*b2(T) + m^2*b3(T)
|
|
75
|
+
# ====================================================================
|
|
76
|
+
|
|
77
|
+
# CO2: column scaling 1e0, 1e-2, 1e-4, 1e-8, 1e-11
|
|
78
|
+
_DUB_CO2_B1 = np.array([3.114712456, -2.7655585e-2, 0.9176713976e-4,
|
|
79
|
+
-12.78795941e-8, 6.2704268351e-11])
|
|
80
|
+
_DUB_CO2_B2 = np.array([-2.05637458, 2.081980200e-2, -0.765857702e-4,
|
|
81
|
+
12.011325315e-8, -6.790343083e-11])
|
|
82
|
+
_DUB_CO2_B3 = np.array([0.253424331, -0.26047432e-2, 0.0972580216e-4,
|
|
83
|
+
-1.551654794e-8, 0.8948557284e-11])
|
|
84
|
+
|
|
85
|
+
# H2S: column scaling 1e0, 1e-2, 1e-4, 1e-7, 1e-10
|
|
86
|
+
_DUB_H2S_B1 = np.array([-12.4617636, 12.69373100e-2, -4.791540697e-4,
|
|
87
|
+
7.9817223650e-7, -4.931093145e-10])
|
|
88
|
+
_DUB_H2S_B2 = np.array([5.327383011, -5.82779828e-2, 2.3650333285e-4,
|
|
89
|
+
-4.207913036e-7, 2.7628521914e-10])
|
|
90
|
+
_DUB_H2S_B3 = np.array([-0.75715275, 0.831927411e-2, -0.338668040e-4,
|
|
91
|
+
0.6037602785e-7, -0.397049836e-10])
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _poly4(c, T):
|
|
95
|
+
"""Evaluate 4th-order polynomial c[0] + c[1]*T + ... + c[4]*T^4."""
|
|
96
|
+
T = np.asarray(T, dtype=float)
|
|
97
|
+
return c[0] + c[1]*T + c[2]*T**2 + c[3]*T**3 + c[4]*T**4
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def ks_dubessy_co2(T_K, m_NaCl=0.0):
|
|
101
|
+
"""Dubessy et al. 2005 effective Sechenov for CO2-NaCl (log10).
|
|
102
|
+
|
|
103
|
+
Valid: T <= 543 K (270 C), P <= 300 bar, m <= 6. R^2 = 0.83.
|
|
104
|
+
"""
|
|
105
|
+
return (_poly4(_DUB_CO2_B1, T_K) + m_NaCl * _poly4(_DUB_CO2_B2, T_K)
|
|
106
|
+
+ m_NaCl**2 * _poly4(_DUB_CO2_B3, T_K))
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def ks_dubessy_h2s(T_K, m_NaCl=0.0):
|
|
110
|
+
"""Dubessy et al. 2005 effective Sechenov for H2S-NaCl (log10).
|
|
111
|
+
|
|
112
|
+
Valid: T <= 523 K (250 C), P <= 150 bar, m <= 6. R^2 = 0.74.
|
|
113
|
+
"""
|
|
114
|
+
return (_poly4(_DUB_H2S_B1, T_K) + m_NaCl * _poly4(_DUB_H2S_B2, T_K)
|
|
115
|
+
+ m_NaCl**2 * _poly4(_DUB_H2S_B3, T_K))
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# ====================================================================
|
|
119
|
+
# 3. Akinfiev, Majer & Shvarov (2016) — Pitzer model for H2S-NaCl
|
|
120
|
+
# Chemical Geology, 424, 1-11. DOI: 10.1016/j.chemgeo.2016.01.006
|
|
121
|
+
#
|
|
122
|
+
# Pitzer activity coefficient (Eq. 16):
|
|
123
|
+
# ln(gamma_s) = 2*m_s*lam_ss + 3*m_s^2*tau_sss
|
|
124
|
+
# + 2*m_e*B_se + 3*m_e^2*C_see + 6*m_s*m_e*C_sse
|
|
125
|
+
#
|
|
126
|
+
# For dilute H2S (m_s << m_e), the effective Sechenov is:
|
|
127
|
+
# ks_eff ≈ (2*B_se + 6*m_s*C_sse) / ln(10) [on log10/molality]
|
|
128
|
+
#
|
|
129
|
+
# But the rigorous approach uses the full recommended solubility tables
|
|
130
|
+
# or the complete Pitzer framework.
|
|
131
|
+
# ====================================================================
|
|
132
|
+
|
|
133
|
+
# Binary H2S-H2O self-interaction: Eq. (18)
|
|
134
|
+
# lambda_ss = a_lam + b_lam * (100/(T-228)) + c_lam * (T/(T-760))
|
|
135
|
+
_AK_A_LAM = -0.19515
|
|
136
|
+
_AK_B_LAM = 0.102822
|
|
137
|
+
_AK_C_LAM = -0.033726
|
|
138
|
+
_AK_TAU_SSS = 0.004900
|
|
139
|
+
|
|
140
|
+
# Ternary H2S-H2O-NaCl: Eq. (20)
|
|
141
|
+
# B_se = b_B * (100/(T-228)) + c_B * (T/(T-760))
|
|
142
|
+
_AK_B_B = 0.03568
|
|
143
|
+
_AK_C_B = -0.02354
|
|
144
|
+
_AK_C_SEE = 0.0 # set to zero by authors
|
|
145
|
+
_AK_C_SSE = 0.002558
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _akinfiev_lambda_ss(T_K):
|
|
149
|
+
"""H2S-H2S self-interaction parameter (Akinfiev Eq. 18)."""
|
|
150
|
+
T = np.asarray(T_K, dtype=float)
|
|
151
|
+
return _AK_A_LAM + _AK_B_LAM * (100.0 / (T - 228.0)) + _AK_C_LAM * (T / (T - 760.0))
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _akinfiev_B_se(T_K):
|
|
155
|
+
"""H2S-NaCl interaction parameter B_se (Akinfiev Eq. 20)."""
|
|
156
|
+
T = np.asarray(T_K, dtype=float)
|
|
157
|
+
return _AK_B_B * (100.0 / (T - 228.0)) + _AK_C_B * (T / (T - 760.0))
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def akinfiev_ln_gamma_h2s(T_K, m_h2s, m_NaCl):
|
|
161
|
+
"""Natural log of H2S activity coefficient in H2S-H2O-NaCl (Eq. 16).
|
|
162
|
+
|
|
163
|
+
Parameters
|
|
164
|
+
----------
|
|
165
|
+
T_K : float
|
|
166
|
+
Temperature in Kelvin (283-573 K).
|
|
167
|
+
m_h2s : float
|
|
168
|
+
H2S molality (mol/kg H2O).
|
|
169
|
+
m_NaCl : float
|
|
170
|
+
NaCl molality (mol/kg H2O), 0-6.
|
|
171
|
+
|
|
172
|
+
Returns
|
|
173
|
+
-------
|
|
174
|
+
ln_gamma : float
|
|
175
|
+
Natural log of activity coefficient on Henry scale.
|
|
176
|
+
"""
|
|
177
|
+
lam = _akinfiev_lambda_ss(T_K)
|
|
178
|
+
B = _akinfiev_B_se(T_K)
|
|
179
|
+
return (2.0 * m_h2s * lam + 3.0 * m_h2s**2 * _AK_TAU_SSS
|
|
180
|
+
+ 2.0 * m_NaCl * B + 3.0 * m_NaCl**2 * _AK_C_SEE
|
|
181
|
+
+ 6.0 * m_h2s * m_NaCl * _AK_C_SSE)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def ks_akinfiev_h2s(T_K, m_NaCl=1.0, m_h2s_approx=0.1):
|
|
185
|
+
"""Effective Sechenov coefficient for H2S-NaCl from Akinfiev Pitzer (log10).
|
|
186
|
+
|
|
187
|
+
Computes the effective ks from the Pitzer activity coefficient difference
|
|
188
|
+
between saline and fresh solutions, at a specified approximate H2S molality.
|
|
189
|
+
|
|
190
|
+
For dilute H2S, ln(gamma_saline/gamma_fresh) ≈ 2*m_e*B_se + 6*m_s*m_e*C_sse
|
|
191
|
+
so ks ≈ (2*B_se + 6*m_s*C_sse) / ln(10).
|
|
192
|
+
|
|
193
|
+
Parameters
|
|
194
|
+
----------
|
|
195
|
+
T_K : float or array
|
|
196
|
+
Temperature in Kelvin (283-573 K).
|
|
197
|
+
m_NaCl : float
|
|
198
|
+
NaCl molality for computing the effective ks. Default 1 m.
|
|
199
|
+
m_h2s_approx : float
|
|
200
|
+
Approximate H2S molality (affects C_sse contribution). Default 0.1 m.
|
|
201
|
+
|
|
202
|
+
Returns
|
|
203
|
+
-------
|
|
204
|
+
ks : float or array
|
|
205
|
+
Effective Sechenov coefficient (log10 basis, molality scale).
|
|
206
|
+
"""
|
|
207
|
+
B = _akinfiev_B_se(T_K)
|
|
208
|
+
# ln(gamma_brine) - ln(gamma_fresh) at given m_h2s:
|
|
209
|
+
# = 2*m_NaCl*B_se + 3*m_NaCl^2*C_see + 6*m_h2s*m_NaCl*C_sse
|
|
210
|
+
# Dividing by m_NaCl gives the ln-based Sechenov:
|
|
211
|
+
ks_ln = 2.0 * B + 3.0 * m_NaCl * _AK_C_SEE + 6.0 * m_h2s_approx * _AK_C_SSE
|
|
212
|
+
return ks_ln / LN10
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def ks_akinfiev_h2s_from_tables(T_K, m_NaCl):
|
|
216
|
+
"""Effective ks for H2S from Akinfiev 2016 recommended solubility tables.
|
|
217
|
+
|
|
218
|
+
Uses linearly interpolated Table 3 (pure water) and Table 4 (brine)
|
|
219
|
+
values at 5 MPa to compute ks = log10(m_water/m_brine) / m_NaCl.
|
|
220
|
+
|
|
221
|
+
This is the most reliable method — uses the model output directly,
|
|
222
|
+
avoids any approximation in the Pitzer-to-Sechenov conversion.
|
|
223
|
+
|
|
224
|
+
Parameters
|
|
225
|
+
----------
|
|
226
|
+
T_K : float or array
|
|
227
|
+
Temperature in Kelvin. Supported: 298-523 K.
|
|
228
|
+
m_NaCl : float
|
|
229
|
+
Must be one of: 1, 2, 4, or 6 mol/kg.
|
|
230
|
+
|
|
231
|
+
Returns
|
|
232
|
+
-------
|
|
233
|
+
ks : float or array
|
|
234
|
+
Effective Sechenov coefficient (log10 basis).
|
|
235
|
+
"""
|
|
236
|
+
# Tables 3 & 4 at P = 5 MPa (mid-range, well within experimental coverage)
|
|
237
|
+
# T_K: 298.15 323.15 373.15 423.15 473.15 523.15
|
|
238
|
+
_T = np.array([298.15, 323.15, 373.15, 423.15, 473.15, 523.15])
|
|
239
|
+
_m0 = np.array([np.nan, 2.026, 1.651, 1.271, 0.956, 0.300]) # pure water, 5 MPa
|
|
240
|
+
# NaCl = 1 m
|
|
241
|
+
_m1 = np.array([np.nan, 1.752, 1.437, 1.110, 0.831, 0.260])
|
|
242
|
+
# NaCl = 2 m
|
|
243
|
+
_m2 = np.array([np.nan, 1.523, 1.259, 0.975, 0.726, 0.226])
|
|
244
|
+
# NaCl = 4 m
|
|
245
|
+
_m4 = np.array([np.nan, 1.168, 0.983, 0.763, 0.561, 0.171])
|
|
246
|
+
# NaCl = 6 m
|
|
247
|
+
_m6 = np.array([np.nan, 0.907, 0.779, 0.606, 0.438, 0.130])
|
|
248
|
+
|
|
249
|
+
brine_tables = {1: _m1, 2: _m2, 4: _m4, 6: _m6}
|
|
250
|
+
if m_NaCl not in brine_tables:
|
|
251
|
+
raise ValueError(f"m_NaCl must be 1, 2, 4, or 6. Got {m_NaCl}")
|
|
252
|
+
|
|
253
|
+
m_brine = brine_tables[m_NaCl]
|
|
254
|
+
# Compute ks at each table temperature (skip 298.15 where data missing at 5 MPa)
|
|
255
|
+
valid = ~np.isnan(_m0) & ~np.isnan(m_brine)
|
|
256
|
+
ks_pts = np.full_like(_T, np.nan)
|
|
257
|
+
ks_pts[valid] = np.log10(_m0[valid] / m_brine[valid]) / m_NaCl
|
|
258
|
+
|
|
259
|
+
# Interpolate to requested temperatures
|
|
260
|
+
T_valid = _T[valid]
|
|
261
|
+
ks_valid = ks_pts[valid]
|
|
262
|
+
T_K = np.asarray(T_K, dtype=float)
|
|
263
|
+
scalar = T_K.ndim == 0
|
|
264
|
+
T_K = np.atleast_1d(T_K)
|
|
265
|
+
result = np.interp(T_K, T_valid, ks_valid)
|
|
266
|
+
return float(result[0]) if scalar else result
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
# ====================================================================
|
|
270
|
+
# 4. Li, Zhang, Luo & Li (2015) — Pitzer model for CH4-C2H6-C3H8-nC4H10
|
|
271
|
+
# Applied Geochemistry. DOI: 10.1016/j.apgeochem.2015.02.006
|
|
272
|
+
#
|
|
273
|
+
# Eq. 15 (for NaCl, with lambda_{i-Cl-} = 0):
|
|
274
|
+
# ln(gamma_i) = 2 * m * lambda_{i-Na+}(T,P) + m^2 * zeta_{i-Na+-Cl-}
|
|
275
|
+
#
|
|
276
|
+
# Effective Sechenov (log10):
|
|
277
|
+
# ks = (2*lambda + zeta*m) / ln(10)
|
|
278
|
+
#
|
|
279
|
+
# Validity: 273-473 K, 1-1000 bar, 0-6 m NaCl
|
|
280
|
+
# NOTE: C3H8 and nC4H10 have very limited brine data (1 atm only).
|
|
281
|
+
# ====================================================================
|
|
282
|
+
|
|
283
|
+
def _li2015_lambda_ch4(T_K, P_bar=100.0):
|
|
284
|
+
"""CH4-Na+ Pitzer lambda (Li et al. 2015 Table 7). T in K, P in bar."""
|
|
285
|
+
T = np.asarray(T_K, dtype=float)
|
|
286
|
+
P = float(P_bar)
|
|
287
|
+
return (-5.7066455e-1 + 7.2997588e-4 * T + 1.5176903e2 / T
|
|
288
|
+
+ 3.1927112e-5 * P - 1.642651e-5 * P / T)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _li2015_lambda_c2h6(T_K, P_bar=100.0):
|
|
292
|
+
"""C2H6-Na+ Pitzer lambda (Li et al. 2015 Table 7). T in K, P in bar."""
|
|
293
|
+
T = np.asarray(T_K, dtype=float)
|
|
294
|
+
P = float(P_bar)
|
|
295
|
+
return (-2.143686 + 2.598765e-3 * T + 4.6942351e2 / T
|
|
296
|
+
- 4.6849541e-5 * P - 8.4616602e-10 * P**2 * T
|
|
297
|
+
+ 1.095219e-6 * P * T)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _li2015_lambda_c3h8(T_K, P_bar=100.0):
|
|
301
|
+
"""C3H8-Na+ Pitzer lambda (Li et al. 2015 Table 7). T-only (no P dep)."""
|
|
302
|
+
T = np.asarray(T_K, dtype=float)
|
|
303
|
+
return 0.513068 - 0.000958 * T
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _li2015_lambda_nc4h10(T_K, P_bar=100.0):
|
|
307
|
+
"""nC4H10-Na+ Pitzer lambda (Li et al. 2015 Table 7). T-only (no P dep)."""
|
|
308
|
+
T = np.asarray(T_K, dtype=float)
|
|
309
|
+
return 0.52862384 - 1.0298104e-3 * T
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
# Zeta (third virial) — all constants, no T or P dependence
|
|
313
|
+
_LI2015_ZETA = {
|
|
314
|
+
'CH4': -2.9990084e-3,
|
|
315
|
+
'C2H6': -1.0165947e-2,
|
|
316
|
+
'C3H8': -0.007485,
|
|
317
|
+
'NC4H10': 0.0206946, # NOTE: positive (unlike the others)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
_LI2015_LAMBDA = {
|
|
321
|
+
'CH4': _li2015_lambda_ch4,
|
|
322
|
+
'C2H6': _li2015_lambda_c2h6,
|
|
323
|
+
'C3H8': _li2015_lambda_c3h8,
|
|
324
|
+
'NC4H10': _li2015_lambda_nc4h10,
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def ks_li2015(T_K, gas, m_NaCl=0.0, P_bar=100.0):
|
|
329
|
+
"""Effective Sechenov for light HCs from Li et al. 2015 Pitzer (log10).
|
|
330
|
+
|
|
331
|
+
ln(gamma) = 2*m*lambda(T,P) + m^2*zeta
|
|
332
|
+
ks = (2*lambda + zeta*m) / ln(10)
|
|
333
|
+
|
|
334
|
+
Parameters
|
|
335
|
+
----------
|
|
336
|
+
T_K : float or array
|
|
337
|
+
Temperature in Kelvin (273-473 K).
|
|
338
|
+
gas : str
|
|
339
|
+
'CH4', 'C2H6', 'C3H8', or 'nC4H10'.
|
|
340
|
+
m_NaCl : float
|
|
341
|
+
NaCl molality. Affects effective ks through zeta term.
|
|
342
|
+
P_bar : float
|
|
343
|
+
Pressure in bar (affects CH4 and C2H6 lambda). Default 100.
|
|
344
|
+
|
|
345
|
+
Returns
|
|
346
|
+
-------
|
|
347
|
+
ks : float or array
|
|
348
|
+
Effective Sechenov coefficient (log10 basis, molality scale).
|
|
349
|
+
"""
|
|
350
|
+
g = gas.upper().replace('N', 'N') # normalize
|
|
351
|
+
if g == 'NC4H10' or g == 'NC4':
|
|
352
|
+
g = 'NC4H10'
|
|
353
|
+
elif g not in _LI2015_LAMBDA:
|
|
354
|
+
raise ValueError(f"Li 2015 covers CH4, C2H6, C3H8, nC4H10. Got {gas}")
|
|
355
|
+
|
|
356
|
+
lam = _LI2015_LAMBDA[g](T_K, P_bar)
|
|
357
|
+
zeta = _LI2015_ZETA[g]
|
|
358
|
+
return (2.0 * lam + zeta * m_NaCl) / LN10
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
# ====================================================================
|
|
362
|
+
# 5. Mao & Duan (2006) — Pitzer model for N2-NaCl
|
|
363
|
+
# Fluid Phase Equilibria, 248, 103-114.
|
|
364
|
+
# DOI: 10.1016/j.fluid.2006.07.020
|
|
365
|
+
#
|
|
366
|
+
# Eq. 8 (for NaCl, with lambda_{N2-Cl-} = 0):
|
|
367
|
+
# ln(gamma_N2) = 2 * m * lambda_{N2-Na+}(T,P) + m^2 * xi_{N2-Na+-Cl-}
|
|
368
|
+
#
|
|
369
|
+
# Effective Sechenov (log10):
|
|
370
|
+
# ks = (2*lambda + xi*m) / ln(10)
|
|
371
|
+
#
|
|
372
|
+
# Validity: 273-400 K, 1-600 bar, 0-6 m NaCl
|
|
373
|
+
# ====================================================================
|
|
374
|
+
|
|
375
|
+
def _mao2006_lambda_n2(T_K, P_bar=100.0):
|
|
376
|
+
"""N2-Na+ Pitzer lambda (Mao & Duan 2006 Table 3). T in K, P in bar."""
|
|
377
|
+
T = np.asarray(T_K, dtype=float)
|
|
378
|
+
P = float(P_bar)
|
|
379
|
+
return (-2.4434074 + 0.0036351795 * T + 447.47364 / T
|
|
380
|
+
- 0.000013711527 * P + 0.0000071037217 * P**2 / T)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
_MAO2006_XI_N2 = -0.0058071053 # constant, no T or P dependence
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def ks_mao2006_n2(T_K, m_NaCl=0.0, P_bar=100.0):
|
|
387
|
+
"""Effective Sechenov for N2 from Mao & Duan 2006 Pitzer model (log10).
|
|
388
|
+
|
|
389
|
+
ln(gamma_N2) = 2*m*lambda(T,P) + m^2*xi
|
|
390
|
+
ks = (2*lambda + xi*m) / ln(10)
|
|
391
|
+
|
|
392
|
+
Parameters
|
|
393
|
+
----------
|
|
394
|
+
T_K : float or array
|
|
395
|
+
Temperature in Kelvin (273-400 K).
|
|
396
|
+
m_NaCl : float
|
|
397
|
+
NaCl molality. Affects effective ks through xi term.
|
|
398
|
+
P_bar : float
|
|
399
|
+
Pressure in bar (1-600 bar). Default 100.
|
|
400
|
+
|
|
401
|
+
Returns
|
|
402
|
+
-------
|
|
403
|
+
ks : float or array
|
|
404
|
+
Effective Sechenov coefficient (log10 basis, molality scale).
|
|
405
|
+
"""
|
|
406
|
+
lam = _mao2006_lambda_n2(T_K, P_bar)
|
|
407
|
+
return (2.0 * lam + _MAO2006_XI_N2 * m_NaCl) / LN10
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
# ====================================================================
|
|
411
|
+
# 6. Duan & Sun (2003) — Pitzer model for CO2-NaCl
|
|
412
|
+
# Chemical Geology, 193, 257-271.
|
|
413
|
+
# DOI: 10.1016/S0009-2541(02)00263-2
|
|
414
|
+
#
|
|
415
|
+
# Same Pitzer framework as Mao 2006 (N2) — Pitzer et al. (1984) form:
|
|
416
|
+
# Par(T,P) = c1 + c2*T + c3/T + c4*T^2 + c5/(630-T)
|
|
417
|
+
# + c6*P + c7*P*ln(T) + c8*P/T + c9*P/(630-T)
|
|
418
|
+
# + c10*P^2/(630-T)^2 + c11*T*ln(P)
|
|
419
|
+
#
|
|
420
|
+
# lambda_{CO2-Na+} uses c1,c2,c3,c8,c9 (5 coeffs)
|
|
421
|
+
# zeta_{CO2-Na+-Cl-} uses c1,c2,c8,c9 (4 coeffs)
|
|
422
|
+
# lambda_{CO2-Cl-} = 0 (set to zero)
|
|
423
|
+
#
|
|
424
|
+
# ln(gamma_CO2) = 2*m*lambda + m^2*zeta
|
|
425
|
+
# Effective Sechenov: ks = (2*lambda + zeta*m) / ln(10)
|
|
426
|
+
#
|
|
427
|
+
# Validity: 273-533 K, 0-2000 bar, 0-4.3 m NaCl
|
|
428
|
+
# ====================================================================
|
|
429
|
+
|
|
430
|
+
def _duan2003_lambda_co2(T_K, P_bar=100.0):
|
|
431
|
+
"""CO2-Na+ Pitzer lambda (Duan & Sun 2003 Table 2). T in K, P in bar."""
|
|
432
|
+
T = np.asarray(T_K, dtype=float)
|
|
433
|
+
P = float(P_bar)
|
|
434
|
+
return (-0.411370585 + 6.07632013e-4 * T + 97.5347708 / T
|
|
435
|
+
- 0.0237622469 * P / T + 0.0170656236 * P / (630.0 - T))
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def _duan2003_zeta_co2(T_K, P_bar=100.0):
|
|
439
|
+
"""CO2-Na+-Cl- Pitzer zeta (Duan & Sun 2003 Table 2). T in K, P in bar."""
|
|
440
|
+
T = np.asarray(T_K, dtype=float)
|
|
441
|
+
P = float(P_bar)
|
|
442
|
+
return (3.36389723e-4 - 1.98298980e-5 * T
|
|
443
|
+
+ 2.12220830e-3 * P / T - 5.24873303e-3 * P / (630.0 - T))
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def ks_duan2003_co2(T_K, m_NaCl=0.0, P_bar=100.0):
|
|
447
|
+
"""Effective Sechenov for CO2 from Duan & Sun 2003 Pitzer model (log10).
|
|
448
|
+
|
|
449
|
+
ln(gamma_CO2) = 2*m*lambda(T,P) + m^2*zeta(T,P)
|
|
450
|
+
ks = (2*lambda + zeta*m) / ln(10)
|
|
451
|
+
|
|
452
|
+
Parameters
|
|
453
|
+
----------
|
|
454
|
+
T_K : float or array
|
|
455
|
+
Temperature in Kelvin (273-533 K).
|
|
456
|
+
m_NaCl : float
|
|
457
|
+
NaCl molality (0-4.3 m). Affects effective ks through zeta term.
|
|
458
|
+
P_bar : float
|
|
459
|
+
Pressure in bar (0-2000 bar). Default 100.
|
|
460
|
+
|
|
461
|
+
Returns
|
|
462
|
+
-------
|
|
463
|
+
ks : float or array
|
|
464
|
+
Effective Sechenov coefficient (log10 basis, molality scale).
|
|
465
|
+
"""
|
|
466
|
+
lam = _duan2003_lambda_co2(T_K, P_bar)
|
|
467
|
+
zeta = _duan2003_zeta_co2(T_K, P_bar)
|
|
468
|
+
return (2.0 * lam + zeta * m_NaCl) / LN10
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
# ====================================================================
|
|
472
|
+
# Convenience: unified interface
|
|
473
|
+
# ====================================================================
|
|
474
|
+
|
|
475
|
+
def ks_library(T_K, gas, source='sw_eq8', m_NaCl=0.0, **kwargs):
|
|
476
|
+
"""Unified Sechenov coefficient lookup (log10 basis, molality scale).
|
|
477
|
+
|
|
478
|
+
Parameters
|
|
479
|
+
----------
|
|
480
|
+
T_K : float or array
|
|
481
|
+
Temperature in Kelvin.
|
|
482
|
+
gas : str
|
|
483
|
+
Gas name: 'H2', 'N2', 'CH4', 'C2H6', 'C3H8', 'CO2', 'H2S', etc.
|
|
484
|
+
source : str
|
|
485
|
+
'sw_eq8' — Soreide & Whitson 1992 Eq 8 (all gases)
|
|
486
|
+
'dubessy' — Dubessy et al. 2005 (CO2, H2S only)
|
|
487
|
+
'akinfiev' — Akinfiev et al. 2016 Pitzer (H2S only)
|
|
488
|
+
'akinfiev_table'— Akinfiev et al. 2016 from tables (H2S only)
|
|
489
|
+
'li2015' — Li et al. 2015 Pitzer (CH4, C2H6, C3H8, nC4H10)
|
|
490
|
+
'mao2006' — Mao & Duan 2006 Pitzer (N2 only)
|
|
491
|
+
'duan2003' — Duan & Sun 2003 Pitzer (CO2 only)
|
|
492
|
+
m_NaCl : float
|
|
493
|
+
NaCl molality (needed for non-linear Sechenov models).
|
|
494
|
+
|
|
495
|
+
Returns
|
|
496
|
+
-------
|
|
497
|
+
ks : float or array
|
|
498
|
+
"""
|
|
499
|
+
gas = gas.upper()
|
|
500
|
+
if gas == 'NC4H10':
|
|
501
|
+
pass # already normalized
|
|
502
|
+
elif gas == 'NC4':
|
|
503
|
+
gas = 'NC4H10'
|
|
504
|
+
src = source.lower()
|
|
505
|
+
|
|
506
|
+
if src == 'sw_eq8':
|
|
507
|
+
return ks_sw_eq8(T_K, gas)
|
|
508
|
+
elif src == 'dubessy':
|
|
509
|
+
if gas == 'CO2':
|
|
510
|
+
return ks_dubessy_co2(T_K, m_NaCl)
|
|
511
|
+
elif gas == 'H2S':
|
|
512
|
+
return ks_dubessy_h2s(T_K, m_NaCl)
|
|
513
|
+
else:
|
|
514
|
+
raise ValueError(f"Dubessy only covers CO2 and H2S, not {gas}")
|
|
515
|
+
elif src == 'akinfiev':
|
|
516
|
+
if gas == 'H2S':
|
|
517
|
+
return ks_akinfiev_h2s(T_K, m_NaCl, **kwargs)
|
|
518
|
+
else:
|
|
519
|
+
raise ValueError(f"Akinfiev 2016 only covers H2S, not {gas}")
|
|
520
|
+
elif src == 'akinfiev_table':
|
|
521
|
+
if gas == 'H2S':
|
|
522
|
+
return ks_akinfiev_h2s_from_tables(T_K, m_NaCl)
|
|
523
|
+
else:
|
|
524
|
+
raise ValueError(f"Akinfiev tables only for H2S, not {gas}")
|
|
525
|
+
elif src == 'li2015':
|
|
526
|
+
return ks_li2015(T_K, gas, m_NaCl, **kwargs)
|
|
527
|
+
elif src == 'mao2006':
|
|
528
|
+
if gas == 'N2':
|
|
529
|
+
return ks_mao2006_n2(T_K, m_NaCl, **kwargs)
|
|
530
|
+
else:
|
|
531
|
+
raise ValueError(f"Mao & Duan 2006 only covers N2, not {gas}")
|
|
532
|
+
elif src == 'duan2003':
|
|
533
|
+
if gas == 'CO2':
|
|
534
|
+
return ks_duan2003_co2(T_K, m_NaCl, **kwargs)
|
|
535
|
+
else:
|
|
536
|
+
raise ValueError(f"Duan & Sun 2003 only covers CO2, not {gas}")
|
|
537
|
+
else:
|
|
538
|
+
raise ValueError(f"Unknown source: {source}")
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
# ====================================================================
|
|
542
|
+
# Main: comparison table
|
|
543
|
+
# ====================================================================
|
|
544
|
+
if __name__ == '__main__':
|
|
545
|
+
temps_C = [25, 50, 75, 100, 125, 150, 200, 250]
|
|
546
|
+
temps_K = [T + 273.15 for T in temps_C]
|
|
547
|
+
|
|
548
|
+
print("=" * 80)
|
|
549
|
+
print("Sechenov Coefficient Library — All values on log10 / molality basis")
|
|
550
|
+
print(" ks > 0 means salting-out; log10(S_water/S_brine) = ks * m_NaCl")
|
|
551
|
+
print("=" * 80)
|
|
552
|
+
|
|
553
|
+
# --- CO2 comparison ---
|
|
554
|
+
print("\n--- CO2: S&W vs Dubessy vs Duan 2003 ---")
|
|
555
|
+
print(f"{'T(C)':>6} {'S&W Eq8':>8} {'Dub m=0':>8} {'Dub m=1':>8} "
|
|
556
|
+
f"{'Dub m=4':>8} {'Duan m=0':>9} {'Duan m=1':>9} {'Duan m=4':>9}")
|
|
557
|
+
print("-" * 82)
|
|
558
|
+
for Tc, Tk in zip(temps_C, temps_K):
|
|
559
|
+
sw = ks_sw_eq8(Tk, 'CO2')
|
|
560
|
+
d0 = ks_dubessy_co2(Tk, 0)
|
|
561
|
+
d1 = ks_dubessy_co2(Tk, 1)
|
|
562
|
+
d4 = ks_dubessy_co2(Tk, 4)
|
|
563
|
+
if Tk <= 533:
|
|
564
|
+
dn0 = ks_duan2003_co2(Tk, 0, 100)
|
|
565
|
+
dn1 = ks_duan2003_co2(Tk, 1, 100)
|
|
566
|
+
dn4 = ks_duan2003_co2(Tk, 4, 100)
|
|
567
|
+
print(f"{Tc:>6} {sw:8.4f} {d0:8.4f} {d1:8.4f} {d4:8.4f} "
|
|
568
|
+
f"{dn0:9.4f} {dn1:9.4f} {dn4:9.4f}")
|
|
569
|
+
else:
|
|
570
|
+
print(f"{Tc:>6} {sw:8.4f} {d0:8.4f} {d1:8.4f} {d4:8.4f} "
|
|
571
|
+
f"{'n/a':>9} {'n/a':>9} {'n/a':>9}")
|
|
572
|
+
|
|
573
|
+
# --- H2S comparison ---
|
|
574
|
+
print("\n--- H2S ---")
|
|
575
|
+
print(f"{'T(C)':>6} {'S&W Eq8':>8} {'Dub m=0':>8} {'Dub m=1':>8} "
|
|
576
|
+
f"{'Akin m=1':>9} {'Akin m=4':>9} {'AkTbl m=1':>10} {'AkTbl m=4':>10}")
|
|
577
|
+
print("-" * 90)
|
|
578
|
+
for Tc, Tk in zip(temps_C, temps_K):
|
|
579
|
+
sw = ks_sw_eq8(Tk, 'H2S')
|
|
580
|
+
d0 = ks_dubessy_h2s(Tk, 0)
|
|
581
|
+
d1 = ks_dubessy_h2s(Tk, 1)
|
|
582
|
+
ak1 = ks_akinfiev_h2s(Tk, m_NaCl=1.0, m_h2s_approx=0.1)
|
|
583
|
+
ak4 = ks_akinfiev_h2s(Tk, m_NaCl=4.0, m_h2s_approx=0.1)
|
|
584
|
+
# Table-based (only for T >= 323.15 K)
|
|
585
|
+
if Tk >= 323.15:
|
|
586
|
+
akt1 = ks_akinfiev_h2s_from_tables(Tk, 1)
|
|
587
|
+
akt4 = ks_akinfiev_h2s_from_tables(Tk, 4)
|
|
588
|
+
print(f"{Tc:>6} {sw:8.4f} {d0:8.4f} {d1:8.4f} "
|
|
589
|
+
f"{ak1:9.4f} {ak4:9.4f} {akt1:10.4f} {akt4:10.4f}")
|
|
590
|
+
else:
|
|
591
|
+
print(f"{Tc:>6} {sw:8.4f} {d0:8.4f} {d1:8.4f} "
|
|
592
|
+
f"{ak1:9.4f} {ak4:9.4f} {'n/a':>10} {'n/a':>10}")
|
|
593
|
+
|
|
594
|
+
# --- Light HC comparison: S&W vs Li 2015 ---
|
|
595
|
+
print("\n--- Light Hydrocarbons: S&W Eq 8 vs Li et al. 2015 Pitzer ---")
|
|
596
|
+
print(" Li 2015 at P=100 bar, m=0 (infinite dilution) and m=2")
|
|
597
|
+
hc_gases = ['CH4', 'C2H6', 'C3H8', 'nC4H10']
|
|
598
|
+
print(f"{'T(C)':>6}", end="")
|
|
599
|
+
for g in hc_gases:
|
|
600
|
+
print(f" {'SW '+g:>11} {'Li m=0':>7} {'Li m=2':>7}", end="")
|
|
601
|
+
print()
|
|
602
|
+
print("-" * 118)
|
|
603
|
+
for Tc, Tk in zip(temps_C, temps_K):
|
|
604
|
+
print(f"{Tc:>6}", end="")
|
|
605
|
+
for g in hc_gases:
|
|
606
|
+
sw = ks_sw_eq8(Tk, g)
|
|
607
|
+
li0 = ks_li2015(Tk, g, m_NaCl=0.0, P_bar=100.0)
|
|
608
|
+
li2 = ks_li2015(Tk, g, m_NaCl=2.0, P_bar=100.0)
|
|
609
|
+
print(f" {sw:11.4f} {li0:7.4f} {li2:7.4f}", end="")
|
|
610
|
+
print()
|
|
611
|
+
|
|
612
|
+
# --- All S&W gases ---
|
|
613
|
+
print("\n--- S&W Eq 8 for all gases (m-independent) ---")
|
|
614
|
+
gases = ['H2', 'N2', 'CH4', 'C2H6', 'C3H8', 'nC4H10', 'CO2', 'H2S']
|
|
615
|
+
header = f"{'T(C)':>6}" + "".join(f" {g:>8}" for g in gases)
|
|
616
|
+
print(header)
|
|
617
|
+
print("-" * (8 + 10 * len(gases)))
|
|
618
|
+
for Tc, Tk in zip(temps_C, temps_K):
|
|
619
|
+
row = f"{Tc:>6}"
|
|
620
|
+
for g in gases:
|
|
621
|
+
row += f" {ks_sw_eq8(Tk, g):8.4f}"
|
|
622
|
+
print(row)
|
|
623
|
+
|
|
624
|
+
# --- N2 comparison: S&W vs Mao & Duan 2006 ---
|
|
625
|
+
print("\n--- N2: S&W Eq 8 vs Mao & Duan 2006 Pitzer ---")
|
|
626
|
+
print(" Mao 2006 at P=100 bar, various m_NaCl")
|
|
627
|
+
print(f"{'T(C)':>6} {'S&W Eq8':>8} {'Mao m=0':>8} {'Mao m=1':>8} "
|
|
628
|
+
f"{'Mao m=2':>8} {'Mao m=4':>8}")
|
|
629
|
+
print("-" * 56)
|
|
630
|
+
for Tc, Tk in zip(temps_C, temps_K):
|
|
631
|
+
if Tk > 400:
|
|
632
|
+
# Mao 2006 valid to 400 K only
|
|
633
|
+
sw = ks_sw_eq8(Tk, 'N2')
|
|
634
|
+
print(f"{Tc:>6} {sw:8.4f} {'n/a':>8} {'n/a':>8} {'n/a':>8} {'n/a':>8}")
|
|
635
|
+
else:
|
|
636
|
+
sw = ks_sw_eq8(Tk, 'N2')
|
|
637
|
+
m0 = ks_mao2006_n2(Tk, m_NaCl=0.0, P_bar=100.0)
|
|
638
|
+
m1 = ks_mao2006_n2(Tk, m_NaCl=1.0, P_bar=100.0)
|
|
639
|
+
m2 = ks_mao2006_n2(Tk, m_NaCl=2.0, P_bar=100.0)
|
|
640
|
+
m4 = ks_mao2006_n2(Tk, m_NaCl=4.0, P_bar=100.0)
|
|
641
|
+
print(f"{Tc:>6} {sw:8.4f} {m0:8.4f} {m1:8.4f} {m2:8.4f} {m4:8.4f}")
|
|
642
|
+
|
|
643
|
+
# --- N2: Pressure sensitivity (Mao 2006) ---
|
|
644
|
+
print("\n--- N2: Mao 2006 ks at different pressures (m=0) ---")
|
|
645
|
+
print(f"{'T(C)':>6} {'1 bar':>8} {'50 bar':>8} {'100 bar':>8} "
|
|
646
|
+
f"{'200 bar':>8} {'500 bar':>8} {'S&W':>8}")
|
|
647
|
+
print("-" * 62)
|
|
648
|
+
for Tc, Tk in zip(temps_C, temps_K):
|
|
649
|
+
if Tk > 400:
|
|
650
|
+
sw = ks_sw_eq8(Tk, 'N2')
|
|
651
|
+
print(f"{Tc:>6} {'n/a':>8} {'n/a':>8} {'n/a':>8} {'n/a':>8} {'n/a':>8} {sw:8.4f}")
|
|
652
|
+
else:
|
|
653
|
+
vals = [ks_mao2006_n2(Tk, 0, P) for P in [1, 50, 100, 200, 500]]
|
|
654
|
+
sw = ks_sw_eq8(Tk, 'N2')
|
|
655
|
+
print(f"{Tc:>6}" + "".join(f" {v:8.4f}" for v in vals) + f" {sw:8.4f}")
|
|
656
|
+
|
|
657
|
+
# --- CH4: Pressure sensitivity (Li 2015) ---
|
|
658
|
+
print("\n--- CH4: Li 2015 ks at different pressures (m=0) ---")
|
|
659
|
+
print(f"{'T(C)':>6} {'1 bar':>8} {'50 bar':>8} {'100 bar':>8} "
|
|
660
|
+
f"{'200 bar':>8} {'500 bar':>8} {'S&W':>8}")
|
|
661
|
+
print("-" * 62)
|
|
662
|
+
for Tc, Tk in zip(temps_C, temps_K):
|
|
663
|
+
vals = [ks_li2015(Tk, 'CH4', 0, P) for P in [1, 50, 100, 200, 500]]
|
|
664
|
+
sw = ks_sw_eq8(Tk, 'CH4')
|
|
665
|
+
print(f"{Tc:>6}" + "".join(f" {v:8.4f}" for v in vals) + f" {sw:8.4f}")
|
|
666
|
+
|
|
667
|
+
# --- H2S model comparison summary ---
|
|
668
|
+
print("\n--- H2S: Akinfiev table ks at multiple pressures (log10) ---")
|
|
669
|
+
print(" Showing that ks is largely P-independent (as expected)")
|
|
670
|
+
# Tables at 1 MPa and 10 MPa for m=2
|
|
671
|
+
T_tab = [323.15, 373.15, 423.15, 473.15, 523.15]
|
|
672
|
+
m0_1 = [0.596, 0.297, 0.136, np.nan, np.nan] # pure water, 1 MPa
|
|
673
|
+
m2_1 = [0.467, 0.241, 0.111, np.nan, np.nan] # 2m NaCl, 1 MPa
|
|
674
|
+
m0_5 = [2.026, 1.651, 1.271, 0.956, 0.300] # pure water, 5 MPa
|
|
675
|
+
m2_5 = [1.523, 1.259, 0.975, 0.726, 0.226] # 2m NaCl, 5 MPa
|
|
676
|
+
m0_10 = [2.100, 2.703, 2.752, 2.622, 2.045] # pure water, 10 MPa
|
|
677
|
+
m2_10 = [1.577, 1.980, 1.969, 1.834, 1.411] # 2m NaCl, 10 MPa
|
|
678
|
+
m0_20 = [2.237, 3.065, 4.407, 5.395, 5.458] # pure water, 20 MPa
|
|
679
|
+
m2_20 = [1.676, 2.224, 3.039, 3.659, 3.677] # 2m NaCl, 20 MPa
|
|
680
|
+
|
|
681
|
+
print(f"{'T(C)':>6} {'1 MPa':>8} {'5 MPa':>8} {'10 MPa':>8} {'20 MPa':>8}")
|
|
682
|
+
print("-" * 46)
|
|
683
|
+
for i, Tk in enumerate(T_tab):
|
|
684
|
+
Tc = Tk - 273.15
|
|
685
|
+
vals = []
|
|
686
|
+
for mw, mb in [(m0_1[i], m2_1[i]), (m0_5[i], m2_5[i]),
|
|
687
|
+
(m0_10[i], m2_10[i]), (m0_20[i], m2_20[i])]:
|
|
688
|
+
if np.isnan(mw) or np.isnan(mb) or mb <= 0:
|
|
689
|
+
vals.append(" n/a ")
|
|
690
|
+
else:
|
|
691
|
+
ks = np.log10(mw / mb) / 2.0
|
|
692
|
+
vals.append(f"{ks:8.4f}")
|
|
693
|
+
print(f"{Tc:>6.0f} {' '.join(vals)}")
|