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.
- fmu/__init__.py +2 -0
- fmu/pem/__init__.py +19 -0
- fmu/pem/__main__.py +53 -0
- fmu/pem/forward_models/__init__.py +7 -0
- fmu/pem/forward_models/pem_model.py +72 -0
- fmu/pem/hook_implementations/__init__.py +0 -0
- fmu/pem/hook_implementations/jobs.py +19 -0
- fmu/pem/pem_functions/__init__.py +17 -0
- fmu/pem/pem_functions/density.py +55 -0
- fmu/pem/pem_functions/effective_pressure.py +168 -0
- fmu/pem/pem_functions/estimate_saturated_rock.py +90 -0
- fmu/pem/pem_functions/fluid_properties.py +281 -0
- fmu/pem/pem_functions/mineral_properties.py +230 -0
- fmu/pem/pem_functions/regression_models.py +261 -0
- fmu/pem/pem_functions/run_friable_model.py +119 -0
- fmu/pem/pem_functions/run_patchy_cement_model.py +120 -0
- fmu/pem/pem_functions/run_t_matrix_and_pressure.py +186 -0
- fmu/pem/pem_utilities/__init__.py +66 -0
- fmu/pem/pem_utilities/cumsum_properties.py +104 -0
- fmu/pem/pem_utilities/delta_cumsum_time.py +104 -0
- fmu/pem/pem_utilities/enum_defs.py +54 -0
- fmu/pem/pem_utilities/export_routines.py +272 -0
- fmu/pem/pem_utilities/import_config.py +93 -0
- fmu/pem/pem_utilities/import_routines.py +161 -0
- fmu/pem/pem_utilities/pem_class_definitions.py +113 -0
- fmu/pem/pem_utilities/pem_config_validation.py +505 -0
- fmu/pem/pem_utilities/rpm_models.py +177 -0
- fmu/pem/pem_utilities/update_grid.py +54 -0
- fmu/pem/pem_utilities/utils.py +262 -0
- fmu/pem/run_pem.py +98 -0
- fmu/pem/version.py +21 -0
- fmu_pem-0.0.1.dist-info/METADATA +768 -0
- fmu_pem-0.0.1.dist-info/RECORD +37 -0
- fmu_pem-0.0.1.dist-info/WHEEL +5 -0
- fmu_pem-0.0.1.dist-info/entry_points.txt +5 -0
- fmu_pem-0.0.1.dist-info/licenses/LICENSE +674 -0
- fmu_pem-0.0.1.dist-info/top_level.txt +1 -0
fmu/__init__.py
ADDED
fmu/pem/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The INTERNAL_EQUINOR property means that fmu-pem is run within the Equinor organisation
|
|
3
|
+
where there is access to more elaborate fluid models and other rock physics
|
|
4
|
+
models.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
try: # noqa: SIM105
|
|
8
|
+
import init_rock_physics
|
|
9
|
+
except (ImportError, ModuleNotFoundError):
|
|
10
|
+
INTERNAL_EQUINOR = False
|
|
11
|
+
else:
|
|
12
|
+
INTERNAL_EQUINOR = True
|
|
13
|
+
|
|
14
|
+
from .run_pem import pem_fcn
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"pem_fcn",
|
|
18
|
+
"INTERNAL_EQUINOR",
|
|
19
|
+
]
|
fmu/pem/__main__.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# pylint: disable=missing-module-docstring
|
|
2
|
+
import argparse
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from warnings import warn
|
|
5
|
+
|
|
6
|
+
from .pem_utilities import restore_dir
|
|
7
|
+
from .run_pem import pem_fcn
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main():
|
|
11
|
+
parser = argparse.ArgumentParser(__file__)
|
|
12
|
+
parser.add_argument(
|
|
13
|
+
"-s",
|
|
14
|
+
"--startdir",
|
|
15
|
+
type=Path,
|
|
16
|
+
required=True,
|
|
17
|
+
help="Start directory for running script (required)",
|
|
18
|
+
)
|
|
19
|
+
parser.add_argument(
|
|
20
|
+
"-c",
|
|
21
|
+
"--configdir",
|
|
22
|
+
type=Path,
|
|
23
|
+
required=True,
|
|
24
|
+
help="Path to config file (required)",
|
|
25
|
+
)
|
|
26
|
+
parser.add_argument(
|
|
27
|
+
"-f",
|
|
28
|
+
"--configfile",
|
|
29
|
+
type=Path,
|
|
30
|
+
required=True,
|
|
31
|
+
help="Configuration yaml file name (required)",
|
|
32
|
+
)
|
|
33
|
+
args = parser.parse_args()
|
|
34
|
+
cwd = args.startdir
|
|
35
|
+
if str(cwd).endswith("rms/model"):
|
|
36
|
+
run_folder = cwd
|
|
37
|
+
else:
|
|
38
|
+
try:
|
|
39
|
+
run_folder = cwd.joinpath("rms/model")
|
|
40
|
+
assert run_folder.exists() and run_folder.is_dir()
|
|
41
|
+
except AssertionError as e:
|
|
42
|
+
warn(f"PEM model should be run from the rms/model folder. {e}")
|
|
43
|
+
run_folder = cwd
|
|
44
|
+
with restore_dir(run_folder):
|
|
45
|
+
pem_fcn(
|
|
46
|
+
start_dir=args.startdir,
|
|
47
|
+
rel_path_pem=args.configdir,
|
|
48
|
+
pem_config_file_name=args.configfile,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
if __name__ == "__main__":
|
|
53
|
+
main()
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from ert import (
|
|
4
|
+
ForwardModelStepDocumentation,
|
|
5
|
+
ForwardModelStepJSON,
|
|
6
|
+
ForwardModelStepPlugin,
|
|
7
|
+
ForwardModelStepValidationError,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PetroElasticModel(ForwardModelStepPlugin):
|
|
12
|
+
def __init__(self) -> None:
|
|
13
|
+
super().__init__(
|
|
14
|
+
name="PEM",
|
|
15
|
+
command=[
|
|
16
|
+
"pem",
|
|
17
|
+
"--startdir",
|
|
18
|
+
"<START_DIR>",
|
|
19
|
+
"--configdir",
|
|
20
|
+
"<CONFIG_DIR>",
|
|
21
|
+
"--configfile",
|
|
22
|
+
"<CONFIG_FILE>",
|
|
23
|
+
],
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
def validate_pre_realization_run(
|
|
27
|
+
self, fm_step_json: ForwardModelStepJSON
|
|
28
|
+
) -> ForwardModelStepJSON:
|
|
29
|
+
return fm_step_json
|
|
30
|
+
|
|
31
|
+
def validate_pre_experiment(self, fm_step_json: ForwardModelStepJSON) -> None:
|
|
32
|
+
import os
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
|
|
35
|
+
from fmu.pem.pem_utilities import read_pem_config
|
|
36
|
+
|
|
37
|
+
model_dir_env = os.environ.get("PEM_MODEL_DIR")
|
|
38
|
+
if model_dir_env is None:
|
|
39
|
+
raise ValueError(
|
|
40
|
+
'environment variable "PEM_MODEL_DIR" must be set to '
|
|
41
|
+
"validate PEM model pre-experiment"
|
|
42
|
+
)
|
|
43
|
+
model_dir = Path(model_dir_env)
|
|
44
|
+
|
|
45
|
+
runpath_config_dir = Path(fm_step_json["argList"][3])
|
|
46
|
+
config_dir = (
|
|
47
|
+
model_dir
|
|
48
|
+
/ "../.."
|
|
49
|
+
/ runpath_config_dir.parent.name
|
|
50
|
+
/ runpath_config_dir.name
|
|
51
|
+
).resolve()
|
|
52
|
+
config_file = fm_step_json["argList"][5]
|
|
53
|
+
try:
|
|
54
|
+
os.chdir(model_dir)
|
|
55
|
+
_ = read_pem_config(config_dir / config_file)
|
|
56
|
+
except Exception as e:
|
|
57
|
+
raise ForwardModelStepValidationError(f"pem validation failed:\n {str(e)}")
|
|
58
|
+
|
|
59
|
+
@staticmethod
|
|
60
|
+
def documentation() -> ForwardModelStepDocumentation | None:
|
|
61
|
+
return ForwardModelStepDocumentation(
|
|
62
|
+
category="modelling.reservoir",
|
|
63
|
+
source_package="fmu.pem",
|
|
64
|
+
source_function_name="PetroElasticModel",
|
|
65
|
+
description="",
|
|
66
|
+
examples="""
|
|
67
|
+
.. code-block:: console
|
|
68
|
+
|
|
69
|
+
FORWARD_MODEL PEM(<START_DIR>=../../rms/model, <CONFIG_DIR>=../../sim2seis/model, <CONFIG_FILE>=new_pem.yml)
|
|
70
|
+
|
|
71
|
+
""", # noqa: E501,
|
|
72
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ert
|
|
4
|
+
|
|
5
|
+
from fmu.pem.forward_models import PetroElasticModel
|
|
6
|
+
|
|
7
|
+
PLUGIN_NAME = "pem"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@ert.plugin(name=PLUGIN_NAME)
|
|
11
|
+
def installable_workflow_jobs() -> dict[str, str]:
|
|
12
|
+
return {}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@ert.plugin(name=PLUGIN_NAME)
|
|
16
|
+
def installable_forward_model_steps() -> list[ert.ForwardModelStepPlugin]:
|
|
17
|
+
return [ # type: ignore
|
|
18
|
+
PetroElasticModel,
|
|
19
|
+
]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from .density import estimate_bulk_density
|
|
2
|
+
from .effective_pressure import estimate_pressure
|
|
3
|
+
from .estimate_saturated_rock import estimate_saturated_rock
|
|
4
|
+
from .fluid_properties import effective_fluid_properties
|
|
5
|
+
from .mineral_properties import (
|
|
6
|
+
effective_mineral_properties,
|
|
7
|
+
estimate_effective_mineral_properties,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"effective_fluid_properties",
|
|
12
|
+
"effective_mineral_properties",
|
|
13
|
+
"estimate_bulk_density",
|
|
14
|
+
"estimate_effective_mineral_properties",
|
|
15
|
+
"estimate_pressure",
|
|
16
|
+
"estimate_saturated_rock",
|
|
17
|
+
]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
from rock_physics_open.equinor_utilities.std_functions import rho_b
|
|
4
|
+
|
|
5
|
+
from fmu.pem.pem_utilities import (
|
|
6
|
+
EffectiveFluidProperties,
|
|
7
|
+
MatrixProperties,
|
|
8
|
+
PemConfig,
|
|
9
|
+
SimInitProperties,
|
|
10
|
+
estimate_cement,
|
|
11
|
+
)
|
|
12
|
+
from fmu.pem.pem_utilities.rpm_models import PatchyCementRPM
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def estimate_bulk_density(
|
|
16
|
+
config: PemConfig,
|
|
17
|
+
init_prop: SimInitProperties,
|
|
18
|
+
fluid_props: List[EffectiveFluidProperties],
|
|
19
|
+
mineral_props: MatrixProperties,
|
|
20
|
+
) -> List:
|
|
21
|
+
"""
|
|
22
|
+
Estimate the bulk density per restart date.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
config: Parameter settings.
|
|
26
|
+
init_prop: Constant properties, here using porosity.
|
|
27
|
+
fluid_props: List of EffectiveFluidProperties objects representing the effective
|
|
28
|
+
fluid properties per restart date.
|
|
29
|
+
mineral_props: EffectiveMineralProperties object representing the effective
|
|
30
|
+
properties.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
List of bulk densities per restart date.
|
|
34
|
+
|
|
35
|
+
Raises:
|
|
36
|
+
ValueError: If fluid_props is an empty list.
|
|
37
|
+
"""
|
|
38
|
+
if config.rock_matrix.rpm == PatchyCementRPM:
|
|
39
|
+
# Get cement mineral properties
|
|
40
|
+
cement_mineral = config.rock_matrix.cement
|
|
41
|
+
mineral = config.rock_matrix.minerals[cement_mineral]
|
|
42
|
+
# Cement properties
|
|
43
|
+
cement_properties = estimate_cement(
|
|
44
|
+
mineral.bulk_modulus, mineral.shear_modulus, mineral.density, init_prop.poro
|
|
45
|
+
)
|
|
46
|
+
rel_frac_cem = (
|
|
47
|
+
config.rock_matrix.rpm.parameters.cement_fraction / init_prop.poro
|
|
48
|
+
)
|
|
49
|
+
rho_m = (
|
|
50
|
+
rel_frac_cem * cement_properties.dens
|
|
51
|
+
+ (1 - rel_frac_cem) * mineral_props.dens
|
|
52
|
+
)
|
|
53
|
+
else:
|
|
54
|
+
rho_m = mineral_props.dens
|
|
55
|
+
return [rho_b(init_prop.poro, fluid.dens, rho_m) for fluid in fluid_props]
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
from typing import List, Union
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from fmu.pem.pem_utilities import (
|
|
6
|
+
EffectiveFluidProperties,
|
|
7
|
+
MatrixProperties,
|
|
8
|
+
PemConfig,
|
|
9
|
+
PressureProperties,
|
|
10
|
+
SimInitProperties,
|
|
11
|
+
SimRstProperties,
|
|
12
|
+
to_masked_array,
|
|
13
|
+
)
|
|
14
|
+
from fmu.pem.pem_utilities.enum_defs import OverburdenPressure
|
|
15
|
+
|
|
16
|
+
from .density import estimate_bulk_density
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def estimate_pressure(
|
|
20
|
+
config: PemConfig,
|
|
21
|
+
sim_init: SimInitProperties,
|
|
22
|
+
sim_rst: List[SimRstProperties],
|
|
23
|
+
matrix_props: MatrixProperties,
|
|
24
|
+
fluid_props: list[EffectiveFluidProperties],
|
|
25
|
+
) -> List[PressureProperties]:
|
|
26
|
+
"""Estimate effective and overburden pressure.
|
|
27
|
+
Effective pressure is defined as overburden pressure minus formation (or pore)
|
|
28
|
+
pressure multiplied with the Biot factor
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
config: configuration parameters
|
|
32
|
+
sim_init: initial properties from simulation model
|
|
33
|
+
sim_rst: restart properties from the simulation model
|
|
34
|
+
matrix_props: rock properties
|
|
35
|
+
fluid_props: effective fluid properties
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
effective pressure [bar], overburden_pressure [bar]
|
|
39
|
+
|
|
40
|
+
Raises:
|
|
41
|
+
ValueError: If sim_rst is an empty list.
|
|
42
|
+
"""
|
|
43
|
+
# Effective pressure, get formation pressures and convert to Pa from bar
|
|
44
|
+
# Saturated rock bulk density bulk
|
|
45
|
+
bulk_density = estimate_bulk_density(config, sim_init, fluid_props, matrix_props)
|
|
46
|
+
|
|
47
|
+
ovb = config.pressure.overburden
|
|
48
|
+
if ovb.type == OverburdenPressure.CONSTANT:
|
|
49
|
+
eff_pres = [
|
|
50
|
+
estimate_effective_pressure(
|
|
51
|
+
formation_pressure=sim_date.pressure * 1.0e5,
|
|
52
|
+
bulk_density=dens,
|
|
53
|
+
reference_overburden_pressure=ovb.constant,
|
|
54
|
+
)
|
|
55
|
+
for (sim_date, dens) in zip(sim_rst, bulk_density)
|
|
56
|
+
]
|
|
57
|
+
else: # ovb.type == 'trend':
|
|
58
|
+
eff_pres = [
|
|
59
|
+
estimate_effective_pressure(
|
|
60
|
+
formation_pressure=sim_date.pressure * 1.0e5,
|
|
61
|
+
bulk_density=dens,
|
|
62
|
+
reference_overburden_pressure=overburden_pressure_from_trend(
|
|
63
|
+
inter=ovb.trend.intercept,
|
|
64
|
+
grad=ovb.trend.gradient,
|
|
65
|
+
depth=sim_init.depth,
|
|
66
|
+
),
|
|
67
|
+
)
|
|
68
|
+
for (sim_date, dens) in zip(sim_rst, bulk_density)
|
|
69
|
+
]
|
|
70
|
+
# Sanity check on results - effective pressure should not be negative
|
|
71
|
+
for i, pres in enumerate(eff_pres):
|
|
72
|
+
if np.any(pres.effective_pressure < 0.0):
|
|
73
|
+
raise ValueError(
|
|
74
|
+
f"effective pressure calculation: formation pressure exceeds "
|
|
75
|
+
f"overburden pressure for date {config.global_params.seis_dates[i]}, \n"
|
|
76
|
+
f"minimum effective pressure is {np.min(pres.effective_pressure):.2f} "
|
|
77
|
+
f"bar, the number of cells with negative effective pressure is "
|
|
78
|
+
f"{np.sum(pres.effective_pressure < 0.0)}"
|
|
79
|
+
)
|
|
80
|
+
# Add effective pressure to RST properties
|
|
81
|
+
return eff_pres
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def estimate_effective_pressure(
|
|
85
|
+
formation_pressure: np.ma.MaskedArray,
|
|
86
|
+
bulk_density: np.ma.MaskedArray,
|
|
87
|
+
reference_overburden_pressure: Union[np.ma.MaskedArray, float],
|
|
88
|
+
biot_coeff: float = 1.0,
|
|
89
|
+
) -> PressureProperties:
|
|
90
|
+
"""Estimate effective pressure from reference overburden pressure, formation
|
|
91
|
+
pressure, depth and bulk density
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
formation_pressure: formation pressure [Pa]
|
|
95
|
+
bulk_density: bulk density [kg/m3]
|
|
96
|
+
reference_overburden_pressure: constant or one-layer array with reference
|
|
97
|
+
biot_coeff: Biot coefficient, in the range [0.0, 1.0] [unitless]
|
|
98
|
+
.mineral
|
|
99
|
+
Returns:
|
|
100
|
+
PressureProperties object with formation pressure [bar], effective pressure
|
|
101
|
+
[bar], overburden_pressure [bar]
|
|
102
|
+
|
|
103
|
+
Raises:
|
|
104
|
+
ValueError: If reference overburden pressure is not of type float or numpy
|
|
105
|
+
masked array, or if reference overburden pressure is not of the same
|
|
106
|
+
dimension as comparable grids, or if reference overburden pressure does
|
|
107
|
+
not have the same shape as comparable grids.
|
|
108
|
+
"""
|
|
109
|
+
reference_overburden_pressure = _verify_ovb_press(
|
|
110
|
+
reference_overburden_pressure, bulk_density
|
|
111
|
+
)
|
|
112
|
+
effective_pressure = reference_overburden_pressure - biot_coeff * formation_pressure
|
|
113
|
+
return PressureProperties(
|
|
114
|
+
formation_pressure=formation_pressure * 1.0e-5,
|
|
115
|
+
effective_pressure=effective_pressure * 1.0e-5,
|
|
116
|
+
overburden_pressure=reference_overburden_pressure * 1.0e-5,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _verify_ovb_press(
|
|
121
|
+
ref_pres: Union[float, np.ma.MaskedArray], reference_cube: np.ma.MaskedArray
|
|
122
|
+
) -> np.ma.MaskedArray:
|
|
123
|
+
if isinstance(ref_pres, float):
|
|
124
|
+
return to_masked_array(ref_pres, reference_cube)
|
|
125
|
+
if not isinstance(ref_pres, np.ma.MaskedArray):
|
|
126
|
+
raise ValueError(
|
|
127
|
+
f"{__file__}: Reference overburden pressure is not of type float or numpy "
|
|
128
|
+
"masked array"
|
|
129
|
+
)
|
|
130
|
+
if not ref_pres.ndim == reference_cube.ndim:
|
|
131
|
+
raise ValueError(
|
|
132
|
+
f"{__file__}: Reference overburden pressure is not of the same dimension "
|
|
133
|
+
"as comparable grids"
|
|
134
|
+
)
|
|
135
|
+
if not np.all(ref_pres.shape[0:-1] == reference_cube.shape[0:-1]):
|
|
136
|
+
raise ValueError(
|
|
137
|
+
f"{__file__}: Reference overburden pressure does not have the same shape "
|
|
138
|
+
"as comparable grids"
|
|
139
|
+
)
|
|
140
|
+
return ref_pres
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def overburden_pressure_from_trend(
|
|
144
|
+
inter: float, grad: float, depth: np.ma.MaskedArray
|
|
145
|
+
) -> np.ma.MaskedArray:
|
|
146
|
+
"""Calculate overburden pressure from depth trend for top of depth grid
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
inter: intercept in trend
|
|
150
|
+
grad: gradient in trend
|
|
151
|
+
depth: depth cube [m]
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
overburden pressure [Pa]
|
|
155
|
+
|
|
156
|
+
Raises:
|
|
157
|
+
ValueError: If unable to calculate overburden pressure due to non-numeric
|
|
158
|
+
intercept or gradient.
|
|
159
|
+
"""
|
|
160
|
+
try:
|
|
161
|
+
a = float(inter)
|
|
162
|
+
b = float(grad)
|
|
163
|
+
ovb_pres = a + b * depth
|
|
164
|
+
except ValueError as err:
|
|
165
|
+
raise ValueError(
|
|
166
|
+
f"{__file__}: unable to calculate overburden pressure:"
|
|
167
|
+
) from err
|
|
168
|
+
return ovb_pres
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
from fmu.pem.pem_utilities import (
|
|
4
|
+
EffectiveFluidProperties,
|
|
5
|
+
MatrixProperties,
|
|
6
|
+
PemConfig,
|
|
7
|
+
PressureProperties,
|
|
8
|
+
SaturatedRockProperties,
|
|
9
|
+
SimInitProperties,
|
|
10
|
+
estimate_cement,
|
|
11
|
+
)
|
|
12
|
+
from fmu.pem.pem_utilities.rpm_models import (
|
|
13
|
+
FriableRPM,
|
|
14
|
+
PatchyCementRPM,
|
|
15
|
+
RegressionModels,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from .regression_models import run_regression_models
|
|
19
|
+
from .run_friable_model import run_friable
|
|
20
|
+
from .run_patchy_cement_model import run_patchy_cement
|
|
21
|
+
from .run_t_matrix_and_pressure import run_t_matrix_and_pressure_models
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def estimate_saturated_rock(
|
|
25
|
+
config: PemConfig,
|
|
26
|
+
sim_init: SimInitProperties,
|
|
27
|
+
eff_pres: list[PressureProperties],
|
|
28
|
+
matrix_props: MatrixProperties,
|
|
29
|
+
fluid_props: list[EffectiveFluidProperties],
|
|
30
|
+
) -> List[SaturatedRockProperties]:
|
|
31
|
+
"""Wrapper to call rock physics model
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
config: PEM configuration parameters
|
|
35
|
+
sim_init: initial properties from simulation model
|
|
36
|
+
eff_pres: restart properties from simulation model
|
|
37
|
+
matrix_props: rock properties (mineral and fluids)
|
|
38
|
+
fluid_props: effective fluid properties
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
saturated rock properties per restart date
|
|
42
|
+
"""
|
|
43
|
+
if isinstance(config.rock_matrix.rpm, PatchyCementRPM):
|
|
44
|
+
# Patchy cement model
|
|
45
|
+
cement = config.rock_matrix.minerals[config.rock_matrix.cement]
|
|
46
|
+
cement_properties = estimate_cement(
|
|
47
|
+
density=cement.density,
|
|
48
|
+
bulk_modulus=cement.bulk_modulus,
|
|
49
|
+
shear_modulus=cement.shear_modulus,
|
|
50
|
+
grid=sim_init.poro,
|
|
51
|
+
)
|
|
52
|
+
sat_rock_props = run_patchy_cement(
|
|
53
|
+
matrix_props,
|
|
54
|
+
fluid_props,
|
|
55
|
+
cement_properties,
|
|
56
|
+
sim_init.poro,
|
|
57
|
+
eff_pres,
|
|
58
|
+
config,
|
|
59
|
+
)
|
|
60
|
+
elif isinstance(config.rock_matrix.rpm, FriableRPM):
|
|
61
|
+
# Friable sandstone model
|
|
62
|
+
sat_rock_props = run_friable(
|
|
63
|
+
matrix_props,
|
|
64
|
+
fluid_props,
|
|
65
|
+
sim_init.poro,
|
|
66
|
+
eff_pres,
|
|
67
|
+
config,
|
|
68
|
+
)
|
|
69
|
+
elif isinstance(config.rock_matrix.rpm, RegressionModels):
|
|
70
|
+
# Regression models for dry rock properties, saturation by Gassmann
|
|
71
|
+
sat_rock_props = run_regression_models(
|
|
72
|
+
matrix_props,
|
|
73
|
+
fluid_props,
|
|
74
|
+
sim_init.poro,
|
|
75
|
+
eff_pres,
|
|
76
|
+
config,
|
|
77
|
+
vsh=sim_init.ntg_pem,
|
|
78
|
+
)
|
|
79
|
+
else:
|
|
80
|
+
# Using default values for T-Matrix parameter file and vp and vs pressure
|
|
81
|
+
# model files
|
|
82
|
+
sat_rock_props = run_t_matrix_and_pressure_models(
|
|
83
|
+
matrix_props,
|
|
84
|
+
fluid_props,
|
|
85
|
+
sim_init.poro,
|
|
86
|
+
sim_init.ntg_pem,
|
|
87
|
+
eff_pres,
|
|
88
|
+
config,
|
|
89
|
+
)
|
|
90
|
+
return sat_rock_props
|