pyrestoolbox 2.5.0__tar.gz → 2.5.1__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.5.0/pyrestoolbox.egg-info → pyrestoolbox-2.5.1}/PKG-INFO +1 -1
- pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/__pycache__/brine_properties.cpython-310.pyc/357/200/272Zone.Identifier +0 -0
- pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/__pycache__/garcia_mixing.cpython-310.pyc/357/200/272Zone.Identifier +0 -0
- pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/__pycache__/iapws_if97.cpython-310.pyc/357/200/272Zone.Identifier +0 -0
- pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/__pycache__/plyasunov_model.cpython-310.pyc/357/200/272Zone.Identifier +0 -0
- pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/__pycache__/water_properties.cpython-310.pyc/357/200/272Zone.Identifier +0 -0
- pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/brine_properties.py +245 -0
- pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/brine_properties.py/357/200/272Zone.Identifier +0 -0
- pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/garcia_mixing.py +390 -0
- pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/garcia_mixing.py/357/200/272Zone.Identifier +0 -0
- pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/generate_technical_note.py +433 -0
- pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/generate_technical_note.py/357/200/272Zone.Identifier +0 -0
- pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/iapws_if97.py +240 -0
- pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/iapws_if97.py/357/200/272Zone.Identifier +0 -0
- pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/papers/14211-MS.pdf +0 -0
- pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/papers/15081-MS.pdf +0 -0
- pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/papers/96013-MS.pdf +0 -0
- pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/papers/H2S Aqueous Viscosity je60063a015.pdf +0 -0
- pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/plyasunov_model.py +404 -0
- pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/plyasunov_model.py/357/200/272Zone.Identifier +0 -0
- pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/project_summary.txt +165 -0
- pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/project_summary.txt/357/200/272Zone.Identifier +0 -0
- pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/pyrestoolbox_integration.txt +614 -0
- pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/pyrestoolbox_integration.txt/357/200/272Zone.Identifier +0 -0
- pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/validation.py +251 -0
- pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/validation.py/357/200/272Zone.Identifier +0 -0
- pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/water_properties.py +139 -0
- pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/water_properties.py/357/200/272Zone.Identifier +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/classes/classes.py +7 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/docs/.ipynb_checkpoints/examples-checkpoint.ipynb +71 -71
- pyrestoolbox-2.5.1/pyrestoolbox/docs/.ipynb_checkpoints/nodal_examples-checkpoint.ipynb +825 -0
- pyrestoolbox-2.5.1/pyrestoolbox/docs/examples.ipynb +3284 -0
- pyrestoolbox-2.5.1/pyrestoolbox/docs/nodal.rst +787 -0
- pyrestoolbox-2.5.1/pyrestoolbox/docs/nodal_examples.ipynb +845 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/gas/gas.py +51 -0
- pyrestoolbox-2.5.1/pyrestoolbox/nodal/__init__.py +1 -0
- pyrestoolbox-2.5.1/pyrestoolbox/nodal/nodal.py +2009 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/oil/oil.py +54 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/tests/run_all_tests.py +1 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/tests/test_doc_examples.py +184 -0
- pyrestoolbox-2.5.1/pyrestoolbox/tests/test_nodal.py +636 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1/pyrestoolbox.egg-info}/PKG-INFO +1 -1
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox.egg-info/SOURCES.txt +33 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/setup.cfg +1 -1
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/setup.py +1 -1
- pyrestoolbox-2.5.0/pyrestoolbox/docs/examples.ipynb +0 -1458
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/LICENSE +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/MANIFEST.in +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/README.md +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/README.rst +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyproject.toml +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/__init__.py +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/brine/__init__.py +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/brine/_lib_salting_library.py +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/brine/_lib_vle_engine.py +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/brine/brine.py +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/classes/__init__.py +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/constants/__init__.py +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/constants/constants.py +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/docs/brine.rst +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/docs/changelist.rst +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/docs/gas.rst +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/docs/img/bot.png +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/docs/img/bot_PVTO.png +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/docs/img/bot_img.png +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/docs/img/dry_gas.png +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/docs/img/grid_sat_df.png +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/docs/img/influence.png +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/docs/img/properties_df.png +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/docs/img/sgof.png +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/docs/img/swof.png +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/docs/layer.rst +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/docs/library.rst +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/docs/oil.rst +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/docs/simtools.rst +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/gas/__init__.py +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/layer/__init__.py +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/layer/layer.py +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/library/__init__.py +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/library/component_library.xlsx +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/library/library.py +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/oil/__init__.py +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/plyasunov/__init__.py +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/plyasunov/iapws_if97.py +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/plyasunov/plyasunov_model.py +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/plyasunov/water_properties.py +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/shared_fns/__init__.py +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/shared_fns/shared_fns.py +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/simtools/__init__.py +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/simtools/simtools.py +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/tests/__init__.py +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/tests/test_brine.py +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/tests/test_gas.py +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/tests/test_layer.py +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/tests/test_oil.py +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/tests/test_simtools.py +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/tests/test_unified_brine_design.py +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/tests/test_viscosity_scaling.py +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/validate/__init__.py +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/validate/validate.py +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox.egg-info/dependency_links.txt +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox.egg-info/requires.txt +0 -0
- {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox.egg-info/top_level.txt +0 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Brine density correlations for NaCl solutions.
|
|
3
|
+
|
|
4
|
+
Provides:
|
|
5
|
+
- rho_brine(T, P, S): brine density in kg/m³ (primary entry point)
|
|
6
|
+
- rho_brine_spivey(T, P, S): Spivey et al. (modified) from McCain (2011)
|
|
7
|
+
- rho_brine_bw(T, P, S): Batzle & Wang (1992) correlation (legacy)
|
|
8
|
+
|
|
9
|
+
Parameters:
|
|
10
|
+
T: temperature in K
|
|
11
|
+
P: pressure in MPa
|
|
12
|
+
S: salinity as weight fraction of NaCl (e.g., 0.1 = 100,000 ppm = 10 wt%)
|
|
13
|
+
|
|
14
|
+
References:
|
|
15
|
+
Spivey et al. (modified): McCain, Petroleum Reservoir Fluid Properties,
|
|
16
|
+
Chapter 4, Eqs. 4.1-4.12. Multi-step thermodynamic approach with
|
|
17
|
+
reference state at 70 MPa. Used by pyResToolbox.
|
|
18
|
+
Batzle & Wang (1992): Geophysics 57(11), 1396-1408.
|
|
19
|
+
Simpler correlation. Valid: 10-350°C, 5-100 MPa, 0-0.3 wt fraction.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import numpy as np
|
|
23
|
+
from water_properties import rho_w
|
|
24
|
+
|
|
25
|
+
# =============================================================================
|
|
26
|
+
# Spivey coefficient tables (from pyResToolbox brine.py / McCain Ch.4)
|
|
27
|
+
# =============================================================================
|
|
28
|
+
|
|
29
|
+
# Rational function form (Eq 4.1): f(t) = (a1*(t/100)^2 + a2*(t/100) + a3) / (a4*(t/100)^2 + a5*(t/100) + 1)
|
|
30
|
+
# Input arrays are [0, a1, a2, a3, a4, a5] (index 0 unused)
|
|
31
|
+
|
|
32
|
+
_RHOW_T70 = [0, -0.127213, 0.645486, 1.03265, -0.070291, 0.639589]
|
|
33
|
+
_EWT = [0, 4.221, -3.478, 6.221, 0.5182, -0.4405]
|
|
34
|
+
_FWT = [0, -11.403, 29.932, 27.952, 0.20684, 0.3768]
|
|
35
|
+
_DM2T = [0, -0.00011149, 0.000175105, -0.00043766, 0, 0]
|
|
36
|
+
_DM32T = [0, -0.0008878, -0.0001388, -0.00296318, 0, 0.51103]
|
|
37
|
+
_DM1T = [0, 0.0021466, 0.012427, 0.042648, -0.081009, 0.525417]
|
|
38
|
+
_DM12T = [0, 0.0002356, -0.0003636, -0.0002278, 0, 0]
|
|
39
|
+
_EMT = [0, 0, 0, 0.1249, 0, 0]
|
|
40
|
+
_FM32T = [0, -0.617, -0.747, -0.4339, 0, 10.26]
|
|
41
|
+
_FM1T = [0, 0, 9.917, 5.1128, 0, 3.892]
|
|
42
|
+
_FM12T = [0, 0.0365, -0.0369, 0, 0, 0]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _eq41(t_celsius, coeffs):
|
|
46
|
+
"""McCain Eq. 4.1 rational function. t in °C, coeffs = [0, a1..a5]."""
|
|
47
|
+
t2 = t_celsius / 100.0
|
|
48
|
+
return (coeffs[1] * t2**2 + coeffs[2] * t2 + coeffs[3]) / \
|
|
49
|
+
(coeffs[4] * t2**2 + coeffs[5] * t2 + 1.0)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def rho_brine_spivey(T, P, S):
|
|
53
|
+
"""
|
|
54
|
+
Brine density from Spivey et al. (modified) per McCain Ch. 4, Eqs 4.1-4.12.
|
|
55
|
+
|
|
56
|
+
This is the same correlation used by pyResToolbox. It uses a thermodynamic
|
|
57
|
+
reference state at 70 MPa and compressibility-based corrections to compute
|
|
58
|
+
density at arbitrary (T, P).
|
|
59
|
+
|
|
60
|
+
Parameters:
|
|
61
|
+
T: temperature in K
|
|
62
|
+
P: pressure in MPa
|
|
63
|
+
S: salinity as weight fraction of NaCl (0 to ~0.3)
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
brine density in kg/m³
|
|
67
|
+
"""
|
|
68
|
+
T_C = float(T) - 273.15
|
|
69
|
+
MPa = float(P)
|
|
70
|
+
S_val = float(S)
|
|
71
|
+
|
|
72
|
+
# Molality from weight fraction: m = 1000*S / (M_NaCl * (1-S))
|
|
73
|
+
M_NaCl = 58.4428
|
|
74
|
+
if S_val > 0:
|
|
75
|
+
wt_pct = S_val # weight fraction (0-1)
|
|
76
|
+
m = 1000.0 * wt_pct / (M_NaCl * (1.0 - wt_pct))
|
|
77
|
+
else:
|
|
78
|
+
m = 0.0
|
|
79
|
+
|
|
80
|
+
# Step 1: Density of pure water at T and 70 MPa reference pressure (g/cm³)
|
|
81
|
+
rhow_t70 = _eq41(T_C, _RHOW_T70)
|
|
82
|
+
|
|
83
|
+
# Step 2: Compressibility coefficients for pure water
|
|
84
|
+
Ewt = _eq41(T_C, _EWT)
|
|
85
|
+
Fwt = _eq41(T_C, _FWT)
|
|
86
|
+
|
|
87
|
+
# Step 3: Pure water density at (T, P) via compressibility integral
|
|
88
|
+
Iwt70 = (1.0 / Ewt) * np.log(abs(Ewt + Fwt)) # Eq 4.3
|
|
89
|
+
Iwtp = (1.0 / Ewt) * np.log(abs(Ewt * (MPa / 70.0) + Fwt)) # Eq 4.4
|
|
90
|
+
rhowtp = rhow_t70 * np.exp(Iwtp - Iwt70) # Eq 4.5
|
|
91
|
+
|
|
92
|
+
if m == 0.0:
|
|
93
|
+
return float(rhowtp) * 1000.0 # g/cm³ → kg/m³
|
|
94
|
+
|
|
95
|
+
# Step 4: Salt correction coefficients
|
|
96
|
+
Dm2t = _eq41(T_C, _DM2T)
|
|
97
|
+
Dm32t = _eq41(T_C, _DM32T)
|
|
98
|
+
Dm1t = _eq41(T_C, _DM1T)
|
|
99
|
+
Dm12t = _eq41(T_C, _DM12T)
|
|
100
|
+
|
|
101
|
+
# Step 5: Brine compressibility correction coefficients
|
|
102
|
+
Emt = _eq41(T_C, _EMT)
|
|
103
|
+
Fm32t = _eq41(T_C, _FM32T)
|
|
104
|
+
Fm1t = _eq41(T_C, _FM1T)
|
|
105
|
+
Fm12t = _eq41(T_C, _FM12T)
|
|
106
|
+
|
|
107
|
+
# Brine density at T and 70 MPa (Eq 4.6)
|
|
108
|
+
rhobt70 = rhow_t70 + Dm2t * m**2 + Dm32t * m**1.5 + Dm1t * m + Dm12t * m**0.5
|
|
109
|
+
|
|
110
|
+
# Brine compressibility coefficients (Eqs 4.7-4.8)
|
|
111
|
+
Ebtm = Ewt + Emt * m
|
|
112
|
+
Fbtm = Fwt + Fm32t * m**1.5 + Fm1t * m + Fm12t * m**0.5
|
|
113
|
+
|
|
114
|
+
# Brine density at (T, P) via compressibility integral (Eqs 4.10-4.12)
|
|
115
|
+
Ibt70 = (1.0 / Ebtm) * np.log(abs(Ebtm + Fbtm))
|
|
116
|
+
Ibtpm = (1.0 / Ebtm) * np.log(abs(Ebtm * (MPa / 70.0) + Fbtm))
|
|
117
|
+
rhobtpm = rhobt70 * np.exp(Ibtpm - Ibt70) # g/cm³
|
|
118
|
+
|
|
119
|
+
return float(rhobtpm) * 1000.0 # g/cm³ → kg/m³
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def rho_brine_bw(T, P, S):
|
|
123
|
+
"""
|
|
124
|
+
Brine density from Batzle & Wang (1992) Eq. 27.
|
|
125
|
+
|
|
126
|
+
Parameters:
|
|
127
|
+
T: temperature in K (converted internally to °C)
|
|
128
|
+
P: pressure in MPa
|
|
129
|
+
S: salinity as weight fraction of NaCl (0 to ~0.3)
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
brine density in kg/m³
|
|
133
|
+
"""
|
|
134
|
+
T = np.atleast_1d(np.asarray(T, dtype=float))
|
|
135
|
+
P = np.atleast_1d(np.asarray(P, dtype=float))
|
|
136
|
+
S = np.atleast_1d(np.asarray(S, dtype=float))
|
|
137
|
+
|
|
138
|
+
T, P, S = np.broadcast_arrays(T, P, S)
|
|
139
|
+
|
|
140
|
+
T_C = T - 273.15
|
|
141
|
+
|
|
142
|
+
rho_water = np.zeros_like(T_C)
|
|
143
|
+
for i in np.ndindex(T.shape):
|
|
144
|
+
rho_water[i] = rho_w(float(T[i]), float(P[i]))
|
|
145
|
+
|
|
146
|
+
rho_w_gcc = rho_water / 1000.0
|
|
147
|
+
|
|
148
|
+
rho_b_gcc = rho_w_gcc + S * (0.668 + 0.44 * S + 1e-6 * (
|
|
149
|
+
300.0 * P - 2400.0 * P * S
|
|
150
|
+
+ T_C * (80.0 + 3.0 * T_C - 3300.0 * S - 13.0 * P + 47.0 * P * S)
|
|
151
|
+
))
|
|
152
|
+
|
|
153
|
+
rho_b = rho_b_gcc * 1000.0
|
|
154
|
+
|
|
155
|
+
return float(rho_b) if rho_b.ndim == 0 else rho_b.squeeze()
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def rho_brine(T, P, S):
|
|
159
|
+
"""
|
|
160
|
+
Brine density — main entry point.
|
|
161
|
+
|
|
162
|
+
Uses Spivey et al. (modified) correlation from McCain (2011).
|
|
163
|
+
|
|
164
|
+
Parameters:
|
|
165
|
+
T: temperature in K
|
|
166
|
+
P: pressure in MPa
|
|
167
|
+
S: salinity as weight fraction of NaCl
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
density in kg/m³
|
|
171
|
+
"""
|
|
172
|
+
return rho_brine_spivey(T, P, S)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def rho_water_spivey(T, P):
|
|
176
|
+
"""
|
|
177
|
+
Pure water density from Spivey correlation (S=0).
|
|
178
|
+
|
|
179
|
+
Useful for comparing against IAPWS-95.
|
|
180
|
+
|
|
181
|
+
Parameters:
|
|
182
|
+
T: temperature in K
|
|
183
|
+
P: pressure in MPa
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
density in kg/m³
|
|
187
|
+
"""
|
|
188
|
+
return rho_brine_spivey(T, P, 0.0)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def salinity_from_ppm(ppm):
|
|
192
|
+
"""Convert salinity from ppm (mg/L ~ mg/kg) to weight fraction."""
|
|
193
|
+
return ppm / 1e6
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def salinity_from_molality(m_NaCl):
|
|
197
|
+
"""
|
|
198
|
+
Convert NaCl molality (mol/kg solvent) to weight fraction.
|
|
199
|
+
|
|
200
|
+
S = m * M_NaCl / (1000 + m * M_NaCl)
|
|
201
|
+
where M_NaCl = 58.44 g/mol
|
|
202
|
+
"""
|
|
203
|
+
M_NaCl = 58.44
|
|
204
|
+
return m_NaCl * M_NaCl / (1000.0 + m_NaCl * M_NaCl)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
if __name__ == "__main__":
|
|
208
|
+
print("=== Brine Density Validation ===\n")
|
|
209
|
+
|
|
210
|
+
T_test = 298.15 # 25°C
|
|
211
|
+
P_test = 10.0 # 10 MPa
|
|
212
|
+
|
|
213
|
+
rho_pure = rho_w(T_test, P_test)
|
|
214
|
+
rho_pure_spivey = rho_water_spivey(T_test, P_test)
|
|
215
|
+
print(f"Pure water at T={T_test}K, P={P_test}MPa:")
|
|
216
|
+
print(f" IAPWS-95: ρ = {rho_pure:.4f} kg/m³")
|
|
217
|
+
print(f" Spivey: ρ = {rho_pure_spivey:.4f} kg/m³")
|
|
218
|
+
print(f" Diff: {rho_pure_spivey - rho_pure:+.4f} kg/m³")
|
|
219
|
+
|
|
220
|
+
print(f"\nBrine density (Spivey vs Batzle-Wang):")
|
|
221
|
+
print(f"{'S(wt%)':>8} {'Spivey':>10} {'B&W':>10} {'Diff':>8} {'IAPWS_w':>10}")
|
|
222
|
+
print("-" * 52)
|
|
223
|
+
|
|
224
|
+
for S_pct in [0, 1, 3, 5, 10, 15, 20, 25]:
|
|
225
|
+
S = S_pct / 100.0
|
|
226
|
+
rho_sp = rho_brine_spivey(T_test, P_test, S)
|
|
227
|
+
rho_bw = rho_brine_bw(T_test, P_test, S)
|
|
228
|
+
print(f"{S_pct:8.1f} {rho_sp:10.4f} {rho_bw:10.4f} {rho_sp - rho_bw:+8.4f} {rho_pure:10.4f}")
|
|
229
|
+
|
|
230
|
+
# Temperature sweep at 10 wt% NaCl
|
|
231
|
+
S_10 = 0.10
|
|
232
|
+
print(f"\n\nBrine density at S=10 wt% NaCl, P=30 MPa:")
|
|
233
|
+
print(f"{'T(°C)':>6} {'Spivey':>10} {'B&W':>10} {'Diff':>8}")
|
|
234
|
+
print("-" * 38)
|
|
235
|
+
for T_C in [25, 50, 100, 150, 200, 250]:
|
|
236
|
+
T_K = T_C + 273.15
|
|
237
|
+
rho_sp = rho_brine_spivey(T_K, 30.0, S_10)
|
|
238
|
+
rho_bw = rho_brine_bw(T_K, 30.0, S_10)
|
|
239
|
+
print(f"{T_C:6} {rho_sp:10.4f} {rho_bw:10.4f} {rho_sp - rho_bw:+8.4f}")
|
|
240
|
+
|
|
241
|
+
# Molality conversion test
|
|
242
|
+
print(f"\n\nMolality to weight fraction:")
|
|
243
|
+
for m in [0.5, 1.0, 2.0, 4.0]:
|
|
244
|
+
S = salinity_from_molality(m)
|
|
245
|
+
print(f" {m:.1f} mol/kg → S = {S:.4f} ({S*100:.2f} wt%)")
|
|
Binary file
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Garcia (2001) density mixing rule for gas-dissolved aqueous solutions.
|
|
3
|
+
|
|
4
|
+
Single gas (Garcia Eq. 18):
|
|
5
|
+
ρ = (1 + x2·M2/(M1·x1)) / (x2·V_phi/(M1·x1) + 1/ρ1)
|
|
6
|
+
|
|
7
|
+
Mixed gas (mole-fraction-weighted):
|
|
8
|
+
V_phi_eff = Σ yi·V_phi_i
|
|
9
|
+
M2_eff = Σ yi·M2_i
|
|
10
|
+
where yi = mole fraction of gas i among dissolved gases (Σyi = 1)
|
|
11
|
+
Then apply single-gas formula with V_phi_eff, M2_eff.
|
|
12
|
+
|
|
13
|
+
Brine support:
|
|
14
|
+
For saline solutions, ρ1 is replaced with ρ_brine from Batzle & Wang (1992).
|
|
15
|
+
V_phi values remain from the Plyasunov model (Garcia found salinity effects
|
|
16
|
+
on V_phi are weak and within experimental uncertainty).
|
|
17
|
+
|
|
18
|
+
Units:
|
|
19
|
+
T in K, P in MPa, S in weight fraction NaCl
|
|
20
|
+
x2 = total dissolved gas mole fraction in liquid phase
|
|
21
|
+
ρ in kg/m³, V_phi in cm³/mol, M in g/mol
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import numpy as np
|
|
25
|
+
from water_properties import rho_w, MW_WATER
|
|
26
|
+
from plyasunov_model import V_phi, gas_mw
|
|
27
|
+
from brine_properties import rho_brine
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def density_single_gas(gas, x2, T, P, rho1=None, S=0.0):
|
|
31
|
+
"""
|
|
32
|
+
Solution density with a single dissolved gas using Garcia Eq. 18.
|
|
33
|
+
|
|
34
|
+
Parameters:
|
|
35
|
+
gas: gas name string ('CO2', 'CH4', etc.)
|
|
36
|
+
x2: mole fraction of dissolved gas in liquid phase
|
|
37
|
+
T: temperature in K
|
|
38
|
+
P: pressure in MPa
|
|
39
|
+
rho1: solvent density in kg/m³ (computed from S if None)
|
|
40
|
+
S: salinity as weight fraction NaCl (default 0 = pure water)
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
solution density in kg/m³
|
|
44
|
+
"""
|
|
45
|
+
if rho1 is None:
|
|
46
|
+
rho1 = rho_brine(T, P, S) if S > 0 else rho_w(T, P)
|
|
47
|
+
|
|
48
|
+
if x2 <= 0:
|
|
49
|
+
return float(rho1)
|
|
50
|
+
|
|
51
|
+
x1 = 1.0 - x2
|
|
52
|
+
M1 = MW_WATER # g/mol
|
|
53
|
+
M2 = gas_mw(gas) # g/mol
|
|
54
|
+
vphi = V_phi(gas, T, P) # cm³/mol
|
|
55
|
+
|
|
56
|
+
# Garcia Eq. 18: ρ = (1 + x2·M2/(M1·x1)) / (x2·V_phi/(M1·x1) + 1/ρ1)
|
|
57
|
+
# V_phi is in cm³/mol, M1 in g/mol, ρ1 in kg/m³
|
|
58
|
+
# Need consistent units: convert V_phi to m³/mol? or ρ1 to g/cm³?
|
|
59
|
+
#
|
|
60
|
+
# Work in g/cm³ for clarity:
|
|
61
|
+
# ρ1_gcc = ρ1 / 1000 [g/cm³]
|
|
62
|
+
# Then: ρ [g/cm³] = (1 + x2·M2/(M1·x1)) / (x2·V_phi/(M1·x1) + 1/ρ1_gcc)
|
|
63
|
+
# Convert result back to kg/m³ by multiplying by 1000.
|
|
64
|
+
|
|
65
|
+
rho1_gcc = rho1 / 1000.0 # kg/m³ → g/cm³
|
|
66
|
+
|
|
67
|
+
numerator = 1.0 + x2 * M2 / (M1 * x1)
|
|
68
|
+
denominator = x2 * vphi / (M1 * x1) + 1.0 / rho1_gcc
|
|
69
|
+
|
|
70
|
+
rho_gcc = numerator / denominator # g/cm³
|
|
71
|
+
return rho_gcc * 1000.0 # kg/m³
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def density_mixed_gas(gas_dict, T, P, rho1=None, S=0.0):
|
|
75
|
+
"""
|
|
76
|
+
Solution density with multiple dissolved gases.
|
|
77
|
+
|
|
78
|
+
Uses mole-fraction-weighted effective V_phi and MW, then applies
|
|
79
|
+
Garcia Eq. 18 with the effective values.
|
|
80
|
+
|
|
81
|
+
Parameters:
|
|
82
|
+
gas_dict: dict of {gas_name: x2_i} where x2_i is the mole fraction
|
|
83
|
+
of that gas in the liquid phase. Sum of all x2_i = total x2.
|
|
84
|
+
T: temperature in K
|
|
85
|
+
P: pressure in MPa
|
|
86
|
+
rho1: solvent density in kg/m³ (computed from S if None)
|
|
87
|
+
S: salinity as weight fraction NaCl (default 0 = pure water)
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
solution density in kg/m³
|
|
91
|
+
"""
|
|
92
|
+
if rho1 is None:
|
|
93
|
+
rho1 = rho_brine(T, P, S) if S > 0 else rho_w(T, P)
|
|
94
|
+
|
|
95
|
+
x2_total = sum(gas_dict.values())
|
|
96
|
+
if x2_total <= 0:
|
|
97
|
+
return float(rho1)
|
|
98
|
+
|
|
99
|
+
# Compute mole-fraction-weighted effective properties
|
|
100
|
+
# yi = x2_i / x2_total (fraction of gas i among all dissolved gases)
|
|
101
|
+
vphi_eff = 0.0
|
|
102
|
+
mw_eff = 0.0
|
|
103
|
+
for gas, x2_i in gas_dict.items():
|
|
104
|
+
yi = x2_i / x2_total
|
|
105
|
+
vphi_eff += yi * V_phi(gas, T, P)
|
|
106
|
+
mw_eff += yi * gas_mw(gas)
|
|
107
|
+
|
|
108
|
+
# Apply Garcia Eq. 18 with effective values
|
|
109
|
+
x1 = 1.0 - x2_total
|
|
110
|
+
M1 = MW_WATER
|
|
111
|
+
rho1_gcc = rho1 / 1000.0
|
|
112
|
+
|
|
113
|
+
numerator = 1.0 + x2_total * mw_eff / (M1 * x1)
|
|
114
|
+
denominator = x2_total * vphi_eff / (M1 * x1) + 1.0 / rho1_gcc
|
|
115
|
+
|
|
116
|
+
rho_gcc = numerator / denominator
|
|
117
|
+
return rho_gcc * 1000.0
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# ============================================================================
|
|
121
|
+
# VISCOSITY CORRECTION
|
|
122
|
+
# ============================================================================
|
|
123
|
+
#
|
|
124
|
+
# Experimentally-calibrated viscosity corrections for dissolved gases.
|
|
125
|
+
# Each gas uses the best available experimental source:
|
|
126
|
+
#
|
|
127
|
+
# CO2: Islam & Carlson (2012), Energy Fuels 26(8), 5330-5336
|
|
128
|
+
# mu = mu_brine * (1 + 4.65 * x_CO2^1.0134)
|
|
129
|
+
#
|
|
130
|
+
# CH4: Ostermann, Bloori & Dehghani (1985), SPE 14211
|
|
131
|
+
# Confirmed by Ostermann et al. (1986), SPE 15081
|
|
132
|
+
# mu_sat/mu_free = 1.109 - 5.98e-4*T + 1.0933e-6*T^2 (T in degF)
|
|
133
|
+
# NOTE: SPE 14211 prints 1.0933e-5 (TYPO); verified 1.0933e-6 from
|
|
134
|
+
# stated plateau values (1.060 at 100F, 1.044 at 150F, 1.028 at 250F).
|
|
135
|
+
# Plateau above ~2000 psi; T-dependent, not mole-fraction-dependent.
|
|
136
|
+
#
|
|
137
|
+
# H2S: Murphy & Gaines (1974), J. Chem. Eng. Data 19(4), 359-362
|
|
138
|
+
# mu = mu_brine * (1 + 1.5 * x_H2S^1.0134)
|
|
139
|
+
# Calibrated from 3-6% increase near saturation at 28-30 degC.
|
|
140
|
+
# x_H2S at saturation ~ 0.027-0.033 (from S&W VLE at Murphy & Gaines
|
|
141
|
+
# conditions). Implied 'a' ranges from 1.0 to 2.1 across the 5 data
|
|
142
|
+
# points (large scatter; authors note the effect is "only slightly more
|
|
143
|
+
# than experimental error"). Central estimate a=1.5.
|
|
144
|
+
# Like CH4, effect is strongly T-dependent (nearly zero at 35 degC),
|
|
145
|
+
# but only 5 data points spanning 28-35 degC — insufficient for a
|
|
146
|
+
# T-dependent correlation. The constant a=1.5 is conservative at
|
|
147
|
+
# higher T and approximate at lower T.
|
|
148
|
+
#
|
|
149
|
+
# C2H6: NO EFFECT — Ostermann et al. (1986) SPE 15081
|
|
150
|
+
# N2: NO EFFECT — Murphy & Gaines (1974) control experiment
|
|
151
|
+
# H2, C3H8, nC4H10: No data — no correction applied (conservative)
|
|
152
|
+
#
|
|
153
|
+
# NOTE: A previous density-based scaling approach (a_i = a_CO2 * drho%_i/drho%_CO2)
|
|
154
|
+
# was found to be fundamentally wrong. It predicted large viscosity DECREASES for
|
|
155
|
+
# CH4 (a=-11.0) and H2S (a=-0.9), but experiments show CH4 INCREASES viscosity
|
|
156
|
+
# by up to 6% and H2S INCREASES by 3-6%. The physical mechanism is hydrophobic
|
|
157
|
+
# hydration — dissolved gas molecules organize water into cage-like structures,
|
|
158
|
+
# increasing structural order and viscosity. This is unrelated to density effects.
|
|
159
|
+
|
|
160
|
+
_IC_A_CO2 = 4.65 # Islam-Carlson coefficient for CO2
|
|
161
|
+
_IC_A_H2S = 1.50 # Calibrated from Murphy & Gaines (1974), central estimate
|
|
162
|
+
_IC_B = 1.0134 # Islam-Carlson exponent (nearly linear)
|
|
163
|
+
|
|
164
|
+
# Ostermann (1985) Eq. 10 / (1986) Eq. 7 coefficients for CH4
|
|
165
|
+
# mu_sat/mu_free = c0 + c1*T + c2*T^2 where T in degF
|
|
166
|
+
# NOTE: SPE 14211 prints the quadratic coefficient as 1.0933e-5, but this
|
|
167
|
+
# is a TYPOGRAPHICAL ERROR. Back-calculation from the stated plateau values
|
|
168
|
+
# (1.060 at 100F, 1.044 at 150F, 1.028 at 250F) proves the correct
|
|
169
|
+
# exponent is 10^-6. SPE 15081 prints the linear term as -5.93e-4 (vs
|
|
170
|
+
# -5.98e-4 in SPE 14211); this minor difference does not affect results
|
|
171
|
+
# at the stated precision.
|
|
172
|
+
_OST_C0 = 1.109
|
|
173
|
+
_OST_C1 = -5.98e-4
|
|
174
|
+
_OST_C2 = 1.0933e-6
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _ostermann_ch4_plateau(degf):
|
|
178
|
+
"""
|
|
179
|
+
CH4-saturated water viscosity ratio at plateau (P > ~2000 psi).
|
|
180
|
+
|
|
181
|
+
Ostermann et al. (1985) SPE 14211, Eq. 10:
|
|
182
|
+
mu_sat/mu_free = 1.109 - 5.98e-4*T + 1.0933e-5*T^2
|
|
183
|
+
|
|
184
|
+
Validated values: 1.060 at 100 degF, 1.044 at 150 degF, 1.028 at 250 degF.
|
|
185
|
+
Effect diminishes with temperature but remains positive (>1.0) across
|
|
186
|
+
the full reservoir temperature range.
|
|
187
|
+
|
|
188
|
+
Parameters:
|
|
189
|
+
degf: temperature in degrees Fahrenheit
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
viscosity ratio mu_sat/mu_free (always >= 1.0)
|
|
193
|
+
"""
|
|
194
|
+
ratio = _OST_C0 + _OST_C1 * degf + _OST_C2 * degf ** 2
|
|
195
|
+
return max(ratio, 1.0)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def viscosity_correction_single(gas, x2, degf=None):
|
|
199
|
+
"""
|
|
200
|
+
Viscosity multiplier for a single dissolved gas.
|
|
201
|
+
|
|
202
|
+
Uses experimentally-calibrated corrections:
|
|
203
|
+
- CO2: Islam-Carlson (2012) power-law
|
|
204
|
+
- CH4: Ostermann (1985) T-dependent plateau (requires degf)
|
|
205
|
+
- H2S: Calibrated from Murphy & Gaines (1974)
|
|
206
|
+
- All others: no correction (1.0)
|
|
207
|
+
|
|
208
|
+
mu_corrected = mu_brine * viscosity_correction_single(gas, x2, degf)
|
|
209
|
+
|
|
210
|
+
Parameters:
|
|
211
|
+
gas: gas name string ('CO2', 'CH4', 'H2S', etc.)
|
|
212
|
+
x2: mole fraction of dissolved gas in liquid phase
|
|
213
|
+
degf: temperature in degrees Fahrenheit (required for CH4)
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
viscosity multiplier (>= 1.0 for all calibrated gases)
|
|
217
|
+
"""
|
|
218
|
+
if x2 <= 0:
|
|
219
|
+
return 1.0
|
|
220
|
+
|
|
221
|
+
gas = gas.upper()
|
|
222
|
+
|
|
223
|
+
if gas == 'CO2':
|
|
224
|
+
return 1.0 + _IC_A_CO2 * x2 ** _IC_B
|
|
225
|
+
|
|
226
|
+
if gas == 'H2S':
|
|
227
|
+
return 1.0 + _IC_A_H2S * x2 ** _IC_B
|
|
228
|
+
|
|
229
|
+
if gas == 'CH4':
|
|
230
|
+
if degf is None:
|
|
231
|
+
raise ValueError("degf (temperature) is required for CH4 viscosity correction")
|
|
232
|
+
# Ostermann plateau value — the full effect at saturation.
|
|
233
|
+
# The plateau is reached above ~2000 psi CH4 partial pressure.
|
|
234
|
+
# For typical reservoir conditions (P > 2000 psi), this is appropriate.
|
|
235
|
+
# For undersaturated conditions, the effect could be scaled by
|
|
236
|
+
# Rsw/Rsw_sat, but Ostermann's data shows the plateau is reached
|
|
237
|
+
# at relatively low pressures, so the full correction is used here.
|
|
238
|
+
return _ostermann_ch4_plateau(degf)
|
|
239
|
+
|
|
240
|
+
# C2H6, N2: experimentally confirmed no effect (Ostermann 1986, Murphy & Gaines 1974)
|
|
241
|
+
# H2, C3H8, NC4H10: no data available — conservative (no correction)
|
|
242
|
+
return 1.0
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def viscosity_correction_mixed(gas_dict, degf=None):
|
|
246
|
+
"""
|
|
247
|
+
Viscosity multiplier for multiple dissolved gases.
|
|
248
|
+
|
|
249
|
+
Applies multiplicative corrections:
|
|
250
|
+
mu = mu_brine * Product_i(correction_i)
|
|
251
|
+
|
|
252
|
+
Parameters:
|
|
253
|
+
gas_dict: dict of {gas_name: x2_i}
|
|
254
|
+
degf: temperature in degrees Fahrenheit (required if CH4 present)
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
combined viscosity multiplier
|
|
258
|
+
"""
|
|
259
|
+
factor = 1.0
|
|
260
|
+
for gas, x2 in gas_dict.items():
|
|
261
|
+
if x2 > 0:
|
|
262
|
+
factor *= viscosity_correction_single(gas, x2, degf=degf)
|
|
263
|
+
return factor
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def density_change_pct(gas_or_dict, x2_or_none, T, P, S=0.0):
|
|
267
|
+
"""
|
|
268
|
+
Percentage density change relative to solvent (water or brine).
|
|
269
|
+
|
|
270
|
+
Parameters:
|
|
271
|
+
gas_or_dict: either a gas name string, or a dict {gas: x2_i}
|
|
272
|
+
x2_or_none: mole fraction if gas_or_dict is a string, None if dict
|
|
273
|
+
T, P: temperature (K) and pressure (MPa)
|
|
274
|
+
S: salinity as weight fraction NaCl (default 0 = pure water)
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
(rho_solution, rho_solvent, percent_change)
|
|
278
|
+
"""
|
|
279
|
+
rho1 = rho_brine(T, P, S) if S > 0 else rho_w(T, P)
|
|
280
|
+
if isinstance(gas_or_dict, dict):
|
|
281
|
+
rho_sol = density_mixed_gas(gas_dict=gas_or_dict, T=T, P=P, rho1=rho1)
|
|
282
|
+
else:
|
|
283
|
+
rho_sol = density_single_gas(gas_or_dict, x2_or_none, T, P, rho1=rho1)
|
|
284
|
+
pct = 100.0 * (rho_sol - rho1) / rho1
|
|
285
|
+
return rho_sol, rho1, pct
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
# ============================================================================
|
|
289
|
+
# MAIN - VALIDATION
|
|
290
|
+
# ============================================================================
|
|
291
|
+
|
|
292
|
+
if __name__ == "__main__":
|
|
293
|
+
# Validate against Garcia (2001) CO2 results
|
|
294
|
+
# Garcia reports ~2.5% density increase at x_CO2 = 0.05
|
|
295
|
+
print("=== Single Gas Density Tests ===\n")
|
|
296
|
+
|
|
297
|
+
T_test = 298.15 # 25°C
|
|
298
|
+
P_test = 10.0 # 10 MPa (representative reservoir P)
|
|
299
|
+
|
|
300
|
+
rho1 = rho_w(T_test, P_test)
|
|
301
|
+
print(f"Pure water at T={T_test}K, P={P_test}MPa: ρ = {rho1:.4f} kg/m³")
|
|
302
|
+
print()
|
|
303
|
+
|
|
304
|
+
gases = ['CO2', 'CH4', 'H2S', 'N2', 'H2', 'C2H6', 'C3H8', 'NC4H10']
|
|
305
|
+
x2_test = 0.02 # 2 mol% dissolved gas
|
|
306
|
+
|
|
307
|
+
print(f"Dissolved gas mole fraction x2 = {x2_test}")
|
|
308
|
+
print(f"{'Gas':<8} {'MW':>6} {'V_phi':>8} {'ρ_sol':>10} {'Δρ':>8} {'%change':>8}")
|
|
309
|
+
print("-" * 55)
|
|
310
|
+
|
|
311
|
+
for gas in gases:
|
|
312
|
+
vphi = V_phi(gas, T_test, P_test)
|
|
313
|
+
rho_sol = density_single_gas(gas, x2_test, T_test, P_test, rho1=rho1)
|
|
314
|
+
delta = rho_sol - rho1
|
|
315
|
+
pct = 100 * delta / rho1
|
|
316
|
+
mw = gas_mw(gas)
|
|
317
|
+
print(f"{gas:<8} {mw:6.2f} {vphi:8.2f} {rho_sol:10.4f} {delta:+8.4f} {pct:+7.3f}%")
|
|
318
|
+
|
|
319
|
+
# Test: CO2 at x2=0.05 should give ~2.5% increase (Garcia Fig. 3)
|
|
320
|
+
print(f"\n--- CO2 at x2=0.05 (Garcia reports ~2.5% increase) ---")
|
|
321
|
+
rho_co2, _, pct_co2 = density_change_pct('CO2', 0.05, T_test, P_test)
|
|
322
|
+
print(f" ρ = {rho_co2:.4f} kg/m³, Δρ/ρ = {pct_co2:+.3f}%")
|
|
323
|
+
|
|
324
|
+
# Mixed gas test
|
|
325
|
+
print("\n=== Mixed Gas Test ===\n")
|
|
326
|
+
gas_mix = {'CO2': 0.01, 'CH4': 0.005, 'H2S': 0.003}
|
|
327
|
+
rho_mix = density_mixed_gas(gas_mix, T_test, P_test, rho1=rho1)
|
|
328
|
+
delta_mix = rho_mix - rho1
|
|
329
|
+
pct_mix = 100 * delta_mix / rho1
|
|
330
|
+
total_x2 = sum(gas_mix.values())
|
|
331
|
+
print(f"Gas mix: {gas_mix}")
|
|
332
|
+
print(f"Total x2 = {total_x2}")
|
|
333
|
+
print(f"ρ_mix = {rho_mix:.4f} kg/m³, Δρ = {delta_mix:+.4f}, %change = {pct_mix:+.3f}%")
|
|
334
|
+
|
|
335
|
+
# Brine tests
|
|
336
|
+
print("\n=== Brine + Gas Tests ===\n")
|
|
337
|
+
S_test = 0.10 # 10 wt% NaCl
|
|
338
|
+
rho_brine_val = rho_brine(T_test, P_test, S_test)
|
|
339
|
+
print(f"Brine at T={T_test}K, P={P_test}MPa, S={S_test*100}wt%: ρ = {rho_brine_val:.4f} kg/m³")
|
|
340
|
+
print(f"\nDensity effect of dissolved gas in brine vs pure water (x2=0.02):")
|
|
341
|
+
print(f"{'Gas':<8} {'ρ(water)':>10} {'Δρ%(w)':>8} {'ρ(brine)':>10} {'Δρ%(b)':>8}")
|
|
342
|
+
print("-" * 50)
|
|
343
|
+
for gas in ['CO2', 'CH4', 'H2S', 'N2']:
|
|
344
|
+
rho_w_sol, rho_w_base, pct_w = density_change_pct(gas, x2_test, T_test, P_test, S=0.0)
|
|
345
|
+
rho_b_sol, rho_b_base, pct_b = density_change_pct(gas, x2_test, T_test, P_test, S=S_test)
|
|
346
|
+
print(f"{gas:<8} {rho_w_sol:10.4f} {pct_w:+7.3f}% {rho_b_sol:10.4f} {pct_b:+7.3f}%")
|
|
347
|
+
|
|
348
|
+
# Temperature sweep for CO2
|
|
349
|
+
print("\n=== CO2 Density vs Temperature (x2=0.02, P=30 MPa) ===\n")
|
|
350
|
+
print(f"{'T(°C)':<8} {'ρ_water':>10} {'ρ_CO2aq':>10} {'Δρ%':>8}")
|
|
351
|
+
print("-" * 40)
|
|
352
|
+
for T_C in [25, 50, 100, 150, 200, 250]:
|
|
353
|
+
T_K = T_C + 273.15
|
|
354
|
+
rho_w_val = rho_w(T_K, 30.0)
|
|
355
|
+
rho_sol_val = density_single_gas('CO2', 0.02, T_K, 30.0, rho1=rho_w_val)
|
|
356
|
+
pct_val = 100 * (rho_sol_val - rho_w_val) / rho_w_val
|
|
357
|
+
print(f"{T_C:<8} {rho_w_val:10.4f} {rho_sol_val:10.4f} {pct_val:+7.3f}%")
|
|
358
|
+
|
|
359
|
+
# ================================================================
|
|
360
|
+
# VISCOSITY VALIDATION
|
|
361
|
+
# ================================================================
|
|
362
|
+
print("\n=== Viscosity Correction Tests ===\n")
|
|
363
|
+
|
|
364
|
+
# CO2: Islam-Carlson (2012) at x=0.02
|
|
365
|
+
co2_vis = viscosity_correction_single('CO2', 0.02)
|
|
366
|
+
print(f"CO2 at x=0.02: factor = {co2_vis:.6f} (expected ~1.09)")
|
|
367
|
+
|
|
368
|
+
# H2S: Murphy & Gaines calibrated (a=0.8)
|
|
369
|
+
h2s_vis_005 = viscosity_correction_single('H2S', 0.05)
|
|
370
|
+
h2s_vis_020 = viscosity_correction_single('H2S', 0.20)
|
|
371
|
+
print(f"H2S at x=0.05: factor = {h2s_vis_005:.6f} (expect ~+4% increase)")
|
|
372
|
+
print(f"H2S at x=0.20: factor = {h2s_vis_020:.6f} (expect ~+15.5%)")
|
|
373
|
+
|
|
374
|
+
# CH4: Ostermann plateau values
|
|
375
|
+
print("\nCH4 Ostermann plateau vs temperature:")
|
|
376
|
+
print(f" {'T(degF)':<10} {'Plateau':>10} {'Expected':>10}")
|
|
377
|
+
print(f" {'-'*32}")
|
|
378
|
+
for degf, expected in [(100, 1.060), (150, 1.044), (250, 1.028)]:
|
|
379
|
+
plateau = _ostermann_ch4_plateau(degf)
|
|
380
|
+
print(f" {degf:<10} {plateau:10.4f} {expected:10.3f}")
|
|
381
|
+
|
|
382
|
+
# Gases with no effect
|
|
383
|
+
print(f"\nC2H6 at x=0.02: factor = {viscosity_correction_single('C2H6', 0.02):.4f} (expect 1.0)")
|
|
384
|
+
print(f"N2 at x=0.02: factor = {viscosity_correction_single('N2', 0.02):.4f} (expect 1.0)")
|
|
385
|
+
print(f"H2 at x=0.02: factor = {viscosity_correction_single('H2', 0.02):.4f} (expect 1.0)")
|
|
386
|
+
|
|
387
|
+
# Mixed gas test
|
|
388
|
+
gas_mix_v = {'CO2': 0.01, 'CH4': 0.005, 'H2S': 0.003}
|
|
389
|
+
mixed_vis = viscosity_correction_mixed(gas_mix_v, degf=150)
|
|
390
|
+
print(f"\nMixed viscosity ({gas_mix_v}, T=150F): factor = {mixed_vis:.6f}")
|