rock-physics-open 0.2.3__py3-none-any.whl → 0.3.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 (42) hide show
  1. rock_physics_open/equinor_utilities/gen_utilities/dict_to_float.py +6 -1
  2. rock_physics_open/equinor_utilities/gen_utilities/dim_check_vector.py +35 -5
  3. rock_physics_open/equinor_utilities/gen_utilities/filter_input.py +11 -6
  4. rock_physics_open/equinor_utilities/gen_utilities/filter_output.py +29 -19
  5. rock_physics_open/equinor_utilities/machine_learning_utilities/__init__.py +18 -5
  6. rock_physics_open/equinor_utilities/machine_learning_utilities/base_pressure_model.py +172 -0
  7. rock_physics_open/equinor_utilities/machine_learning_utilities/exponential_model.py +100 -86
  8. rock_physics_open/equinor_utilities/machine_learning_utilities/friable_pressure_models.py +230 -0
  9. rock_physics_open/equinor_utilities/machine_learning_utilities/import_ml_models.py +23 -4
  10. rock_physics_open/equinor_utilities/machine_learning_utilities/patchy_cement_pressure_models.py +280 -0
  11. rock_physics_open/equinor_utilities/machine_learning_utilities/polynomial_model.py +128 -0
  12. rock_physics_open/equinor_utilities/machine_learning_utilities/sigmoidal_model.py +204 -155
  13. rock_physics_open/equinor_utilities/optimisation_utilities/__init__.py +19 -0
  14. rock_physics_open/equinor_utilities/snapshot_test_utilities/compare_snapshots.py +1 -2
  15. rock_physics_open/fluid_models/brine_model/brine_properties.py +70 -35
  16. rock_physics_open/fluid_models/gas_model/gas_properties.py +79 -37
  17. rock_physics_open/fluid_models/oil_model/dead_oil_density.py +21 -16
  18. rock_physics_open/fluid_models/oil_model/dead_oil_velocity.py +9 -7
  19. rock_physics_open/fluid_models/oil_model/live_oil_density.py +16 -13
  20. rock_physics_open/fluid_models/oil_model/live_oil_velocity.py +3 -3
  21. rock_physics_open/fluid_models/oil_model/oil_properties.py +59 -29
  22. rock_physics_open/sandstone_models/__init__.py +2 -0
  23. rock_physics_open/sandstone_models/constant_cement_optimisation.py +4 -1
  24. rock_physics_open/sandstone_models/friable_optimisation.py +4 -1
  25. rock_physics_open/sandstone_models/patchy_cement_model.py +89 -6
  26. rock_physics_open/sandstone_models/patchy_cement_optimisation.py +4 -1
  27. rock_physics_open/t_matrix_models/__init__.py +0 -10
  28. rock_physics_open/t_matrix_models/carbonate_pressure_substitution.py +1 -1
  29. rock_physics_open/t_matrix_models/curvefit_t_matrix_exp.py +1 -2
  30. rock_physics_open/t_matrix_models/t_matrix_opt_fluid_sub_exp.py +3 -3
  31. rock_physics_open/t_matrix_models/t_matrix_opt_fluid_sub_petec.py +5 -1
  32. rock_physics_open/t_matrix_models/t_matrix_opt_forward_model_exp.py +5 -1
  33. rock_physics_open/t_matrix_models/t_matrix_opt_forward_model_min.py +4 -1
  34. rock_physics_open/t_matrix_models/t_matrix_parameter_optimisation_exp.py +5 -1
  35. rock_physics_open/t_matrix_models/t_matrix_parameter_optimisation_min.py +4 -1
  36. rock_physics_open/version.py +2 -2
  37. {rock_physics_open-0.2.3.dist-info → rock_physics_open-0.3.0.dist-info}/METADATA +4 -8
  38. {rock_physics_open-0.2.3.dist-info → rock_physics_open-0.3.0.dist-info}/RECORD +42 -37
  39. /rock_physics_open/{t_matrix_models → equinor_utilities/optimisation_utilities}/opt_subst_utilities.py +0 -0
  40. {rock_physics_open-0.2.3.dist-info → rock_physics_open-0.3.0.dist-info}/WHEEL +0 -0
  41. {rock_physics_open-0.2.3.dist-info → rock_physics_open-0.3.0.dist-info}/licenses/LICENSE +0 -0
  42. {rock_physics_open-0.2.3.dist-info → rock_physics_open-0.3.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,230 @@
1
+ from __future__ import annotations
2
+
3
+ import pickle
4
+ from typing import Any, Self
5
+
6
+ import numpy as np
7
+
8
+ from rock_physics_open.sandstone_models.friable_models import friable_model_dry
9
+
10
+ from .base_pressure_model import BasePressureModel
11
+
12
+
13
+ class FriableDryPressureModel(BasePressureModel):
14
+ """
15
+ Pressure sensitivity model using friable dry rock bulk modulus.
16
+
17
+ This model calculates pressure-dependent bulk modulus using the friable
18
+ sandstone model, which represents unconsolidated sands where porosity
19
+ variation is due to grain sorting.
20
+
21
+ Input format (n,5): [phi, k_min, mu_min, p_eff_in_situ, p_eff_depleted]
22
+
23
+ Where:
24
+ - phi: porosity [fraction]
25
+ - k_min: mineral bulk modulus per sample [Pa]
26
+ - mu_min: mineral shear modulus per sample [Pa]
27
+ - p_eff_in_situ: effective pressure in-situ [Pa]
28
+ - p_eff_depleted: effective pressure depleted [Pa]
29
+
30
+ The model returns bulk and shear modulus changes due to pressure variations.
31
+ """
32
+
33
+ def __init__(
34
+ self,
35
+ phi_c: float,
36
+ coord_num_func: str = "Porosity",
37
+ n: float | None = None,
38
+ shear_red: float = 1.0,
39
+ model_max_pressure: float | None = None,
40
+ description: str = "",
41
+ ):
42
+ """
43
+ Initialize friable dry pressure model.
44
+
45
+ Parameters
46
+ ----------
47
+ phi_c : float
48
+ Critical porosity [fraction]. Porosity above this value is undefined.
49
+ coord_num_func : str
50
+ Coordination number calculation method: "Porosity" or "ConstVal".
51
+ n : float | None
52
+ Coordination number [unitless]. Used only if coord_num_func="ConstVal".
53
+ shear_red : float
54
+ Shear reduction factor [fraction] for tangential grain contacts.
55
+ model_max_pressure : float | None
56
+ Maximum pressure for predict_max method [Pa].
57
+ description : str
58
+ Human-readable description of this model instance.
59
+ """
60
+ super().__init__(model_max_pressure, description)
61
+ self._phi_c = phi_c
62
+ self._coord_num_func = coord_num_func
63
+ self._n = n
64
+ self._shear_red = shear_red
65
+
66
+ def validate_input(self, inp_arr: np.ndarray) -> np.ndarray:
67
+ """
68
+ Validate input array for friable model.
69
+
70
+ Parameters
71
+ ----------
72
+ inp_arr : np.ndarray
73
+ Input array to validate.
74
+
75
+ Returns
76
+ -------
77
+ np.ndarray
78
+ Validated input array.
79
+
80
+ Raises
81
+ ------
82
+ ValueError
83
+ If input is not a 2D numpy array with 5 columns.
84
+ """
85
+ if not isinstance(inp_arr, np.ndarray):
86
+ raise ValueError("Input must be numpy ndarray.")
87
+ if inp_arr.ndim != 2 or inp_arr.shape[1] != 5:
88
+ raise ValueError(
89
+ "Input must be (n,5): [phi, k_min, mu_min, p_eff_in_situ, p_eff_depleted]"
90
+ )
91
+ return inp_arr
92
+
93
+ def _compute_moduli(
94
+ self, inp_arr: np.ndarray, case: str = "in_situ"
95
+ ) -> tuple[np.ndarray, np.ndarray]:
96
+ """
97
+ Calculate absolute bulk modulus for specified pressure case.
98
+
99
+ Parameters
100
+ ----------
101
+ inp_arr : np.ndarray
102
+ Validated input array (n,5).
103
+ case : str
104
+ Pressure case: "in_situ" or "depleted".
105
+
106
+ Returns
107
+ -------
108
+ np.ndarray
109
+ Bulk modulus values [Pa].
110
+ """
111
+ arr = self.validate_input(inp_arr)
112
+
113
+ # Parse input columns
114
+ phi = arr[:, 0]
115
+ k_min = arr[:, 1]
116
+ mu_min = arr[:, 2]
117
+ p_in_situ = arr[:, 3]
118
+ p_depleted = arr[:, 4]
119
+
120
+ # Select pressure based on case
121
+ p_eff = p_in_situ if case == "in_situ" else p_depleted
122
+
123
+ # Calculate dry bulk modulus using friable model
124
+ k_dry, mu = friable_model_dry(
125
+ k_min,
126
+ mu_min,
127
+ phi,
128
+ p_eff,
129
+ self._phi_c,
130
+ self._coord_num_func,
131
+ self._n,
132
+ self._shear_red,
133
+ )
134
+
135
+ return k_dry, mu
136
+
137
+ def todict(self) -> dict[str, Any]:
138
+ """
139
+ Convert model to dictionary for serialization.
140
+
141
+ Returns
142
+ -------
143
+ dict[str, Any]
144
+ Dictionary containing all model parameters.
145
+ """
146
+ return {
147
+ "phi_c": self._phi_c,
148
+ "coord_num_func": self._coord_num_func,
149
+ "n": self._n,
150
+ "shear_red": self._shear_red,
151
+ "model_max_pressure": self._model_max_pressure,
152
+ "description": self._description,
153
+ }
154
+
155
+ @classmethod
156
+ def load(cls, file: str | bytes) -> Self:
157
+ """
158
+ Load friable model from pickle file.
159
+
160
+ Parameters
161
+ ----------
162
+ file : str, bytes
163
+ File path for loading.
164
+
165
+ Returns
166
+ -------
167
+ FriableDryPressureModel
168
+ Loaded model instance.
169
+ """
170
+ with open(file, "rb") as f_in:
171
+ d = pickle.load(f_in)
172
+
173
+ return cls(
174
+ phi_c=d["phi_c"],
175
+ coord_num_func=d["coord_num_func"],
176
+ n=d["n"],
177
+ shear_red=d["shear_red"],
178
+ model_max_pressure=d["model_max_pressure"],
179
+ description=d["description"],
180
+ )
181
+
182
+
183
+ class FriableDryShearModulusPressureModel(FriableDryPressureModel):
184
+ """
185
+ Pressure sensitivity model using friable dry rock bulk modulus.
186
+
187
+ This model calculates pressure-dependent bulk modulus using the friable
188
+ sandstone model, which represents unconsolidated sands where porosity
189
+ variation is due to grain sorting.
190
+
191
+ Input format (n,5): [phi, k_min, mu_min, p_eff_in_situ, p_eff_depleted]
192
+
193
+ Where:
194
+ - phi: porosity [fraction]
195
+ - k_min: mineral bulk modulus per sample [Pa]
196
+ - mu_min: mineral shear modulus per sample [Pa]
197
+ - p_eff_in_situ: effective pressure in-situ [Pa]
198
+ - p_eff_depleted: effective pressure depleted [Pa]
199
+
200
+ The model returns shear modulus changes due to pressure variations.
201
+ """
202
+
203
+ def predict_abs(self, inp_arr: np.ndarray, case: str = "in_situ") -> np.ndarray:
204
+ k_dry, mu = self._compute_moduli(inp_arr, case)
205
+ return mu
206
+
207
+
208
+ class FriableDryBulkModulusPressureModel(FriableDryPressureModel):
209
+ """
210
+ Pressure sensitivity model using friable dry rock bulk modulus.
211
+
212
+ This model calculates pressure-dependent bulk modulus using the friable
213
+ sandstone model, which represents unconsolidated sands where porosity
214
+ variation is due to grain sorting.
215
+
216
+ Input format (n,5): [phi, k_min, mu_min, p_eff_in_situ, p_eff_depleted]
217
+
218
+ Where:
219
+ - phi: porosity [fraction]
220
+ - k_min: mineral bulk modulus per sample [Pa]
221
+ - mu_min: mineral shear modulus per sample [Pa]
222
+ - p_eff_in_situ: effective pressure in-situ [Pa]
223
+ - p_eff_depleted: effective pressure depleted [Pa]
224
+
225
+ The model returns bulk modulus changes due to pressure variations.
226
+ """
227
+
228
+ def predict_abs(self, inp_arr: np.ndarray, case: str = "in_situ") -> np.ndarray:
229
+ k_dry, mu = self._compute_moduli(inp_arr, case)
230
+ return k_dry
@@ -1,5 +1,14 @@
1
- from .exponential_model import CarbonateExponentialPressure
2
- from .sigmoidal_model import CarbonateSigmoidalPressure
1
+ from .exponential_model import ExponentialPressureModel
2
+ from .friable_pressure_models import (
3
+ FriableDryBulkModulusPressureModel,
4
+ FriableDryShearModulusPressureModel,
5
+ )
6
+ from .patchy_cement_pressure_models import (
7
+ PatchyCementDryBulkModulusPressureModel,
8
+ PatchyCementDryShearModulusPressureModel,
9
+ )
10
+ from .polynomial_model import PolynomialPressureModel
11
+ from .sigmoidal_model import SigmoidalPressureModel
3
12
 
4
13
 
5
14
  def import_model(model_file_name):
@@ -33,9 +42,19 @@ def import_model(model_file_name):
33
42
  mod_dict = load(fin)
34
43
 
35
44
  if mod_dict["model_type"] == "Sigmoid":
36
- models = CarbonateSigmoidalPressure.load(mod_dict["nn_mod"])
45
+ models = SigmoidalPressureModel.load(mod_dict["nn_mod"])
37
46
  elif mod_dict["model_type"] == "Exponential":
38
- models = CarbonateExponentialPressure.load(mod_dict["nn_mod"])
47
+ models = ExponentialPressureModel.load(mod_dict["nn_mod"])
48
+ elif mod_dict["model_type"] == "Polynomial":
49
+ models = PolynomialPressureModel.load(mod_dict["nn_mod"])
50
+ elif mod_dict["model_type"] == "FriableDryBulk":
51
+ models = FriableDryBulkModulusPressureModel.load(mod_dict["nn_mod"])
52
+ elif mod_dict["model_type"] == "FriableDryShear":
53
+ models = FriableDryShearModulusPressureModel.load(mod_dict["nn_mod"])
54
+ elif mod_dict["model_type"] == "PatchyCementDryBulk":
55
+ models = PatchyCementDryBulkModulusPressureModel.load(mod_dict["nn_mod"])
56
+ elif mod_dict["model_type"] == "PatchyCementDryShear":
57
+ models = PatchyCementDryShearModulusPressureModel.load(mod_dict["nn_mod"])
39
58
  else:
40
59
  raise ValueError("unknown model type {}".format(mod_dict["model_type"]))
41
60
 
@@ -0,0 +1,280 @@
1
+ from __future__ import annotations
2
+
3
+ import pickle
4
+ from typing import Any, Self
5
+
6
+ import numpy as np
7
+
8
+ from rock_physics_open.sandstone_models.patchy_cement_model import (
9
+ patchy_cement_model_dry,
10
+ )
11
+
12
+ from .base_pressure_model import BasePressureModel
13
+
14
+
15
+ class PatchyCementDryPressureModel(BasePressureModel):
16
+ """
17
+ Pressure sensitivity model using patchy cement dry rock properties.
18
+
19
+ This model combines friable and cemented sandstone behavior, representing
20
+ partially cemented sands with heterogeneous cement distribution.
21
+
22
+ Input format (n,9): [phi, k_min, mu_min, rho_min, k_cem, mu_cem, rho_cem, p_eff_in_situ, p_eff_depleted]
23
+
24
+ Where:
25
+ - phi: porosity [fraction]
26
+ - k_min: mineral bulk modulus [Pa]
27
+ - mu_min: mineral shear modulus [Pa]
28
+ - rho_min: mineral density [kg/m³]
29
+ - k_cem: cement bulk modulus [Pa]
30
+ - mu_cem: cement shear modulus [Pa]
31
+ - rho_cem: cement density [kg/m³]
32
+ - p_eff_in_situ: effective pressure in-situ [Pa]
33
+ - p_eff_depleted: effective pressure depleted [Pa]
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ frac_cem: float,
39
+ phi_c: float,
40
+ coord_num_func: str = "Porosity",
41
+ n: float | None = None,
42
+ shear_red: float = 1.0,
43
+ model_max_pressure: float | None = None,
44
+ description: str = "",
45
+ ):
46
+ """
47
+ Initialize patchy cement dry pressure model.
48
+
49
+ Parameters
50
+ ----------
51
+ frac_cem : float
52
+ Cement volume fraction [fraction] defining upper bound behavior.
53
+ phi_c : float
54
+ Critical porosity [fraction].
55
+ coord_num_func : str
56
+ Coordination number method: "Porosity" or "ConstVal".
57
+ n : float | None
58
+ Coordination number [unitless]. Used if coord_num_func="ConstVal".
59
+ shear_red : float
60
+ Shear reduction factor [fraction].
61
+ model_max_pressure : float | None
62
+ Maximum pressure for predict_max method [Pa].
63
+ description : str
64
+ Model description.
65
+ """
66
+ super().__init__(model_max_pressure, description)
67
+ self._frac_cem = frac_cem
68
+ self._phi_c = phi_c
69
+ self._coord_num_func = coord_num_func
70
+ self._n = n
71
+ self._shear_red = shear_red
72
+
73
+ def validate_input(self, inp_arr: np.ndarray) -> np.ndarray:
74
+ """
75
+ Validate input array for patchy cement model.
76
+
77
+ Parameters
78
+ ----------
79
+ inp_arr : np.ndarray
80
+ Input array to validate.
81
+
82
+ Returns
83
+ -------
84
+ np.ndarray
85
+ Validated input array.
86
+
87
+ Raises
88
+ ------
89
+ ValueError
90
+ If input is not a 2D numpy array with 9 columns.
91
+ """
92
+ if not isinstance(inp_arr, np.ndarray):
93
+ raise ValueError("Input must be numpy ndarray.")
94
+ if inp_arr.ndim != 2 or inp_arr.shape[1] != 9:
95
+ raise ValueError(
96
+ "Input must be (n,9): [phi, k_min, mu_min, rho_min, k_cem, mu_cem, rho_cem, p_eff_in_situ, p_eff_depleted]"
97
+ )
98
+ return inp_arr
99
+
100
+ def _compute_moduli(
101
+ self, inp_arr: np.ndarray, case: str = "in_situ"
102
+ ) -> tuple[np.ndarray, np.ndarray]:
103
+ """
104
+ Calculate absolute bulk modulus for specified pressure case.
105
+
106
+ Parameters
107
+ ----------
108
+ inp_arr : np.ndarray
109
+ Validated input array (n,9).
110
+ case : str
111
+ Pressure case: 'in_situ' or 'depleted'.
112
+
113
+ Returns
114
+ -------
115
+ np.ndarray
116
+ Bulk modulus values [Pa].
117
+ """
118
+ arr = self.validate_input(inp_arr)
119
+
120
+ # Parse input columns
121
+ phi = arr[:, 0]
122
+ k_min = arr[:, 1]
123
+ mu_min = arr[:, 2]
124
+ rho_min = arr[:, 3]
125
+ k_cem = arr[:, 4]
126
+ mu_cem = arr[:, 5]
127
+ rho_cem = arr[:, 6]
128
+ p_in_situ = arr[:, 7]
129
+ p_depleted = arr[:, 8]
130
+
131
+ # Select pressure based on case
132
+ p_eff = p_in_situ if case == "in_situ" else p_depleted
133
+
134
+ # Calculate dry bulk modulus using patchy cement model
135
+ k_dry, mu, _ = patchy_cement_model_dry(
136
+ k_min,
137
+ mu_min,
138
+ rho_min,
139
+ k_cem,
140
+ mu_cem,
141
+ rho_cem,
142
+ phi,
143
+ p_eff,
144
+ self._frac_cem,
145
+ self._phi_c,
146
+ self._coord_num_func,
147
+ self._n,
148
+ self._shear_red,
149
+ )
150
+
151
+ return k_dry, mu
152
+
153
+ def todict(self) -> dict[str, Any]:
154
+ """
155
+ Convert model to dictionary for serialization.
156
+
157
+ Returns
158
+ -------
159
+ dict[str, Any]
160
+ Dictionary containing all model parameters.
161
+ """
162
+ return {
163
+ "frac_cem": self._frac_cem,
164
+ "phi_c": self._phi_c,
165
+ "coord_num_func": self._coord_num_func,
166
+ "n": self._n,
167
+ "shear_red": self._shear_red,
168
+ "model_max_pressure": self._model_max_pressure,
169
+ "description": self._description,
170
+ }
171
+
172
+ @classmethod
173
+ def load(cls, file: str | bytes) -> Self:
174
+ """
175
+ Load patchy cement model from pickle file.
176
+
177
+ Parameters
178
+ ----------
179
+ file : str | bytes
180
+ File path for loading.
181
+
182
+ Returns
183
+ -------
184
+ PatchyCementDryPressureModel
185
+ Loaded model instance.
186
+ """
187
+ with open(file, "rb") as f_in:
188
+ d = pickle.load(f_in)
189
+
190
+ return cls(
191
+ frac_cem=d["frac_cem"],
192
+ phi_c=d["phi_c"],
193
+ coord_num_func=d["coord_num_func"],
194
+ n=d["n"],
195
+ shear_red=d["shear_red"],
196
+ model_max_pressure=d["model_max_pressure"],
197
+ description=d["description"],
198
+ )
199
+
200
+
201
+ class PatchyCementDryShearModulusPressureModel(PatchyCementDryPressureModel):
202
+ """
203
+ Pressure sensitivity model using patchy cement dry rock properties.
204
+
205
+ This model combines friable and cemented sandstone behavior, representing
206
+ partially cemented sands with heterogeneous cement distribution.
207
+
208
+ Input format (n,9): [phi, k_min, mu_min, rho_min, k_cem, mu_cem, rho_cem, p_eff_in_situ, p_eff_depleted]
209
+
210
+ Where:
211
+ - phi: porosity [fraction]
212
+ - k_min: mineral bulk modulus [Pa]
213
+ - mu_min: mineral shear modulus [Pa]
214
+ - rho_min: mineral density [kg/m³]
215
+ - k_cem: cement bulk modulus [Pa]
216
+ - mu_cem: cement shear modulus [Pa]
217
+ - rho_cem: cement density [kg/m³]
218
+ - p_eff_in_situ: effective pressure in-situ [Pa]
219
+ - p_eff_depleted: effective pressure depleted [Pa]
220
+ """
221
+
222
+ def predict_abs(self, inp_arr: np.ndarray, case: str = "in_situ") -> np.ndarray:
223
+ """
224
+ Calculate absolute shear modulus for specified pressure case.
225
+
226
+ Parameters
227
+ ----------
228
+ inp_arr : np.ndarray
229
+ Validated input array (n,9).
230
+ case : str
231
+ Pressure case: "in_situ" or "depleted".
232
+
233
+ Returns
234
+ -------
235
+ np.ndarray
236
+ Shear modulus values [Pa].
237
+ """
238
+ k_dry, mu = self._compute_moduli(inp_arr, case)
239
+ return mu
240
+
241
+
242
+ class PatchyCementDryBulkModulusPressureModel(PatchyCementDryPressureModel):
243
+ """
244
+ Pressure sensitivity model using patchy cement dry rock properties.
245
+
246
+ This model combines friable and cemented sandstone behavior, representing
247
+ partially cemented sands with heterogeneous cement distribution.
248
+
249
+ Input format (n,9): [phi, k_min, mu_min, rho_min, k_cem, mu_cem, rho_cem, p_eff_in_situ, p_eff_depleted]
250
+
251
+ Where:
252
+ - phi: porosity [fraction]
253
+ - k_min: mineral bulk modulus [Pa]
254
+ - mu_min: mineral shear modulus [Pa]
255
+ - rho_min: mineral density [kg/m³]
256
+ - k_cem: cement bulk modulus [Pa]
257
+ - mu_cem: cement shear modulus [Pa]
258
+ - rho_cem: cement density [kg/m³]
259
+ - p_eff_in_situ: effective pressure in-situ [Pa]
260
+ - p_eff_depleted: effective pressure depleted [Pa]
261
+ """
262
+
263
+ def predict_abs(self, inp_arr: np.ndarray, case: str = "in_situ") -> np.ndarray:
264
+ """
265
+ Calculate absolute bulk modulus for specified pressure case.
266
+
267
+ Parameters
268
+ ----------
269
+ inp_arr : np.ndarray
270
+ Validated input array (n,9).
271
+ case : str
272
+ Pressure case: "in_situ" or "depleted".
273
+
274
+ Returns
275
+ -------
276
+ np.ndarray
277
+ Bulk modulus values [Pa].
278
+ """
279
+ k_dry, mu = self._compute_moduli(inp_arr, case)
280
+ return k_dry
@@ -0,0 +1,128 @@
1
+ from __future__ import annotations
2
+
3
+ import pickle
4
+ from typing import Any
5
+
6
+ import numpy as np
7
+
8
+ from .base_pressure_model import BasePressureModel
9
+
10
+
11
+ class PolynomialPressureModel(BasePressureModel):
12
+ """
13
+ Polynomial pressure sensitivity model for velocity prediction.
14
+
15
+ Uses polynomial function: v = v0 * P(p_depl) / P(p_in_situ)
16
+ where P(p) is a polynomial function with specified coefficients.
17
+
18
+ Input format (n,3): [velocity, p_eff_in_situ, p_eff_depleted]
19
+ """
20
+
21
+ def __init__(
22
+ self,
23
+ weights: list[float],
24
+ model_max_pressure: float | None = None,
25
+ description: str = "",
26
+ ):
27
+ """
28
+ Initialize polynomial pressure model.
29
+
30
+ Parameters
31
+ ----------
32
+ weights : list[float]
33
+ Polynomial coefficients [unitless]. First element is constant term,
34
+ second is linear coefficient, etc.
35
+ model_max_pressure : float | None
36
+ Maximum pressure for predict_max method [Pa].
37
+ description : str
38
+ Model description.
39
+ """
40
+ super().__init__(model_max_pressure, description)
41
+ self._weights = weights
42
+
43
+ @property
44
+ def weights(self) -> list[float]:
45
+ """Polynomial coefficients."""
46
+ return self._weights
47
+
48
+ def validate_input(self, inp_arr: np.ndarray) -> np.ndarray:
49
+ """
50
+ Validate input for polynomial model.
51
+
52
+ Parameters
53
+ ----------
54
+ inp_arr : np.ndarray
55
+ Input array to validate.
56
+
57
+ Returns
58
+ -------
59
+ np.ndarray
60
+ Validated input array.
61
+
62
+ Raises
63
+ ------
64
+ ValueError
65
+ If input format is invalid.
66
+ """
67
+ if not isinstance(inp_arr, np.ndarray):
68
+ raise ValueError("Input must be numpy ndarray.")
69
+ if inp_arr.ndim != 2 or inp_arr.shape[1] != 3:
70
+ raise ValueError(
71
+ "Input must be (n,3): [velocity, p_eff_in_situ, p_eff_depleted]"
72
+ )
73
+ return inp_arr
74
+
75
+ def predict_abs(self, inp_arr: np.ndarray, case: str = "in_situ") -> np.ndarray:
76
+ """
77
+ Calculate absolute velocity for specified pressure case.
78
+
79
+ Parameters
80
+ ----------
81
+ inp_arr : np.ndarray
82
+ Validated input array (n,3).
83
+ case : str
84
+ Pressure case: "in_situ" or "depleted".
85
+
86
+ Returns
87
+ -------
88
+ np.ndarray
89
+ Velocity values [m/s].
90
+ """
91
+ arr = self.validate_input(inp_arr)
92
+
93
+ # Validate weights are set
94
+ if not self._weights:
95
+ raise ValueError('Field "weights" is not set.')
96
+
97
+ vel = arr[:, 0]
98
+ p_in_situ = arr[:, 1]
99
+ p_depleted = arr[:, 2]
100
+
101
+ # Create polynomial from weights
102
+ polynomial_expr = np.polynomial.Polynomial(self._weights)
103
+
104
+ # Select pressure based on case
105
+ p_eff = p_in_situ if case == "in_situ" else p_depleted
106
+
107
+ # Calculate velocity using polynomial pressure correction
108
+ return vel * polynomial_expr(p_eff) / polynomial_expr(p_in_situ)
109
+
110
+ def todict(self) -> dict[str, Any]:
111
+ """Convert model to dictionary."""
112
+ return {
113
+ "weights": self._weights,
114
+ "model_max_pressure": self._model_max_pressure,
115
+ "description": self._description,
116
+ }
117
+
118
+ @classmethod
119
+ def load(cls, file: str | bytes) -> "PolynomialPressureModel":
120
+ """Load polynomial model from pickle file."""
121
+ with open(file, "rb") as f_in:
122
+ d = pickle.load(f_in)
123
+
124
+ return cls(
125
+ weights=d["weights"],
126
+ model_max_pressure=d["model_max_pressure"],
127
+ description=d["description"],
128
+ )