fmu-pem 0.0.1__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 (37) hide show
  1. fmu/__init__.py +2 -0
  2. fmu/pem/__init__.py +19 -0
  3. fmu/pem/__main__.py +53 -0
  4. fmu/pem/forward_models/__init__.py +7 -0
  5. fmu/pem/forward_models/pem_model.py +72 -0
  6. fmu/pem/hook_implementations/__init__.py +0 -0
  7. fmu/pem/hook_implementations/jobs.py +19 -0
  8. fmu/pem/pem_functions/__init__.py +17 -0
  9. fmu/pem/pem_functions/density.py +55 -0
  10. fmu/pem/pem_functions/effective_pressure.py +168 -0
  11. fmu/pem/pem_functions/estimate_saturated_rock.py +90 -0
  12. fmu/pem/pem_functions/fluid_properties.py +281 -0
  13. fmu/pem/pem_functions/mineral_properties.py +230 -0
  14. fmu/pem/pem_functions/regression_models.py +261 -0
  15. fmu/pem/pem_functions/run_friable_model.py +119 -0
  16. fmu/pem/pem_functions/run_patchy_cement_model.py +120 -0
  17. fmu/pem/pem_functions/run_t_matrix_and_pressure.py +186 -0
  18. fmu/pem/pem_utilities/__init__.py +66 -0
  19. fmu/pem/pem_utilities/cumsum_properties.py +104 -0
  20. fmu/pem/pem_utilities/delta_cumsum_time.py +104 -0
  21. fmu/pem/pem_utilities/enum_defs.py +54 -0
  22. fmu/pem/pem_utilities/export_routines.py +272 -0
  23. fmu/pem/pem_utilities/import_config.py +93 -0
  24. fmu/pem/pem_utilities/import_routines.py +161 -0
  25. fmu/pem/pem_utilities/pem_class_definitions.py +113 -0
  26. fmu/pem/pem_utilities/pem_config_validation.py +505 -0
  27. fmu/pem/pem_utilities/rpm_models.py +177 -0
  28. fmu/pem/pem_utilities/update_grid.py +54 -0
  29. fmu/pem/pem_utilities/utils.py +262 -0
  30. fmu/pem/run_pem.py +98 -0
  31. fmu/pem/version.py +21 -0
  32. fmu_pem-0.0.1.dist-info/METADATA +768 -0
  33. fmu_pem-0.0.1.dist-info/RECORD +37 -0
  34. fmu_pem-0.0.1.dist-info/WHEEL +5 -0
  35. fmu_pem-0.0.1.dist-info/entry_points.txt +5 -0
  36. fmu_pem-0.0.1.dist-info/licenses/LICENSE +674 -0
  37. fmu_pem-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,261 @@
1
+ """Regression models for saturated sandstones
2
+
3
+ Regression based on polynomial models for saturated sandstones. The models are
4
+ based on porosity polynomials to estimate either Vp and Vs, or K and Mu.
5
+ """
6
+
7
+ from pathlib import Path
8
+ from typing import List, Union
9
+
10
+ import numpy as np
11
+ import numpy.typing as npt
12
+ from rock_physics_open.equinor_utilities.std_functions import (
13
+ gassmann,
14
+ hashin_shtrikman_average,
15
+ moduli,
16
+ velocity,
17
+ voigt_reuss_hill,
18
+ )
19
+ from rock_physics_open.t_matrix_models import carbonate_pressure_model
20
+
21
+ from fmu.pem.pem_utilities import (
22
+ DryRockProperties,
23
+ EffectiveFluidProperties,
24
+ MatrixProperties,
25
+ PemConfig,
26
+ PressureProperties,
27
+ SaturatedRockProperties,
28
+ filter_and_one_dim,
29
+ reverse_filter_and_restore,
30
+ )
31
+ from fmu.pem.pem_utilities.enum_defs import MineralMixModel
32
+ from fmu.pem.pem_utilities.rpm_models import (
33
+ KMuRegressionParams,
34
+ VpVsRegressionParams,
35
+ )
36
+
37
+
38
+ def gen_regression(
39
+ porosity: np.ndarray, polynom_weights: list | np.ndarray
40
+ ) -> np.ndarray:
41
+ """Regression model for Vp
42
+
43
+ Args:
44
+ porosity: porosity [fraction]
45
+ polynom_weights: weights for the polynomial regression model
46
+
47
+ Returns:
48
+ Vp [m/s]
49
+ """
50
+ pol = np.polynomial.Polynomial(polynom_weights)
51
+ return pol(porosity)
52
+
53
+
54
+ def dry_rock_regression(
55
+ porosity: npt.NDArray[np.float64] | np.ma.MaskedArray,
56
+ rho_min: npt.NDArray[np.float64] | np.ma.MaskedArray,
57
+ params: VpVsRegressionParams | KMuRegressionParams,
58
+ ) -> DryRockProperties:
59
+ """
60
+ Calculates dry rock properties based on porosity and polynomial weights.
61
+
62
+ Args:
63
+ porosity: An array of porosity values.
64
+ rho_min: An array of mineral density values.
65
+ params: PemConfig object containing the regression model parameters.
66
+
67
+ Returns:
68
+ A dictionary with keys corresponding to the calculated properties and
69
+ their values.
70
+
71
+ Raises:
72
+ ValueError: If an invalid mode is provided or necessary weights for
73
+ the selected mode are missing.
74
+ """
75
+ if not params.rho_regression:
76
+ # rho_regression = False, use mineral density
77
+ rho_dry = rho_min * (1 - porosity)
78
+ else:
79
+ rho_dry = gen_regression(porosity, params.rho_weights)
80
+
81
+ if (
82
+ params.mode == "vp_vs"
83
+ and params.vp_weights is not None
84
+ and params.vs_weights is not None
85
+ ):
86
+ vp_dry = gen_regression(porosity, params.vp_weights)
87
+ vs_dry = gen_regression(porosity, params.vs_weights)
88
+ k_dry, mu = moduli(vp_dry, vs_dry, rho_dry)
89
+ elif (
90
+ params.mode == "k_mu"
91
+ and params.k_weights is not None
92
+ and params.mu_weights is not None
93
+ ):
94
+ k_dry = gen_regression(porosity, params.k_weights)
95
+ mu = gen_regression(porosity, params.mu_weights)
96
+ else:
97
+ raise ValueError("Invalid mode or missing weights for the selected mode.")
98
+
99
+ return DryRockProperties(
100
+ bulk_modulus=np.ma.MaskedArray(k_dry),
101
+ shear_modulus=np.ma.MaskedArray(mu),
102
+ dens=np.ma.MaskedArray(rho_dry),
103
+ )
104
+
105
+
106
+ def run_regression_models(
107
+ mineral: MatrixProperties,
108
+ fluid_properties: list[EffectiveFluidProperties],
109
+ porosity: np.ma.MaskedArray,
110
+ pressure: list[PressureProperties],
111
+ config: PemConfig,
112
+ vsh: Union[np.ma.MaskedArray, None] = None,
113
+ pres_model_vp: Path = Path("carbonate_pressure_model_vp_exp.pkl"),
114
+ pres_model_vs: Path = Path("carbonate_pressure_model_vs_exp.pkl"),
115
+ ) -> List[SaturatedRockProperties]:
116
+ """Run regression models for saturated rock properties.
117
+
118
+ Args:
119
+ mineral: Mineral properties containing bulk modulus (k) [Pa],
120
+ shear modulus (mu) [Pa] and density (rho_sat) [kg/m3]
121
+ fluid_properties: List of fluid properties,
122
+ each containing bulk modulus (k) [Pa] and density (rho_sat) [kg/m3]
123
+ porosity: Porosity as a masked array [fraction]
124
+ pressure: List of pressure properties containing effective pressure values
125
+ [bar] following Eclipse reservoir simulator convention
126
+ config: Parameters for the PEM
127
+ vsh: Volume of shale as a masked array [fraction], optional
128
+ pres_model_vp: Path to the pressure sensitivity model file for P-wave velocity
129
+ (default: "carbonate_pressure_model_vp_exp_2023.pkl")
130
+ pres_model_vs: Path to the pressure sensitivity model file for S-wave velocity
131
+ (default: "carbonate_pressure_model_vs_exp_2023.pkl")
132
+
133
+
134
+ Returns:
135
+ List[SaturatedRockProperties]: Saturated rock properties for each time step.
136
+ Only fluid properties change between time steps in this model.
137
+ """
138
+
139
+ saturated_props = []
140
+ tmp_pres_over = None
141
+ tmp_pres_form = None
142
+ tmp_pres_depl = None
143
+
144
+ if vsh is None:
145
+ vsh = np.ma.MaskedArray(
146
+ data=np.zeros_like(porosity), mask=np.zeros_like(porosity).astype(bool)
147
+ )
148
+ multiple_lithologies = False
149
+ else:
150
+ multiple_lithologies = True
151
+ # Convert pressure from bar to Pa
152
+ pres_ovb = pressure[0].overburden_pressure * 1.0e5
153
+ pres_form = pressure[0].formation_pressure * 1.0e5
154
+ for time_step, fl_prop in enumerate(fluid_properties):
155
+ if time_step > 0 and config.rock_matrix.pressure_sensitivity:
156
+ pres_depl = pressure[time_step].formation_pressure * 1.0e5
157
+ (
158
+ mask,
159
+ tmp_min_k,
160
+ tmp_min_mu,
161
+ tmp_min_rho,
162
+ tmp_fl_prop_k,
163
+ tmp_fl_prop_rho,
164
+ tmp_por,
165
+ tmp_vsh,
166
+ tmp_pres_over,
167
+ tmp_pres_form,
168
+ tmp_pres_depl,
169
+ ) = filter_and_one_dim(
170
+ mineral.bulk_modulus,
171
+ mineral.shear_modulus,
172
+ mineral.dens,
173
+ fl_prop.bulk_modulus,
174
+ fl_prop.dens,
175
+ porosity,
176
+ vsh,
177
+ pres_ovb,
178
+ pres_form,
179
+ pres_depl,
180
+ return_numpy_array=True,
181
+ )
182
+ else:
183
+ (
184
+ mask,
185
+ tmp_min_k,
186
+ tmp_min_mu,
187
+ tmp_min_rho,
188
+ tmp_fl_prop_k,
189
+ tmp_fl_prop_rho,
190
+ tmp_por,
191
+ tmp_vsh,
192
+ ) = filter_and_one_dim(
193
+ mineral.bulk_modulus,
194
+ mineral.shear_modulus,
195
+ mineral.dens,
196
+ fl_prop.bulk_modulus,
197
+ fl_prop.dens,
198
+ porosity,
199
+ vsh,
200
+ )
201
+ if not multiple_lithologies:
202
+ dry_props = dry_rock_regression(
203
+ tmp_por, tmp_min_rho, config.rock_matrix.rpm.parameters.sandstone
204
+ )
205
+ else:
206
+ dry_props_sand = dry_rock_regression(
207
+ tmp_por, tmp_min_rho, config.rock_matrix.rpm.parameters.sandstone
208
+ )
209
+ dry_props_shale = dry_rock_regression(
210
+ tmp_por, tmp_min_rho, config.rock_matrix.rpm.parameters.shale
211
+ )
212
+ dry_rho = (
213
+ dry_props_sand.dens * (1.0 - tmp_vsh) + dry_props_shale.dens * tmp_vsh
214
+ )
215
+ if config.rock_matrix.mineral_mix_model == MineralMixModel.HASHIN_SHTRIKMAN:
216
+ k_dry, mu = hashin_shtrikman_average(
217
+ dry_props_sand.bulk_modulus,
218
+ dry_props_shale.bulk_modulus,
219
+ dry_props_sand.shear_modulus,
220
+ dry_props_shale.shear_modulus,
221
+ (1.0 - tmp_vsh),
222
+ )
223
+ else:
224
+ k_dry, mu = voigt_reuss_hill(
225
+ dry_props_sand.bulk_modulus,
226
+ dry_props_shale.bulk_modulus,
227
+ dry_props_sand.shear_modulus,
228
+ dry_props_shale.shear_modulus,
229
+ (1.0 - tmp_vsh),
230
+ )
231
+ dry_props = DryRockProperties(
232
+ bulk_modulus=k_dry, shear_modulus=mu, dens=dry_rho
233
+ )
234
+
235
+ k_sat = gassmann(dry_props.bulk_modulus, tmp_por, tmp_fl_prop_k, tmp_min_k)
236
+ rho_sat = dry_props.dens + tmp_por * tmp_fl_prop_rho
237
+ vp, vs = velocity(k_sat, dry_props.shear_modulus, rho_sat)[0:2]
238
+ if time_step > 0 and config.rock_matrix.pressure_sensitivity:
239
+ # Inputs must be numpy arrays, not masked arrays
240
+ vp, vs, rho_sat, _, _ = carbonate_pressure_model(
241
+ tmp_fl_prop_rho,
242
+ vp.data,
243
+ vs.data,
244
+ rho_sat,
245
+ vp.data,
246
+ vs.data,
247
+ rho_sat,
248
+ tmp_por,
249
+ tmp_pres_over,
250
+ tmp_pres_form,
251
+ tmp_pres_depl,
252
+ pres_model_vp,
253
+ pres_model_vs,
254
+ config.paths.rel_path_pem.absolute(),
255
+ False,
256
+ )
257
+ vp, vs, rho_sat = reverse_filter_and_restore(mask, vp, vs, rho_sat)
258
+ props = SaturatedRockProperties(vp=vp, vs=vs, dens=rho_sat)
259
+ saturated_props.append(props)
260
+
261
+ return saturated_props
@@ -0,0 +1,119 @@
1
+ from dataclasses import asdict
2
+ from typing import List, Union
3
+
4
+ import numpy as np
5
+ from rock_physics_open.sandstone_models import friable_model
6
+
7
+ from fmu.pem.pem_utilities import (
8
+ EffectiveFluidProperties,
9
+ MatrixProperties,
10
+ PemConfig,
11
+ PressureProperties,
12
+ SaturatedRockProperties,
13
+ filter_and_one_dim,
14
+ reverse_filter_and_restore,
15
+ )
16
+
17
+
18
+ def run_friable(
19
+ mineral: MatrixProperties,
20
+ fluid: Union[list[EffectiveFluidProperties], EffectiveFluidProperties],
21
+ porosity: np.ma.MaskedArray,
22
+ pressure: Union[list[PressureProperties], PressureProperties],
23
+ config: PemConfig,
24
+ ) -> List[SaturatedRockProperties]:
25
+ """
26
+ Prepare inputs and parameters for running the Friable sandstone model
27
+
28
+ Args:
29
+ mineral: mineral properties containing k [Pa], mu [Pa] and rho [kg/m3]
30
+ fluid: fluid properties containing k [Pa] and rho [kg/m3], can be several fluid
31
+ properties in a list
32
+ porosity: porosity fraction
33
+ pressure: steps in effective pressure in [bar] due to Eclipse standard
34
+ config: parameters for the PEM
35
+
36
+ Returns:
37
+ saturated rock properties with vp [m/s], vs [m/s], density [kg/m^3], ai
38
+ (vp * density), si (vs * density), vpvs (vp / vs)
39
+ """
40
+ # Mineral and porosity are assumed to be single objects, fluid and
41
+ # effective_pressure can be lists
42
+ fluid, pressure = _verify_inputs(fluid, pressure)
43
+ saturated_props = []
44
+ friable_params = config.rock_matrix.rpm.parameters
45
+ for fl_prop, pres in zip(fluid, pressure):
46
+ (
47
+ mask,
48
+ tmp_min_k,
49
+ tmp_min_mu,
50
+ tmp_min_rho,
51
+ tmp_fl_prop_k,
52
+ tmp_fl_prop_rho,
53
+ tmp_por,
54
+ tmp_pres,
55
+ ) = filter_and_one_dim(
56
+ mineral.bulk_modulus,
57
+ mineral.shear_modulus,
58
+ mineral.dens,
59
+ fl_prop.bulk_modulus,
60
+ fl_prop.dens,
61
+ porosity,
62
+ pres.effective_pressure * 1.0e5,
63
+ )
64
+ """Estimation of effective mineral properties must be able to handle cases where
65
+ there is a more complex combination of minerals than the standard sand/shale
66
+ case. For carbonates the input can be based on minerals (e.g. calcite,
67
+ dolomite, quartz, smectite, ...) or PRTs (petrophysical rock types) that each
68
+ have been assigned elastic properties to."""
69
+ vp, vs, rho, _, _ = friable_model(
70
+ tmp_min_k,
71
+ tmp_min_mu,
72
+ tmp_min_rho,
73
+ tmp_fl_prop_k,
74
+ tmp_fl_prop_rho,
75
+ tmp_por,
76
+ tmp_pres,
77
+ friable_params.critical_porosity,
78
+ friable_params.coord_num_function,
79
+ friable_params.coordination_number,
80
+ friable_params.shear_reduction,
81
+ )
82
+ vp, vs, rho = reverse_filter_and_restore(mask, vp, vs, rho)
83
+ props = SaturatedRockProperties(vp=vp, vs=vs, dens=rho)
84
+ saturated_props.append(props)
85
+ return saturated_props
86
+
87
+
88
+ def _verify_inputs(fl_prop, pres_prop):
89
+ # Ensure that properties of input objects are masked arrays
90
+ def check_masked_arrays(inp_obj):
91
+ for value in inp_obj.values():
92
+ if not isinstance(value, np.ma.MaskedArray):
93
+ raise ValueError("Expected all input object values to be masked arrays")
94
+
95
+ if isinstance(fl_prop, list) and isinstance(pres_prop, list):
96
+ if not len(fl_prop) == len(pres_prop):
97
+ raise ValueError(
98
+ f"{__file__}: unequal steps in fluid properties and pressure: "
99
+ f"{len(fl_prop)} vs. {len(pres_prop)}"
100
+ )
101
+ for item in fl_prop + pres_prop:
102
+ check_masked_arrays(asdict(item))
103
+ return fl_prop, pres_prop
104
+ if isinstance(fl_prop, EffectiveFluidProperties) and isinstance(
105
+ pres_prop, PressureProperties
106
+ ):
107
+ check_masked_arrays(asdict(fl_prop))
108
+ check_masked_arrays(asdict(pres_prop))
109
+ return [
110
+ fl_prop,
111
+ ], [
112
+ pres_prop,
113
+ ]
114
+ # else:
115
+ raise ValueError(
116
+ f"{__file__}: mismatch between fluid and pressure objects, both should "
117
+ f"either be lists or class objects, are {type(fl_prop)} and "
118
+ f"{type(pres_prop)}"
119
+ )
@@ -0,0 +1,120 @@
1
+ from typing import List, Union
2
+
3
+ import numpy as np
4
+ from rock_physics_open.sandstone_models import (
5
+ patchy_cement_model_cem_frac as patchy_cement,
6
+ )
7
+
8
+ from fmu.pem.pem_utilities import (
9
+ EffectiveFluidProperties,
10
+ MatrixProperties,
11
+ PemConfig,
12
+ PressureProperties,
13
+ SaturatedRockProperties,
14
+ filter_and_one_dim,
15
+ reverse_filter_and_restore,
16
+ )
17
+
18
+
19
+ def run_patchy_cement(
20
+ mineral: MatrixProperties,
21
+ fluid: Union[list[EffectiveFluidProperties], EffectiveFluidProperties],
22
+ cement: MatrixProperties,
23
+ porosity: np.ma.MaskedArray,
24
+ pressure: Union[list[PressureProperties], PressureProperties],
25
+ config: PemConfig,
26
+ ) -> List[SaturatedRockProperties]:
27
+ """Prepare inputs and parameters for running the Patchy Cement model
28
+
29
+ Args:
30
+ mineral: mineral properties containing k [Pa], mu [Pa] and rho [kg/m3]
31
+ fluid: fluid properties containing k [Pa] and rho [kg/m3], can be several fluid
32
+ properties in a list
33
+ cement: cement properties containing k [Pa], mu [Pa] and rho [kg/m3]
34
+ porosity: porosity fraction
35
+ pressure: steps in effective pressure in [bar] due to Eclipse standard
36
+ config: parameters for the PEM
37
+
38
+ Returns:
39
+ saturated rock properties with vp [m/s], vs [m/s], density [kg/m^3], ai
40
+ (vp * density), si (vs * density), vpvs (vp / vs)
41
+ """
42
+ # Mineral and porosity are assumed to be single objects, fluid and
43
+ # effective_pressure can be lists
44
+ fluid, pressure = _verify_inputs(fluid, pressure)
45
+ saturated_props = []
46
+ pat_cem_params = config.rock_matrix.rpm.parameters
47
+ for fl_prop, pres in zip(fluid, pressure):
48
+ (
49
+ mask,
50
+ tmp_min_k,
51
+ tmp_min_mu,
52
+ tmp_min_rho,
53
+ tmp_cem_k,
54
+ tmp_cem_mu,
55
+ tmp_cem_rho,
56
+ tmp_fl_prop_k,
57
+ tmp_fl_prop_rho,
58
+ tmp_por,
59
+ tmp_pres,
60
+ ) = filter_and_one_dim(
61
+ mineral.bulk_modulus,
62
+ mineral.shear_modulus,
63
+ mineral.dens,
64
+ cement.bulk_modulus,
65
+ cement.shear_modulus,
66
+ cement.dens,
67
+ fl_prop.bulk_modulus,
68
+ fl_prop.dens,
69
+ porosity,
70
+ pres.effective_pressure * 1.0e5,
71
+ return_numpy_array=True,
72
+ )
73
+ """Estimation of effective mineral properties must be able to handle cases where
74
+ there is a more complex combination of minerals than the standard sand/shale
75
+ case. For carbonates the input can be based on minerals (e.g. calcite,
76
+ dolomite, quartz, smectite, ...) or PRTs (petrophysical rock types) that each
77
+ have been assigned elastic properties to."""
78
+ vp, vs, rho, _, _ = patchy_cement(
79
+ tmp_min_k,
80
+ tmp_min_mu,
81
+ tmp_min_rho,
82
+ tmp_cem_k,
83
+ tmp_cem_mu,
84
+ tmp_cem_rho,
85
+ tmp_fl_prop_k,
86
+ tmp_fl_prop_rho,
87
+ tmp_por,
88
+ tmp_pres,
89
+ pat_cem_params.cement_fraction,
90
+ pat_cem_params.critical_porosity,
91
+ pat_cem_params.coord_num_function,
92
+ pat_cem_params.coordination_number,
93
+ pat_cem_params.shear_reduction,
94
+ )
95
+ vp, vs, rho = reverse_filter_and_restore(mask, vp, vs, rho)
96
+ props = SaturatedRockProperties(vp=vp, vs=vs, dens=rho)
97
+ saturated_props.append(props)
98
+ return saturated_props
99
+
100
+
101
+ def _verify_inputs(fl_prop, pres_prop):
102
+ if isinstance(fl_prop, list) and isinstance(pres_prop, list):
103
+ if not len(fl_prop) == len(pres_prop):
104
+ raise ValueError(
105
+ f"{__file__}: unequal steps in fluid properties and pressure: "
106
+ f"{len(fl_prop)} vs. {len(pres_prop)}"
107
+ )
108
+ return fl_prop, pres_prop
109
+ if isinstance(fl_prop, EffectiveFluidProperties) and (
110
+ isinstance(pres_prop, PressureProperties)
111
+ ):
112
+ return [
113
+ fl_prop,
114
+ ], [
115
+ pres_prop,
116
+ ]
117
+ raise ValueError(
118
+ f"{__file__}: mismatch between fluid and pressure objects, both should either "
119
+ f"be lists or single objects, are {type(fl_prop)} and {type(pres_prop)}"
120
+ )
@@ -0,0 +1,186 @@
1
+ from pathlib import Path
2
+ from typing import List, Union
3
+
4
+ import numpy as np
5
+ from rock_physics_open.t_matrix_models import (
6
+ carbonate_pressure_model,
7
+ run_t_matrix_forward_model_with_opt_params_exp,
8
+ run_t_matrix_forward_model_with_opt_params_petec,
9
+ )
10
+
11
+ from fmu.pem.pem_utilities import (
12
+ EffectiveFluidProperties,
13
+ MatrixProperties,
14
+ PemConfig,
15
+ PressureProperties,
16
+ SaturatedRockProperties,
17
+ filter_and_one_dim,
18
+ reverse_filter_and_restore,
19
+ )
20
+
21
+ from .run_patchy_cement_model import _verify_inputs
22
+
23
+
24
+ def run_t_matrix_and_pressure_models(
25
+ mineral_properties: MatrixProperties,
26
+ fluid_properties: Union[list[EffectiveFluidProperties], EffectiveFluidProperties],
27
+ porosity: np.ma.MaskedArray,
28
+ vsh: Union[None, np.ma.MaskedArray],
29
+ pressure: Union[list[PressureProperties], PressureProperties],
30
+ config: PemConfig,
31
+ petec_parameter_file: Path = Path("t_mat_params_petec.pkl"),
32
+ exp_parameter_file: Path = Path("t_mat_params_exp.pkl"),
33
+ pres_model_vp: Path = Path("carbonate_pressure_model_vp_exp.pkl"),
34
+ pres_model_vs: Path = Path("carbonate_pressure_model_vs_exp.pkl"),
35
+ ) -> List[SaturatedRockProperties]:
36
+ """Model for carbonate rock with possibility to estimate changes due to
37
+ production, i.e., saturation changes, changes in the fluids due to pore pressure
38
+ change and also changes in the properties of the matrix by increase in effective
39
+ pressure.
40
+
41
+ The first timestep is regarded as in situ conditions for the pressure
42
+ substitution, any later timestep also takes into account the changes in effective
43
+ pressure from the initial one.
44
+
45
+ Calibration of parameters for the T-Matrix model must be made before running the
46
+ forward model, this is only possible in RokDoc with 1D match to logs.
47
+
48
+ Args:
49
+ mineral_properties: bulk modulus (k) [Pa], shear modulus (mu) [Pa],
50
+ density [kg/m^3]
51
+ fluid_properties: bulk modulus (k) [Pa] and density [kg/m^3] for each restart
52
+ date
53
+ porosity: porosity [fraction]
54
+ vsh: shale volume [fraction]
55
+ pressure: overburden, effective and formation (pore) pressure per restart date
56
+ config: configuration parameters
57
+ petec_parameter_file: additional parameters for the T-Matrix model, determined
58
+ through optimisation to well logs
59
+ exp_parameter_file: additional parameters for the T-Matrix model, determined
60
+ through optimisation to well logs
61
+ pres_model_vp: pressure sensitivity model for vp
62
+ pres_model_vs: pressure sensitivity model for vs
63
+
64
+ Returns:
65
+ saturated rock properties with vp [m/s], vs [m/s], density [kg/m^3],
66
+ ai (vp * density), si (vs * density), vpvs (vp / vs)
67
+ """
68
+ fluid_properties, pressure = _verify_inputs(fluid_properties, pressure)
69
+
70
+ # All model files are gathered with the config file for the PEM model,
71
+ # i.e. in ../sim2seis/model
72
+ petec_parameter_file = config.paths.rel_path_pem / petec_parameter_file
73
+ exp_parameter_file = config.paths.rel_path_pem / exp_parameter_file
74
+
75
+ saturated_props = []
76
+ t_mat_params = config.rock_matrix.rpm.parameters
77
+
78
+ if vsh is None and t_mat_params.t_mat_model_version == "EXP":
79
+ raise ValueError("Shale volume must be provided for EXP model")
80
+ if vsh is None:
81
+ # For simplicity in the filtering, if no shale volume is provided, it is set
82
+ # to zero
83
+ vsh = np.ma.array(
84
+ np.zeros_like(porosity), mask=np.zeros_like(porosity).astype(bool)
85
+ )
86
+ # Change unit from bar to Pa
87
+ pres_ovb = pressure[0].overburden_pressure * 1.0e5
88
+ pres_form = pressure[0].formation_pressure * 1.0e5
89
+
90
+ for time_step, fl_prop in enumerate(fluid_properties):
91
+ if time_step > 0:
92
+ pres_depl = pressure[time_step].formation_pressure * 1.0e5
93
+ (
94
+ mask,
95
+ tmp_min_k,
96
+ tmp_min_mu,
97
+ tmp_min_rho,
98
+ tmp_fl_k,
99
+ tmp_fl_rho,
100
+ tmp_por,
101
+ tmp_vsh,
102
+ tmp_pres_over,
103
+ tmp_pres_form,
104
+ tmp_pres_depl,
105
+ ) = filter_and_one_dim(
106
+ mineral_properties.bulk_modulus,
107
+ mineral_properties.shear_modulus,
108
+ mineral_properties.dens,
109
+ fl_prop.bulk_modulus,
110
+ fl_prop.dens,
111
+ porosity,
112
+ vsh,
113
+ pres_ovb,
114
+ pres_form,
115
+ pres_depl,
116
+ )
117
+ else:
118
+ tmp_pres_over, tmp_pres_form, tmp_pres_depl = (None, None, None)
119
+ (
120
+ mask,
121
+ tmp_min_k,
122
+ tmp_min_mu,
123
+ tmp_min_rho,
124
+ tmp_fl_k,
125
+ tmp_fl_rho,
126
+ tmp_por,
127
+ tmp_vsh,
128
+ ) = filter_and_one_dim(
129
+ mineral_properties.bulk_modulus,
130
+ mineral_properties.shear_modulus,
131
+ mineral_properties.dens,
132
+ fl_prop.bulk_modulus,
133
+ fl_prop.dens,
134
+ porosity,
135
+ vsh,
136
+ )
137
+ if t_mat_params.t_mat_model_version == "PETEC":
138
+ vp, vs, rho, k, mu = run_t_matrix_forward_model_with_opt_params_petec(
139
+ tmp_min_k,
140
+ tmp_min_mu,
141
+ tmp_min_rho,
142
+ tmp_fl_k,
143
+ tmp_fl_rho,
144
+ tmp_por,
145
+ t_mat_params.angle,
146
+ t_mat_params.perm,
147
+ t_mat_params.visco,
148
+ t_mat_params.tau,
149
+ t_mat_params.freq,
150
+ str(petec_parameter_file),
151
+ )
152
+ else:
153
+ vp, vs, rho, k, mu = run_t_matrix_forward_model_with_opt_params_exp(
154
+ tmp_fl_k,
155
+ tmp_fl_rho,
156
+ tmp_por,
157
+ tmp_vsh,
158
+ t_mat_params.angle,
159
+ t_mat_params.perm,
160
+ t_mat_params.visco,
161
+ t_mat_params.tau,
162
+ t_mat_params.freq,
163
+ str(exp_parameter_file),
164
+ )
165
+ if time_step > 0:
166
+ vp, vs, rho, _, _ = carbonate_pressure_model(
167
+ tmp_fl_rho,
168
+ vp,
169
+ vs,
170
+ rho,
171
+ vp,
172
+ vs,
173
+ rho,
174
+ tmp_por,
175
+ tmp_pres_over,
176
+ tmp_pres_form,
177
+ tmp_pres_depl,
178
+ pres_model_vp,
179
+ pres_model_vs,
180
+ config.paths.rel_path_pem.absolute(),
181
+ False,
182
+ )
183
+ vp, vs, rho = reverse_filter_and_restore(mask, vp, vs, rho)
184
+ props = SaturatedRockProperties(vp=vp, vs=vs, dens=rho)
185
+ saturated_props.append(props)
186
+ return saturated_props