rock-physics-open 0.2.1__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.
- rock_physics_open/equinor_utilities/gen_utilities/dict_to_float.py +6 -1
- rock_physics_open/equinor_utilities/gen_utilities/dim_check_vector.py +35 -5
- rock_physics_open/equinor_utilities/gen_utilities/filter_input.py +11 -6
- rock_physics_open/equinor_utilities/gen_utilities/filter_output.py +29 -19
- rock_physics_open/equinor_utilities/machine_learning_utilities/__init__.py +18 -5
- rock_physics_open/equinor_utilities/machine_learning_utilities/base_pressure_model.py +172 -0
- rock_physics_open/equinor_utilities/machine_learning_utilities/exponential_model.py +100 -86
- rock_physics_open/equinor_utilities/machine_learning_utilities/friable_pressure_models.py +230 -0
- rock_physics_open/equinor_utilities/machine_learning_utilities/import_ml_models.py +23 -4
- rock_physics_open/equinor_utilities/machine_learning_utilities/patchy_cement_pressure_models.py +280 -0
- rock_physics_open/equinor_utilities/machine_learning_utilities/polynomial_model.py +128 -0
- rock_physics_open/equinor_utilities/machine_learning_utilities/sigmoidal_model.py +204 -155
- rock_physics_open/equinor_utilities/optimisation_utilities/__init__.py +19 -0
- rock_physics_open/equinor_utilities/snapshot_test_utilities/compare_snapshots.py +16 -8
- rock_physics_open/equinor_utilities/snapshot_test_utilities/snapshots.py +33 -10
- rock_physics_open/fluid_models/brine_model/brine_properties.py +70 -35
- rock_physics_open/fluid_models/gas_model/gas_properties.py +79 -37
- rock_physics_open/fluid_models/oil_model/dead_oil_density.py +21 -16
- rock_physics_open/fluid_models/oil_model/dead_oil_velocity.py +9 -7
- rock_physics_open/fluid_models/oil_model/live_oil_density.py +16 -13
- rock_physics_open/fluid_models/oil_model/live_oil_velocity.py +3 -3
- rock_physics_open/fluid_models/oil_model/oil_properties.py +59 -29
- rock_physics_open/sandstone_models/__init__.py +2 -0
- rock_physics_open/sandstone_models/constant_cement_optimisation.py +4 -1
- rock_physics_open/sandstone_models/friable_optimisation.py +4 -1
- rock_physics_open/sandstone_models/patchy_cement_model.py +89 -6
- rock_physics_open/sandstone_models/patchy_cement_optimisation.py +4 -1
- rock_physics_open/t_matrix_models/__init__.py +0 -10
- rock_physics_open/t_matrix_models/carbonate_pressure_substitution.py +1 -1
- rock_physics_open/t_matrix_models/curvefit_t_matrix_exp.py +1 -2
- rock_physics_open/t_matrix_models/t_matrix_opt_fluid_sub_exp.py +3 -3
- rock_physics_open/t_matrix_models/t_matrix_opt_fluid_sub_petec.py +5 -1
- rock_physics_open/t_matrix_models/t_matrix_opt_forward_model_exp.py +5 -1
- rock_physics_open/t_matrix_models/t_matrix_opt_forward_model_min.py +4 -1
- rock_physics_open/t_matrix_models/t_matrix_parameter_optimisation_exp.py +5 -1
- rock_physics_open/t_matrix_models/t_matrix_parameter_optimisation_min.py +4 -1
- rock_physics_open/version.py +16 -3
- {rock_physics_open-0.2.1.dist-info → rock_physics_open-0.3.0.dist-info}/METADATA +4 -8
- {rock_physics_open-0.2.1.dist-info → rock_physics_open-0.3.0.dist-info}/RECORD +43 -38
- /rock_physics_open/{t_matrix_models → equinor_utilities/optimisation_utilities}/opt_subst_utilities.py +0 -0
- {rock_physics_open-0.2.1.dist-info → rock_physics_open-0.3.0.dist-info}/WHEEL +0 -0
- {rock_physics_open-0.2.1.dist-info → rock_physics_open-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {rock_physics_open-0.2.1.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
|
|
2
|
-
from .
|
|
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 =
|
|
45
|
+
models = SigmoidalPressureModel.load(mod_dict["nn_mod"])
|
|
37
46
|
elif mod_dict["model_type"] == "Exponential":
|
|
38
|
-
models =
|
|
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
|
|
rock_physics_open/equinor_utilities/machine_learning_utilities/patchy_cement_pressure_models.py
ADDED
|
@@ -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
|
+
)
|