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.
Files changed (103) hide show
  1. {pyrestoolbox-2.5.0/pyrestoolbox.egg-info → pyrestoolbox-2.5.1}/PKG-INFO +1 -1
  2. pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/__pycache__/brine_properties.cpython-310.pyc/357/200/272Zone.Identifier +0 -0
  3. pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/__pycache__/garcia_mixing.cpython-310.pyc/357/200/272Zone.Identifier +0 -0
  4. pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/__pycache__/iapws_if97.cpython-310.pyc/357/200/272Zone.Identifier +0 -0
  5. pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/__pycache__/plyasunov_model.cpython-310.pyc/357/200/272Zone.Identifier +0 -0
  6. pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/__pycache__/water_properties.cpython-310.pyc/357/200/272Zone.Identifier +0 -0
  7. pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/brine_properties.py +245 -0
  8. pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/brine_properties.py/357/200/272Zone.Identifier +0 -0
  9. pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/garcia_mixing.py +390 -0
  10. pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/garcia_mixing.py/357/200/272Zone.Identifier +0 -0
  11. pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/generate_technical_note.py +433 -0
  12. pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/generate_technical_note.py/357/200/272Zone.Identifier +0 -0
  13. pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/iapws_if97.py +240 -0
  14. pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/iapws_if97.py/357/200/272Zone.Identifier +0 -0
  15. pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/papers/14211-MS.pdf +0 -0
  16. pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/papers/15081-MS.pdf +0 -0
  17. pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/papers/96013-MS.pdf +0 -0
  18. pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/papers/H2S Aqueous Viscosity je60063a015.pdf +0 -0
  19. pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/plyasunov_model.py +404 -0
  20. pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/plyasunov_model.py/357/200/272Zone.Identifier +0 -0
  21. pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/project_summary.txt +165 -0
  22. pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/project_summary.txt/357/200/272Zone.Identifier +0 -0
  23. pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/pyrestoolbox_integration.txt +614 -0
  24. pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/pyrestoolbox_integration.txt/357/200/272Zone.Identifier +0 -0
  25. pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/validation.py +251 -0
  26. pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/validation.py/357/200/272Zone.Identifier +0 -0
  27. pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/water_properties.py +139 -0
  28. pyrestoolbox-2.5.1/pyrestoolbox/Garcia_code/water_properties.py/357/200/272Zone.Identifier +0 -0
  29. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/classes/classes.py +7 -0
  30. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/docs/.ipynb_checkpoints/examples-checkpoint.ipynb +71 -71
  31. pyrestoolbox-2.5.1/pyrestoolbox/docs/.ipynb_checkpoints/nodal_examples-checkpoint.ipynb +825 -0
  32. pyrestoolbox-2.5.1/pyrestoolbox/docs/examples.ipynb +3284 -0
  33. pyrestoolbox-2.5.1/pyrestoolbox/docs/nodal.rst +787 -0
  34. pyrestoolbox-2.5.1/pyrestoolbox/docs/nodal_examples.ipynb +845 -0
  35. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/gas/gas.py +51 -0
  36. pyrestoolbox-2.5.1/pyrestoolbox/nodal/__init__.py +1 -0
  37. pyrestoolbox-2.5.1/pyrestoolbox/nodal/nodal.py +2009 -0
  38. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/oil/oil.py +54 -0
  39. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/tests/run_all_tests.py +1 -0
  40. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/tests/test_doc_examples.py +184 -0
  41. pyrestoolbox-2.5.1/pyrestoolbox/tests/test_nodal.py +636 -0
  42. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1/pyrestoolbox.egg-info}/PKG-INFO +1 -1
  43. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox.egg-info/SOURCES.txt +33 -0
  44. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/setup.cfg +1 -1
  45. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/setup.py +1 -1
  46. pyrestoolbox-2.5.0/pyrestoolbox/docs/examples.ipynb +0 -1458
  47. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/LICENSE +0 -0
  48. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/MANIFEST.in +0 -0
  49. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/README.md +0 -0
  50. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/README.rst +0 -0
  51. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyproject.toml +0 -0
  52. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/__init__.py +0 -0
  53. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/brine/__init__.py +0 -0
  54. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/brine/_lib_salting_library.py +0 -0
  55. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/brine/_lib_vle_engine.py +0 -0
  56. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/brine/brine.py +0 -0
  57. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/classes/__init__.py +0 -0
  58. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/constants/__init__.py +0 -0
  59. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/constants/constants.py +0 -0
  60. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/docs/brine.rst +0 -0
  61. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/docs/changelist.rst +0 -0
  62. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/docs/gas.rst +0 -0
  63. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/docs/img/bot.png +0 -0
  64. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/docs/img/bot_PVTO.png +0 -0
  65. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/docs/img/bot_img.png +0 -0
  66. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/docs/img/dry_gas.png +0 -0
  67. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/docs/img/grid_sat_df.png +0 -0
  68. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/docs/img/influence.png +0 -0
  69. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/docs/img/properties_df.png +0 -0
  70. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/docs/img/sgof.png +0 -0
  71. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/docs/img/swof.png +0 -0
  72. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/docs/layer.rst +0 -0
  73. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/docs/library.rst +0 -0
  74. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/docs/oil.rst +0 -0
  75. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/docs/simtools.rst +0 -0
  76. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/gas/__init__.py +0 -0
  77. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/layer/__init__.py +0 -0
  78. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/layer/layer.py +0 -0
  79. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/library/__init__.py +0 -0
  80. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/library/component_library.xlsx +0 -0
  81. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/library/library.py +0 -0
  82. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/oil/__init__.py +0 -0
  83. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/plyasunov/__init__.py +0 -0
  84. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/plyasunov/iapws_if97.py +0 -0
  85. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/plyasunov/plyasunov_model.py +0 -0
  86. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/plyasunov/water_properties.py +0 -0
  87. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/shared_fns/__init__.py +0 -0
  88. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/shared_fns/shared_fns.py +0 -0
  89. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/simtools/__init__.py +0 -0
  90. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/simtools/simtools.py +0 -0
  91. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/tests/__init__.py +0 -0
  92. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/tests/test_brine.py +0 -0
  93. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/tests/test_gas.py +0 -0
  94. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/tests/test_layer.py +0 -0
  95. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/tests/test_oil.py +0 -0
  96. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/tests/test_simtools.py +0 -0
  97. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/tests/test_unified_brine_design.py +0 -0
  98. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/tests/test_viscosity_scaling.py +0 -0
  99. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/validate/__init__.py +0 -0
  100. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox/validate/validate.py +0 -0
  101. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox.egg-info/dependency_links.txt +0 -0
  102. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox.egg-info/requires.txt +0 -0
  103. {pyrestoolbox-2.5.0 → pyrestoolbox-2.5.1}/pyrestoolbox.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyrestoolbox
3
- Version: 2.5.0
3
+ Version: 2.5.1
4
4
  Summary: pyResToolbox - A collection of Reservoir Engineering Utilities
5
5
  Home-page: https://github.com/mwburgoyne/pyResToolbox
6
6
  Author: Mark W. Burgoyne
@@ -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%)")
@@ -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}")