rock-physics-open 0.0__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.

Potentially problematic release.


This version of rock-physics-open might be problematic. Click here for more details.

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