rock-physics-open 0.3.2__py3-none-any.whl

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 (145) hide show
  1. rock_physics_open/__init__.py +0 -0
  2. rock_physics_open/equinor_utilities/__init__.py +0 -0
  3. rock_physics_open/equinor_utilities/anisotropy.py +211 -0
  4. rock_physics_open/equinor_utilities/classification_functions/__init__.py +17 -0
  5. rock_physics_open/equinor_utilities/classification_functions/class_stats.py +68 -0
  6. rock_physics_open/equinor_utilities/classification_functions/lin_class.py +53 -0
  7. rock_physics_open/equinor_utilities/classification_functions/mahal_class.py +63 -0
  8. rock_physics_open/equinor_utilities/classification_functions/norm_class.py +73 -0
  9. rock_physics_open/equinor_utilities/classification_functions/poly_class.py +45 -0
  10. rock_physics_open/equinor_utilities/classification_functions/post_prob.py +27 -0
  11. rock_physics_open/equinor_utilities/classification_functions/two_step_classification.py +60 -0
  12. rock_physics_open/equinor_utilities/conversions.py +10 -0
  13. rock_physics_open/equinor_utilities/gen_utilities/__init__.py +11 -0
  14. rock_physics_open/equinor_utilities/gen_utilities/dict_to_float.py +38 -0
  15. rock_physics_open/equinor_utilities/gen_utilities/dim_check_vector.py +113 -0
  16. rock_physics_open/equinor_utilities/gen_utilities/filter_input.py +131 -0
  17. rock_physics_open/equinor_utilities/gen_utilities/filter_output.py +88 -0
  18. rock_physics_open/equinor_utilities/machine_learning_utilities/__init__.py +15 -0
  19. rock_physics_open/equinor_utilities/machine_learning_utilities/base_pressure_model.py +170 -0
  20. rock_physics_open/equinor_utilities/machine_learning_utilities/dummy_vars.py +53 -0
  21. rock_physics_open/equinor_utilities/machine_learning_utilities/exponential_model.py +137 -0
  22. rock_physics_open/equinor_utilities/machine_learning_utilities/import_ml_models.py +77 -0
  23. rock_physics_open/equinor_utilities/machine_learning_utilities/polynomial_model.py +132 -0
  24. rock_physics_open/equinor_utilities/machine_learning_utilities/run_regression.py +209 -0
  25. rock_physics_open/equinor_utilities/machine_learning_utilities/sigmoidal_model.py +241 -0
  26. rock_physics_open/equinor_utilities/optimisation_utilities/__init__.py +19 -0
  27. rock_physics_open/equinor_utilities/optimisation_utilities/opt_subst_utilities.py +455 -0
  28. rock_physics_open/equinor_utilities/snapshot_test_utilities/__init__.py +10 -0
  29. rock_physics_open/equinor_utilities/snapshot_test_utilities/compare_snapshots.py +184 -0
  30. rock_physics_open/equinor_utilities/snapshot_test_utilities/snapshots.py +97 -0
  31. rock_physics_open/equinor_utilities/std_functions/__init__.py +43 -0
  32. rock_physics_open/equinor_utilities/std_functions/backus_ave.py +68 -0
  33. rock_physics_open/equinor_utilities/std_functions/dvorkin_nur.py +77 -0
  34. rock_physics_open/equinor_utilities/std_functions/gassmann.py +165 -0
  35. rock_physics_open/equinor_utilities/std_functions/hashin_shtrikman.py +224 -0
  36. rock_physics_open/equinor_utilities/std_functions/hertz_mindlin.py +51 -0
  37. rock_physics_open/equinor_utilities/std_functions/moduli_velocity.py +67 -0
  38. rock_physics_open/equinor_utilities/std_functions/reflection_eq.py +120 -0
  39. rock_physics_open/equinor_utilities/std_functions/rho.py +69 -0
  40. rock_physics_open/equinor_utilities/std_functions/voigt_reuss_hill.py +149 -0
  41. rock_physics_open/equinor_utilities/std_functions/walton.py +45 -0
  42. rock_physics_open/equinor_utilities/std_functions/wood_brie.py +94 -0
  43. rock_physics_open/equinor_utilities/various_utilities/Equinor_logo.gif +0 -0
  44. rock_physics_open/equinor_utilities/various_utilities/Equinor_logo.ico +0 -0
  45. rock_physics_open/equinor_utilities/various_utilities/__init__.py +24 -0
  46. rock_physics_open/equinor_utilities/various_utilities/display_result_statistics.py +90 -0
  47. rock_physics_open/equinor_utilities/various_utilities/gassmann_dry_mod.py +56 -0
  48. rock_physics_open/equinor_utilities/various_utilities/gassmann_mod.py +56 -0
  49. rock_physics_open/equinor_utilities/various_utilities/gassmann_sub_mod.py +64 -0
  50. rock_physics_open/equinor_utilities/various_utilities/hs_average.py +59 -0
  51. rock_physics_open/equinor_utilities/various_utilities/pressure.py +96 -0
  52. rock_physics_open/equinor_utilities/various_utilities/reflectivity.py +101 -0
  53. rock_physics_open/equinor_utilities/various_utilities/timeshift.py +104 -0
  54. rock_physics_open/equinor_utilities/various_utilities/vp_vs_rho_set_statistics.py +170 -0
  55. rock_physics_open/equinor_utilities/various_utilities/vrh_3_min.py +83 -0
  56. rock_physics_open/fluid_models/__init__.py +9 -0
  57. rock_physics_open/fluid_models/brine_model/__init__.py +5 -0
  58. rock_physics_open/fluid_models/brine_model/brine_properties.py +178 -0
  59. rock_physics_open/fluid_models/gas_model/__init__.py +5 -0
  60. rock_physics_open/fluid_models/gas_model/gas_properties.py +319 -0
  61. rock_physics_open/fluid_models/oil_model/__init__.py +5 -0
  62. rock_physics_open/fluid_models/oil_model/dead_oil_density.py +65 -0
  63. rock_physics_open/fluid_models/oil_model/dead_oil_velocity.py +30 -0
  64. rock_physics_open/fluid_models/oil_model/live_oil_density.py +82 -0
  65. rock_physics_open/fluid_models/oil_model/live_oil_velocity.py +24 -0
  66. rock_physics_open/fluid_models/oil_model/oil_bubble_point.py +69 -0
  67. rock_physics_open/fluid_models/oil_model/oil_properties.py +146 -0
  68. rock_physics_open/sandstone_models/__init__.py +59 -0
  69. rock_physics_open/sandstone_models/cemented_shalysand_sandyshale_models.py +304 -0
  70. rock_physics_open/sandstone_models/constant_cement_models.py +204 -0
  71. rock_physics_open/sandstone_models/constant_cement_optimisation.py +125 -0
  72. rock_physics_open/sandstone_models/contact_cement_model.py +138 -0
  73. rock_physics_open/sandstone_models/curvefit_sandstone_models.py +143 -0
  74. rock_physics_open/sandstone_models/friable_models.py +177 -0
  75. rock_physics_open/sandstone_models/friable_optimisation.py +115 -0
  76. rock_physics_open/sandstone_models/friable_shalysand_sandyshale_models.py +235 -0
  77. rock_physics_open/sandstone_models/patchy_cement_fluid_substitution_model.py +477 -0
  78. rock_physics_open/sandstone_models/patchy_cement_model.py +384 -0
  79. rock_physics_open/sandstone_models/patchy_cement_optimisation.py +254 -0
  80. rock_physics_open/sandstone_models/unresolved_cemented_sandshale_models.py +134 -0
  81. rock_physics_open/sandstone_models/unresolved_friable_sandshale_models.py +126 -0
  82. rock_physics_open/shale_models/__init__.py +19 -0
  83. rock_physics_open/shale_models/dem.py +174 -0
  84. rock_physics_open/shale_models/dem_dual_por.py +61 -0
  85. rock_physics_open/shale_models/kus_tok.py +59 -0
  86. rock_physics_open/shale_models/multi_sca.py +133 -0
  87. rock_physics_open/shale_models/pq.py +102 -0
  88. rock_physics_open/shale_models/sca.py +90 -0
  89. rock_physics_open/shale_models/shale4_mineral.py +147 -0
  90. rock_physics_open/shale_models/shale4_mineral_dem_overlay.py +92 -0
  91. rock_physics_open/span_wagner/__init__.py +5 -0
  92. rock_physics_open/span_wagner/co2_properties.py +444 -0
  93. rock_physics_open/span_wagner/coefficients.py +165 -0
  94. rock_physics_open/span_wagner/equations.py +104 -0
  95. rock_physics_open/span_wagner/tables/__init__.py +0 -0
  96. rock_physics_open/span_wagner/tables/carbon_dioxide_density.npz +0 -0
  97. rock_physics_open/span_wagner/tables/lookup_table.py +33 -0
  98. rock_physics_open/t_matrix_models/Equinor_logo.ico +0 -0
  99. rock_physics_open/t_matrix_models/__init__.py +35 -0
  100. rock_physics_open/t_matrix_models/carbonate_pressure_substitution.py +124 -0
  101. rock_physics_open/t_matrix_models/curvefit_t_matrix_exp.py +123 -0
  102. rock_physics_open/t_matrix_models/curvefit_t_matrix_min.py +86 -0
  103. rock_physics_open/t_matrix_models/parse_t_matrix_inputs.py +297 -0
  104. rock_physics_open/t_matrix_models/run_t_matrix.py +243 -0
  105. rock_physics_open/t_matrix_models/t_matrix_C.py +210 -0
  106. rock_physics_open/t_matrix_models/t_matrix_opt_fluid_sub_exp.py +137 -0
  107. rock_physics_open/t_matrix_models/t_matrix_opt_fluid_sub_petec.py +167 -0
  108. rock_physics_open/t_matrix_models/t_matrix_opt_forward_model_exp.py +76 -0
  109. rock_physics_open/t_matrix_models/t_matrix_opt_forward_model_min.py +89 -0
  110. rock_physics_open/t_matrix_models/t_matrix_parameter_optimisation_exp.py +176 -0
  111. rock_physics_open/t_matrix_models/t_matrix_parameter_optimisation_min.py +162 -0
  112. rock_physics_open/t_matrix_models/t_matrix_vector/__init__.py +12 -0
  113. rock_physics_open/t_matrix_models/t_matrix_vector/array_functions.py +75 -0
  114. rock_physics_open/t_matrix_models/t_matrix_vector/calc_c_eff.py +163 -0
  115. rock_physics_open/t_matrix_models/t_matrix_vector/calc_isolated.py +95 -0
  116. rock_physics_open/t_matrix_models/t_matrix_vector/calc_kd.py +40 -0
  117. rock_physics_open/t_matrix_models/t_matrix_vector/calc_kd_eff.py +116 -0
  118. rock_physics_open/t_matrix_models/t_matrix_vector/calc_kd_uuv.py +18 -0
  119. rock_physics_open/t_matrix_models/t_matrix_vector/calc_pressure.py +140 -0
  120. rock_physics_open/t_matrix_models/t_matrix_vector/calc_t.py +71 -0
  121. rock_physics_open/t_matrix_models/t_matrix_vector/calc_td.py +42 -0
  122. rock_physics_open/t_matrix_models/t_matrix_vector/calc_theta.py +43 -0
  123. rock_physics_open/t_matrix_models/t_matrix_vector/calc_x.py +33 -0
  124. rock_physics_open/t_matrix_models/t_matrix_vector/calc_z.py +50 -0
  125. rock_physics_open/t_matrix_models/t_matrix_vector/check_and_tile.py +43 -0
  126. rock_physics_open/t_matrix_models/t_matrix_vector/g_tensor.py +140 -0
  127. rock_physics_open/t_matrix_models/t_matrix_vector/iso_av.py +60 -0
  128. rock_physics_open/t_matrix_models/t_matrix_vector/iso_ave_all.py +55 -0
  129. rock_physics_open/t_matrix_models/t_matrix_vector/pressure_input.py +44 -0
  130. rock_physics_open/t_matrix_models/t_matrix_vector/t_matrix_vec.py +278 -0
  131. rock_physics_open/t_matrix_models/t_matrix_vector/velocity_vti_angles.py +81 -0
  132. rock_physics_open/t_matrix_models/tmatrix_python.dll +0 -0
  133. rock_physics_open/t_matrix_models/tmatrix_python.so +0 -0
  134. rock_physics_open/ternary_plots/__init__.py +3 -0
  135. rock_physics_open/ternary_plots/gen_ternary_plot.py +73 -0
  136. rock_physics_open/ternary_plots/shale_prop_ternary.py +337 -0
  137. rock_physics_open/ternary_plots/ternary_patches.py +277 -0
  138. rock_physics_open/ternary_plots/ternary_plot_utilities.py +197 -0
  139. rock_physics_open/ternary_plots/unconventionals_ternary.py +75 -0
  140. rock_physics_open/version.py +34 -0
  141. rock_physics_open-0.3.2.dist-info/METADATA +90 -0
  142. rock_physics_open-0.3.2.dist-info/RECORD +145 -0
  143. rock_physics_open-0.3.2.dist-info/WHEEL +5 -0
  144. rock_physics_open-0.3.2.dist-info/licenses/LICENSE +165 -0
  145. rock_physics_open-0.3.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,444 @@
1
+ import importlib
2
+
3
+ import numpy as np
4
+ import scipy.optimize
5
+ from scipy.interpolate import RegularGridInterpolator
6
+
7
+ from rock_physics_open.equinor_utilities.conversions import celsius_to_kelvin
8
+ from rock_physics_open.span_wagner import equations
9
+ from rock_physics_open.span_wagner.coefficients import (
10
+ a0,
11
+ theta0,
12
+ )
13
+ from rock_physics_open.span_wagner.tables.lookup_table import (
14
+ load_lookup_table_interpolator,
15
+ )
16
+
17
+ # Constants
18
+ CO2_GAS_CONSTANT = 0.1889241 * 1e3 # J / kg K
19
+ CO2_CRITICAL_TEMPERATURE = 304.1282 # K
20
+ CO2_CRITICAL_DENSITY = 467.6 # kg / m^3
21
+ CO2_CRITICAL_PRESSURE = 7.3773 # MPa
22
+ CO2_TRIPLE_TEMPERATURE = 216.592 # K
23
+ CO2_TRIPLE_PRESSURE = 0.51795 # MPa
24
+
25
+
26
+ def co2_properties(temp, pres):
27
+ """
28
+ CO2 properties are estimated according to Span & Wagner's equation of state model.
29
+ References
30
+ ----------
31
+ R. Span and W. Wagner: A new equation of state for carbon dioxide covering the
32
+ fluid region from the triple point temperature to 1100K at pressures up to
33
+ 800 MPa.
34
+ J. Phys. Chem. Ref. Data, Vol. 25, No. 6, 1996, pp 1509 - 1596
35
+
36
+ Parameters
37
+ ----------
38
+ temp: np.ndarray
39
+ Temperature [°C]
40
+ pres: np.ndarray
41
+ Pressure [Pa]
42
+
43
+ Returns
44
+ -------
45
+ tuple
46
+ vel_co2, den_co2, k_co2 : (np.ndarray, np.ndarray, np.ndarray).
47
+ vel_co2: velocity [m/s], den_co2: density [kg/m^3], k_co2: bulk modulus [Pa].
48
+ """
49
+ abs_temp = celsius_to_kelvin(temp)
50
+ pres_mpa = pres * 1.0e-6
51
+ den_co2 = carbon_dioxide_density(abs_temp, pres_mpa)
52
+ k_co2 = carbon_dioxide_bulk_modulus(abs_temp, den_co2)
53
+ vel_co2 = (k_co2 / den_co2) ** 0.5
54
+
55
+ return vel_co2, den_co2, k_co2
56
+
57
+
58
+ def co2_helmholtz_energy(delta, tau, dd, dt):
59
+ """
60
+ Helmholtz energy as defined by equation 6.1 in Span & Wagner [2]
61
+
62
+ :param delta: Reduced density, unit-less. That is, density / CO2_CRITICAL_DENSITY.
63
+ :param tau: Inverse reduced temperature, unit-less. That is, CO2_CRITICAL_TEMPERATURE / (absolute) temperature
64
+ :param dd: Degree of derivation wrt. delta. Integer between 0 and 2.
65
+ :param dt: Degree of derivation wrt. tau. Integer between 0 and 2, as long as (dt + dd < 3)
66
+ """
67
+ return ideal_gas_helmholtz_energy(
68
+ delta, tau, dd, dt
69
+ ) + co2_residual_helmholtz_energy(delta, tau, dd, dt)
70
+
71
+
72
+ def ideal_gas_helmholtz_energy(delta, tau, dd, dt):
73
+ """
74
+ Helmholtz energy from ideal gas behavior as defined by equation 2.3 in Span & Wagner [2]. See function
75
+ co2_helmholtz_energy for argument documentation.
76
+ """
77
+ # Adjust array shapes
78
+ tau = np.asarray(tau)
79
+ delta = np.asarray(delta)
80
+ return_scalar = tau.ndim == 0
81
+ tau2 = tau.reshape(-1, 1) # Needed for array-based sums
82
+
83
+ if dt == dd == 0:
84
+ _sum = np.sum(a0[0, 3:] * np.log(1 - np.exp(-theta0 * tau2)), axis=-1)
85
+ result = (
86
+ np.log(delta) + a0[0, 0] + a0[0, 1] * tau + a0[0, 2] * np.log(tau) + _sum
87
+ )
88
+ elif dt == 1 and dd == 0:
89
+ _sum = np.sum(
90
+ a0[0, 3:] * theta0 * ((1 - np.exp(-theta0 * tau2)) ** -1 - 1), axis=-1
91
+ )
92
+ result = a0[0, 1] + a0[0, 2] / tau + _sum
93
+ elif dt == 0 and dd == 1:
94
+ return 1 / delta
95
+ elif dt == 2 and dd == 0:
96
+ _sum = np.sum(
97
+ a0[0, 3:]
98
+ * theta0**2
99
+ * np.exp(-theta0 * tau2)
100
+ * (1 - np.exp(-theta0 * tau2)) ** -2,
101
+ axis=-1,
102
+ )
103
+ result = -a0[0, 2] / tau**2 - _sum
104
+ elif dt == 0 and dd == 2:
105
+ return -1 / delta**2
106
+ elif dt == 1 and dd == 1:
107
+ return 0
108
+ else:
109
+ raise ValueError
110
+ if return_scalar:
111
+ return result[0]
112
+ return result
113
+
114
+
115
+ def co2_residual_helmholtz_energy(delta, tau, dd, dt):
116
+ """
117
+ Residual part of Helmholtz energy as defined by the equation in Table 32 of Span & Wagner [2]. See
118
+ co2_helmholtz_energy for argument documentation.
119
+ """
120
+ tau = np.asarray(tau)
121
+ delta = np.asarray(delta)
122
+ return_scalar = (tau.ndim == 0) & (delta.ndim == 0)
123
+ tau = tau.reshape(-1, 1)
124
+ delta = delta.reshape(-1, 1)
125
+ # tau == 1.0 or delta == 1.0 leads to numerically invalid results. The values are nudged to avoid nan output.
126
+ tau[tau == 1.0] -= 1e-15
127
+ delta[delta == 1.0] -= 1e-15
128
+
129
+ res = equations.residual_helmholtz_energy(delta, tau, dd, dt)
130
+
131
+ if return_scalar:
132
+ return res[0]
133
+ return res
134
+
135
+
136
+ def carbon_dioxide_pressure(
137
+ absolute_temperature, density, d_density=0, d_temperature=0, isentropic=False
138
+ ):
139
+ """
140
+ CO2 pressure (MPa) as given by Table 3 of Span & Wagner [2]
141
+
142
+ :param absolute_temperature: Temperature in K.
143
+ :param density: CO2 density (kg / m^3).
144
+ :param d_density: Degree of derivation wrt. density.
145
+ :param d_temperature: Degree of derivation wrt. temperature.
146
+ :param isentropic: Correction for isentropic conditions. Relevant primarily for isentropic bulk modulus
147
+ """
148
+ tau = CO2_CRITICAL_TEMPERATURE / absolute_temperature
149
+ delta = density / CO2_CRITICAL_DENSITY
150
+ if d_temperature != 0:
151
+ raise ValueError(f"d_temperature must be 0, but was {d_temperature}")
152
+ if d_density == 0:
153
+ return (
154
+ density
155
+ * CO2_GAS_CONSTANT
156
+ * absolute_temperature
157
+ * (1 + delta * co2_residual_helmholtz_energy(delta, tau, 1, 0))
158
+ / 1e6
159
+ )
160
+ if d_density == 1:
161
+ first = 2 * delta * co2_residual_helmholtz_energy(delta, tau, 1, 0)
162
+ second = delta**2 * co2_residual_helmholtz_energy(delta, tau, 2, 0)
163
+ if isentropic is False:
164
+ third = 0
165
+ else:
166
+ # See Table 3 of Span & Wagner (speed of sound)
167
+ nom = (
168
+ 1
169
+ + delta * co2_residual_helmholtz_energy(delta, tau, 1, 0)
170
+ - delta * tau * co2_residual_helmholtz_energy(delta, tau, 1, 1)
171
+ ) ** 2
172
+ den = tau**2 * (co2_helmholtz_energy(delta, tau, 0, 2))
173
+ third = -nom / den
174
+ return (
175
+ absolute_temperature * CO2_GAS_CONSTANT * (1 + first + second + third) / 1e6
176
+ )
177
+ return None
178
+
179
+
180
+ def saturated_liquid_density(absolute_temperature):
181
+ """
182
+ Saturated liquid density as defined by equation 3.14 of Span & Wagner [2]
183
+
184
+ :param absolute_temperature: Absolute temperature in K. Should satisfy:
185
+ CO2_TRIPLE_TEMPERATURE < absolute_temperature < CO2_CRITICAL_TEMPERATURE
186
+ """
187
+ _a1 = 1.9245108
188
+ _a2 = -0.62385555
189
+ _a3 = -0.32731127
190
+ _a4 = 0.39245142
191
+ _t = 1 - absolute_temperature / CO2_CRITICAL_TEMPERATURE
192
+ inner = _a1 * _t**0.34 + _a2 * _t**0.5 + _a3 * _t ** (10 / 6) + _a4 * _t ** (11 / 6)
193
+ return CO2_CRITICAL_DENSITY * np.exp(inner)
194
+
195
+
196
+ def saturated_vapor_density(absolute_temperature):
197
+ """
198
+ Saturated vapor density as defined by equation 3.15 of Span & Wagner
199
+
200
+ :param absolute_temperature: Absolute temperature in K. Should satisfy:
201
+ CO2_TRIPLE_TEMPERATURE < absolute_temperature < CO2_CRITICAL_TEMPERATURE
202
+ """
203
+ # Assert temp < critical
204
+ _a1 = -1.7074879
205
+ _a2 = -0.82274670
206
+ _a3 = -4.6008549
207
+ _a4 = -10.111178
208
+ _a5 = -29.742252
209
+ _t = 1 - absolute_temperature / CO2_CRITICAL_TEMPERATURE
210
+ inner = (
211
+ _a1 * _t**0.34
212
+ + _a2 * _t**0.5
213
+ + _a3 * _t
214
+ + _a4 * _t ** (7 / 3)
215
+ + _a5 * _t ** (14 / 3)
216
+ )
217
+ return CO2_CRITICAL_DENSITY * np.exp(inner)
218
+
219
+
220
+ def sublimation_pressure(absolute_temperature):
221
+ """
222
+ Sublimation pressure as defined by equation 3.12 of Span & Wagner [2]
223
+
224
+ :param absolute_temperature: Absolute temperature in K. Should satisfy absolute_temperature < CO2_TRIPLE_TEMPERATURE
225
+ """
226
+ _a1 = -14.740846
227
+ _a2 = 2.4327015
228
+ _a3 = -5.3061778
229
+ _t = 1 - absolute_temperature / CO2_TRIPLE_TEMPERATURE
230
+ inner = _a1 * _t + _a2 * _t**1.9 + _a3 * _t**2.9
231
+ return CO2_TRIPLE_PRESSURE * np.exp(inner / (1 - _t))
232
+
233
+
234
+ def vapor_pressure(absolute_temperature):
235
+ """
236
+ Vapor pressure as defined by equation 3.13 of Span & Wagner [2]
237
+
238
+ :param absolute_temperature: Absolute temperature in K. Should satisfy:
239
+ CO2_TRIPLE_TEMPERATURE < absolute_temperature < CO2_CRITICAL_TEMPERATURE
240
+ """
241
+ _a1 = -7.0602087
242
+ _a2 = 1.9391218
243
+ _a3 = -1.6463597
244
+ _a4 = -3.2995634
245
+ _t = 1 - absolute_temperature / CO2_CRITICAL_TEMPERATURE
246
+ inner = _a1 * _t**1.0 + _a2 * _t**1.5 + _a3 * _t**2.0 + _a4 * _t**4.0
247
+ return CO2_CRITICAL_PRESSURE * np.exp(inner / (1 - _t))
248
+
249
+
250
+ def melting_pressure(absolute_temperature):
251
+ """
252
+ Melting pressure as defined by equation 3.10 of Span & Wagner [2]
253
+
254
+ :param absolute_temperature: Absolute temperature in K. Should satisfy CO2_TRIPLE_TEMPERATURE < absolute_temperature
255
+ """
256
+ _a1 = 1955.5390
257
+ _a2 = 2055.4593
258
+ _t = absolute_temperature / CO2_TRIPLE_TEMPERATURE - 1
259
+ return CO2_TRIPLE_PRESSURE * (1 + _a1 * _t + _a2 * _t**2)
260
+
261
+
262
+ def _determine_density_bounds(absolute_temperature, pressure, force_vapor):
263
+ """
264
+ Calculate the upper and lower bound on density
265
+ """
266
+ bounds = np.zeros((absolute_temperature.size, 2))
267
+ bounds[:, 0] = 0.1
268
+ bounds[:, 1] = 1500.0
269
+
270
+ below_triple = absolute_temperature < CO2_TRIPLE_TEMPERATURE
271
+ below_critical = ~below_triple & (absolute_temperature < CO2_CRITICAL_TEMPERATURE)
272
+
273
+ bounds[below_triple, 1] = saturated_vapor_density(
274
+ absolute_temperature[below_triple]
275
+ )
276
+ if force_vapor is True:
277
+ bounds[below_critical, 1] = saturated_vapor_density(
278
+ absolute_temperature[below_critical]
279
+ )
280
+ elif force_vapor is False:
281
+ bounds[below_critical, 0] = saturated_liquid_density(
282
+ absolute_temperature[below_critical]
283
+ )
284
+ else: # force_vapor == 'auto'
285
+ below_vapor_pressure = pressure < vapor_pressure(absolute_temperature)
286
+ is_vapor = below_critical & below_vapor_pressure
287
+ bounds[is_vapor, 1] = saturated_vapor_density(absolute_temperature[is_vapor])
288
+ is_liquid = below_critical & ~below_vapor_pressure
289
+ bounds[is_liquid, 0] = saturated_liquid_density(absolute_temperature[is_liquid])
290
+ return bounds
291
+
292
+
293
+ def _find_initial_density_values(bounds, absolute_temperature, pressure):
294
+ """
295
+ Finds approximate density values for the provided temperature(s) and pressure(s). The result is only intended to be
296
+ used by array_carbon_dioxide_density.
297
+ """
298
+
299
+ temps = np.geomspace(
300
+ np.min(absolute_temperature) * 0.99, np.max(absolute_temperature) * 1.01, 41
301
+ )
302
+ press = np.geomspace(
303
+ np.min(pressure) * 0.99, np.max(absolute_temperature) * 1.01, 41
304
+ )
305
+ tt, pp = np.meshgrid(temps, press, indexing="ij")
306
+ densi = carbon_dioxide_density(
307
+ tt.flatten(), pp.flatten(), force_vapor="auto", raise_error=False
308
+ ).reshape(temps.size, press.size)
309
+ rgi = RegularGridInterpolator(np.array((temps, press)), densi, method="linear")
310
+ iv = rgi(np.array((absolute_temperature, pressure)).T)
311
+ oob = (iv < bounds[:, 0]) | (iv > bounds[:, 1]) | np.isnan(iv)
312
+ iv[oob] = np.mean(bounds[oob], axis=1)
313
+ return iv
314
+
315
+
316
+ def array_carbon_dioxide_density(absolute_temperature, pressure, force_vapor):
317
+ """
318
+ Alternative implementation of a vectorized carbon dioxide density function. Implemented primarily for demonstration
319
+ purposes. For large arrays, a look-up-table approach should be preferred.
320
+
321
+ Utilizes scipy.optimize.newton, which is the only root-finding method of scipy that supports a vectorized functions.
322
+
323
+ For argument documentation, see carbon_dioxide_density.
324
+ """
325
+ absolute_temperature = np.asarray(absolute_temperature)
326
+ pressure = np.asarray(pressure)
327
+ bounds = _determine_density_bounds(absolute_temperature, pressure, force_vapor)
328
+ iv = _find_initial_density_values(bounds, absolute_temperature, pressure)
329
+ opt = scipy.optimize.newton(
330
+ lambda x: carbon_dioxide_pressure(absolute_temperature, x) - pressure,
331
+ x0=iv,
332
+ maxiter=10,
333
+ full_output=True,
334
+ )
335
+ # scipy.optimize.newton may not always converge. We need to determine which of the elements of the solution are
336
+ # invalid. The opt.converged variable does not seem to suffice, so we perform separate checks. First, check that
337
+ # the solution is a valid root
338
+ invalid = ~np.isclose(
339
+ carbon_dioxide_pressure(absolute_temperature, opt.root),
340
+ pressure,
341
+ atol=1e-5,
342
+ rtol=0.0,
343
+ )
344
+
345
+ # Next, check if the solution is anywhere out of bounds (since newton does not support brackets), and check for nan
346
+ # values.
347
+ invalid |= (
348
+ (opt.root < bounds[:, 0]) | (opt.root > bounds[:, 1]) | np.isnan(opt.root)
349
+ )
350
+
351
+ # Finally, use the robust density method to determine the invalid results
352
+ sol = opt.root
353
+ sol[invalid] = carbon_dioxide_density(
354
+ absolute_temperature[invalid], pressure[invalid], force_vapor=force_vapor
355
+ )
356
+ return sol
357
+
358
+
359
+ def float_vectorize(f):
360
+ # TODO:
361
+ # Should test if execution time is longer when using the float_vectorize instead of using numpy arrays directly.
362
+ # It may not be possible to use numpy arrays directly in span-wagner, but that should be tested
363
+ return np.vectorize(f, otypes=[float])
364
+
365
+
366
+ @float_vectorize
367
+ def _calculate_carbon_dioxide_density(
368
+ absolute_temperature, pressure, force_vapor="auto", raise_error=True
369
+ ):
370
+ """
371
+ Density of carbon dioxide. Found solving the Pressure equation of Table 3 in Span & Wagner [2] numerically for
372
+ density. To ensure a single solution, the phase of the liquid must first be determined.
373
+
374
+ :param absolute_temperature: Absolute temperature (K).
375
+ :param pressure: Pressure (MPa).
376
+ :param force_vapor: Boolean or 'auto'. If 'auto', the phase of the fluid is automatically determined. However, along
377
+ the vaporization line (assuming T_triple < absolute_temperature < T_critical), the fluid is in two-phase
378
+ equilibrium and the phase cannot be uniquely determined. If force_vapor is set to True, vapor phase is always
379
+ selected, if False, liquid phase is selected. Outide the temperature bounds, this argument has no effect. This
380
+ argument should only be used close to the vaporization boundary, otherwise the behavior might not be as
381
+ expected.
382
+ :param raise_error: Boolean. If True, raises an error if density cannot be determined. Otherwise, returns np.nan.
383
+
384
+ :return: Density (kg / m^3)
385
+ """
386
+ bounds = _determine_density_bounds(
387
+ np.array([absolute_temperature]), np.array([pressure]), force_vapor
388
+ )[0, :]
389
+
390
+ # Extend bounds slightly to ensure toms748 can converge
391
+ bounds = [bounds[0] * 0.95, bounds[1] * 1.05]
392
+
393
+ try:
394
+ opt = scipy.optimize.root_scalar(
395
+ lambda x: carbon_dioxide_pressure(absolute_temperature, x) - pressure,
396
+ method="toms748",
397
+ bracket=bounds,
398
+ x0=np.sum(bounds) / 2,
399
+ )
400
+ except ValueError:
401
+ if raise_error:
402
+ raise
403
+ return np.nan
404
+ return opt.root
405
+
406
+
407
+ def carbon_dioxide_density(absolute_temperature, pressure, interpolate=False, **kwargs):
408
+ """
409
+ Density of carbon dioxide. Found either by direct calculation or interpolation. Any additional arguments are passed
410
+ to _calculate_carbon_dioxide_density.
411
+
412
+ :param absolute_temperature: Absolute temperature (K).
413
+ :param pressure: Pressure (MPa).
414
+ :param interpolate: Flag whether to interpolate data or not. If not, data is calculated directly. This is more
415
+ accurate, but also more time-consuming. Data outside the bounds of the interpolator will be set to np.nan.
416
+
417
+ :return: Density (kg / m^3)
418
+ """
419
+ if interpolate is False:
420
+ return _calculate_carbon_dioxide_density(
421
+ absolute_temperature, pressure, **kwargs
422
+ )
423
+ assert interpolate is True
424
+
425
+ ref = (
426
+ importlib.resources.files(
427
+ "rock_physics.fluid_models.gas_model.span_wagner.tables"
428
+ )
429
+ / "carbon_dioxide_density.npz"
430
+ )
431
+ with importlib.resources.as_file(ref) as fp:
432
+ interpolator = load_lookup_table_interpolator(fp)
433
+
434
+ return interpolator(absolute_temperature, pressure)
435
+
436
+
437
+ def carbon_dioxide_bulk_modulus(absolute_temperature, density):
438
+ """
439
+ Isentropic bulk modulus, derived from the expression for speed of sound in Table 3 of Span & Wagner
440
+ """
441
+ d_pressure = carbon_dioxide_pressure(
442
+ absolute_temperature, density, d_density=1, isentropic=True
443
+ )
444
+ return density * d_pressure * 1e6
@@ -0,0 +1,165 @@
1
+ """
2
+ Span-Wagner Coefficients
3
+
4
+ Coefficients defined in tables of Span & Wagner. Coefficients are divided into groups according to horizontal lines in
5
+ the table. This is also convenient when evaluating the Helmholtz energy expressions (phi functions).
6
+ """
7
+
8
+ from numpy import array
9
+
10
+ """ Ideal gas part (table 27) """
11
+ a0 = array(
12
+ [
13
+ [
14
+ 8.37304456,
15
+ -3.70454304,
16
+ 2.50000000,
17
+ 1.99427042,
18
+ 0.62105248,
19
+ 0.41195293,
20
+ 1.04028922,
21
+ 0.08327678,
22
+ ]
23
+ ]
24
+ )
25
+ theta0 = array(
26
+ [
27
+ [
28
+ 3.15163,
29
+ 6.11190,
30
+ 6.77708,
31
+ 11.32384,
32
+ 27.08792,
33
+ ]
34
+ ]
35
+ )
36
+
37
+ """ Residual part (table 31) """
38
+
39
+ # First group
40
+ n1 = array(
41
+ [
42
+ [
43
+ 0.38856823203161 * 1e0,
44
+ 0.29385475942740 * 1e1,
45
+ -0.55867188534934 * 1e1,
46
+ -0.76753199592477 * 1e0,
47
+ 0.31729005580416 * 1e0,
48
+ 0.54803315897767 * 1e0,
49
+ 0.12279411220335 * 1e0,
50
+ ]
51
+ ]
52
+ )
53
+ d1 = array([[1, 1, 1, 1, 2, 2, 3]])
54
+ t1 = array([[0.00, 0.75, 1.00, 2.00, 0.75, 2.00, 0.75]])
55
+
56
+ # Second group
57
+ n2 = array(
58
+ [
59
+ [
60
+ 0.21658961543220 * 1e1,
61
+ 0.15841735109724 * 1e1,
62
+ -0.23132705405503 * 1e0,
63
+ 0.58116916431436 * 1e-1,
64
+ -0.55369137205382 * 1e0,
65
+ 0.48946615909422 * 1e0,
66
+ -0.24275739843501 * 1e-1,
67
+ 0.62494790501678 * 1e-1,
68
+ -0.12175860225246 * 1e0,
69
+ -0.37055685270086 * 1e0,
70
+ -0.16775879700426 * 1e-1,
71
+ -0.11960736637987 * 1e0,
72
+ -0.45619362508778 * 1e-1,
73
+ 0.35612789270346 * 1e-1,
74
+ -0.74427727132052 * 1e-2,
75
+ -0.17395704902432 * 1e-2,
76
+ -0.21810121289527 * 1e-1,
77
+ 0.24332166559236 * 1e-1,
78
+ -0.37440133423463 * 1e-1,
79
+ 0.143387157568781 * 1e0,
80
+ -0.13491969083286 * 1e0,
81
+ -0.23151225053480 * 1e-1,
82
+ 0.12363125492901 * 1e-1,
83
+ 0.21058321972940 * 1e-2,
84
+ -0.33958519026368 * 1e-3,
85
+ 0.55993651771592 * 1e-2,
86
+ -0.30335118055646 * 1e-3,
87
+ ]
88
+ ]
89
+ )
90
+ d2 = array(
91
+ [[1, 2, 4, 5, 5, 5, 6, 6, 6, 1, 1, 4, 4, 4, 7, 8, 2, 3, 3, 5, 5, 6, 7, 8, 10, 4, 8]]
92
+ )
93
+ t2 = array(
94
+ [
95
+ [
96
+ 1.50,
97
+ 1.50,
98
+ 2.50,
99
+ 0.00,
100
+ 1.50,
101
+ 2.00,
102
+ 0.00,
103
+ 1.00,
104
+ 2.00,
105
+ 3.00,
106
+ 6.00,
107
+ 3.00,
108
+ 6.00,
109
+ 8.00,
110
+ 6.00,
111
+ 0.00,
112
+ 7.00,
113
+ 12.00,
114
+ 16.00,
115
+ 22.00,
116
+ 24.00,
117
+ 16.00,
118
+ 24.00,
119
+ 8.00,
120
+ 2.00,
121
+ 28.00,
122
+ 14.00,
123
+ ]
124
+ ]
125
+ )
126
+ c2 = array(
127
+ [[1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 4, 4, 5, 6]]
128
+ )
129
+
130
+ # Third group
131
+ n3 = array(
132
+ [
133
+ [
134
+ -0.21365488688320 * 1e3,
135
+ 0.26641569149272 * 1e5,
136
+ -0.24027212204557 * 1e5,
137
+ -0.28341603423999 * 1e3,
138
+ 0.21247284400179 * 1e3,
139
+ ]
140
+ ]
141
+ )
142
+ d3 = array([[2, 2, 2, 3, 3]])
143
+ t3 = array([[1.00, 0.00, 1.00, 3.00, 3.00]])
144
+ alpha3 = array([[25, 25, 25, 15, 20]])
145
+ beta3 = array([[325, 300, 300, 275, 275]])
146
+ gamma3 = array([[1.16, 1.19, 1.19, 1.25, 1.22]])
147
+ epsilon3 = array([[1.00, 1.00, 1.00, 1.00, 1.00]])
148
+
149
+ # Fourth group
150
+ n4 = array(
151
+ [
152
+ [
153
+ -0.66642276540751 * 1e0,
154
+ 0.72608632349897 * 1e0,
155
+ 0.55068668612842 * 1e-1,
156
+ ]
157
+ ]
158
+ )
159
+ a4 = array([[3.500, 3.500, 3.000]])
160
+ b4 = array([[0.875, 0.925, 0.875]])
161
+ beta4 = array([[0.300, 0.300, 0.300]])
162
+ A4 = array([[0.700, 0.700, 0.700]])
163
+ B4 = array([[0.3, 0.3, 1.0]])
164
+ C4 = array([[10.0, 10.0, 12.5]])
165
+ D4 = array([[275, 275, 275]])
@@ -0,0 +1,104 @@
1
+ """
2
+ Package for calculating the residual helmholtz energy for CO2. The module uses sympy to evaluate the functions defined
3
+ in Span & Wagner [2], as well as its derivatives.
4
+ """
5
+
6
+ import numpy as np
7
+ import sympy as sp
8
+
9
+ from . import coefficients
10
+
11
+
12
+ def residual_helmholtz_energy(delta_, tau_, diff_delta, diff_tau):
13
+ """
14
+ Equation 6.1 from Span & Wagner [2]. Calculates the residual helmholtz energy of co2 or its derivatives. tau_ and
15
+ delta_ must have be numpy arrays of shape (N, 1). This allows for vectorization.
16
+
17
+ :param delta_: Reduced density. Unit-less. numpy.ndarray with shape (N, 1)
18
+ :param tau_: Inverse reduced temperature. Unit-less. numpy.ndarray with shape (N, 1)
19
+ :param diff_delta: Degree of derivation wrt. delta. Integer.
20
+ :param diff_tau: Degree of derivation wrt. tau. Integer.
21
+
22
+ :return: Helmholtz free energy. Unit-less. numpy.ndarray with shape (N,)
23
+ """
24
+ _s1 = np.sum(
25
+ _LAMBDIFIED_EXPRESSIONS[(s1, diff_delta, diff_tau)](tau_, delta_), axis=-1
26
+ )
27
+ _s2 = np.sum(
28
+ _LAMBDIFIED_EXPRESSIONS[(s2, diff_delta, diff_tau)](tau_, delta_), axis=-1
29
+ )
30
+ _s3 = np.sum(
31
+ _LAMBDIFIED_EXPRESSIONS[(s3, diff_delta, diff_tau)](tau_, delta_), axis=-1
32
+ )
33
+ _s4 = np.sum(
34
+ _LAMBDIFIED_EXPRESSIONS[(s4, diff_delta, diff_tau)](tau_, delta_), axis=-1
35
+ )
36
+
37
+ return _s1 + _s2 + _s3 + _s4
38
+
39
+
40
+ # Define coefficients symbols. These should correspond one-to-one with variable names in the coefficient module
41
+ coeff_symbols = (
42
+ n1,
43
+ t1,
44
+ d1,
45
+ n2,
46
+ d2,
47
+ t2,
48
+ c2,
49
+ n3,
50
+ d3,
51
+ t3,
52
+ alpha3,
53
+ epsilon3,
54
+ beta3,
55
+ gamma3,
56
+ n4,
57
+ b4,
58
+ a4,
59
+ beta4,
60
+ A4,
61
+ B4,
62
+ C4,
63
+ D4,
64
+ ) = sp.symbols(
65
+ "n1 t1 d1 n2 d2 t2 c2 n3 d3 t3 alpha3 epsilon3 beta3 gamma3 n4 b4 a4 beta4 A4 B4 C4 D4"
66
+ )
67
+ tau, delta = sp.symbols("tau delta", real=True)
68
+ i = sp.symbols("i", integer=True)
69
+ s1 = n1 * delta**d1 * tau**t1
70
+ s2 = n2 * delta**d2 * tau**t2 * sp.exp(-(delta**c2))
71
+ s3 = (
72
+ n3
73
+ * delta**d3
74
+ * tau**t3
75
+ * sp.exp(-alpha3 * (delta - epsilon3) ** 2 - beta3 * (tau - gamma3) ** 2)
76
+ )
77
+
78
+ theta_expr = (1 - tau) + A4 * ((delta - 1) ** 2) ** (1 / (2 * beta4))
79
+ bigdelta_expr = theta_expr**2 + B4 * ((delta - 1) ** 2) ** a4
80
+ bigphi_expr = sp.exp(-C4 * (delta - 1) ** 2 - D4 * (tau - 1) ** 2)
81
+ s4 = n4 * bigdelta_expr**b4 * delta * bigphi_expr
82
+
83
+
84
+ def _lambdify(expr, diff_delta, diff_tau):
85
+ diff = [delta] * diff_delta + [tau] * diff_tau
86
+ if len(diff) > 0:
87
+ expr = expr.diff(*diff)
88
+ expr = expr.powsimp()
89
+ coeff_vars = [getattr(coefficients, s.name) for s in coeff_symbols]
90
+ _sympy_lambda = sp.utilities.lambdify(
91
+ list(coeff_symbols) + [tau, delta],
92
+ expr,
93
+ modules=["numpy", {"DiracDelta": lambda x: x == 0}],
94
+ )
95
+ return lambda _tau, _delta: _sympy_lambda(*coeff_vars, _tau, _delta)
96
+
97
+
98
+ _LAMBDIFIED_EXPRESSIONS = {
99
+ (e, dd, dt): _lambdify(e, dd, dt)
100
+ for e in (s1, s2, s3, s4)
101
+ for dd in (0, 1, 2)
102
+ for dt in (0, 1, 2)
103
+ if dd + dt <= 2
104
+ }