fmu-pem 0.0.1__py3-none-any.whl → 0.0.3__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/pem/__main__.py +32 -16
- fmu/pem/forward_models/pem_model.py +19 -27
- fmu/pem/pem_functions/__init__.py +2 -2
- fmu/pem/pem_functions/density.py +32 -38
- fmu/pem/pem_functions/effective_pressure.py +153 -48
- fmu/pem/pem_functions/estimate_saturated_rock.py +244 -52
- fmu/pem/pem_functions/fluid_properties.py +453 -246
- fmu/pem/pem_functions/mineral_properties.py +77 -74
- fmu/pem/pem_functions/pressure_sensitivity.py +430 -0
- fmu/pem/pem_functions/regression_models.py +129 -97
- fmu/pem/pem_functions/run_friable_model.py +106 -37
- fmu/pem/pem_functions/run_patchy_cement_model.py +107 -45
- fmu/pem/pem_functions/{run_t_matrix_and_pressure.py → run_t_matrix_model.py} +48 -27
- fmu/pem/pem_utilities/__init__.py +31 -9
- fmu/pem/pem_utilities/cumsum_properties.py +29 -37
- fmu/pem/pem_utilities/delta_cumsum_time.py +8 -13
- fmu/pem/pem_utilities/enum_defs.py +77 -4
- fmu/pem/pem_utilities/export_routines.py +84 -72
- fmu/pem/pem_utilities/fipnum_pvtnum_utilities.py +217 -0
- fmu/pem/pem_utilities/import_config.py +64 -46
- fmu/pem/pem_utilities/import_routines.py +58 -69
- fmu/pem/pem_utilities/pem_class_definitions.py +81 -23
- fmu/pem/pem_utilities/pem_config_validation.py +374 -149
- fmu/pem/pem_utilities/rpm_models.py +481 -83
- fmu/pem/pem_utilities/update_grid.py +3 -2
- fmu/pem/pem_utilities/utils.py +90 -38
- fmu/pem/run_pem.py +70 -39
- fmu/pem/version.py +16 -3
- {fmu_pem-0.0.1.dist-info → fmu_pem-0.0.3.dist-info}/METADATA +33 -28
- fmu_pem-0.0.3.dist-info/RECORD +39 -0
- fmu_pem-0.0.1.dist-info/RECORD +0 -37
- {fmu_pem-0.0.1.dist-info → fmu_pem-0.0.3.dist-info}/WHEEL +0 -0
- {fmu_pem-0.0.1.dist-info → fmu_pem-0.0.3.dist-info}/entry_points.txt +0 -0
- {fmu_pem-0.0.1.dist-info → fmu_pem-0.0.3.dist-info}/licenses/LICENSE +0 -0
- {fmu_pem-0.0.1.dist-info → fmu_pem-0.0.3.dist-info}/top_level.txt +0 -0
fmu/pem/__main__.py
CHANGED
|
@@ -9,43 +9,59 @@ from .run_pem import pem_fcn
|
|
|
9
9
|
|
|
10
10
|
def main():
|
|
11
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
12
|
parser.add_argument(
|
|
20
13
|
"-c",
|
|
21
|
-
"--
|
|
14
|
+
"--config-dir",
|
|
22
15
|
type=Path,
|
|
23
16
|
required=True,
|
|
24
17
|
help="Path to config file (required)",
|
|
25
18
|
)
|
|
26
19
|
parser.add_argument(
|
|
27
20
|
"-f",
|
|
28
|
-
"--
|
|
21
|
+
"--config-file",
|
|
29
22
|
type=Path,
|
|
30
23
|
required=True,
|
|
31
24
|
help="Configuration yaml file name (required)",
|
|
32
25
|
)
|
|
26
|
+
parser.add_argument(
|
|
27
|
+
"-g",
|
|
28
|
+
"--global-dir",
|
|
29
|
+
type=Path,
|
|
30
|
+
required=True,
|
|
31
|
+
help="Relative path to global config file (required)",
|
|
32
|
+
)
|
|
33
|
+
parser.add_argument(
|
|
34
|
+
"-o",
|
|
35
|
+
"--global-file",
|
|
36
|
+
type=Path,
|
|
37
|
+
required=True,
|
|
38
|
+
help="Global configuration yaml file name (required)",
|
|
39
|
+
)
|
|
40
|
+
parser.add_argument(
|
|
41
|
+
"-m",
|
|
42
|
+
"--model-dir",
|
|
43
|
+
type=Path,
|
|
44
|
+
required=False,
|
|
45
|
+
help="For ERT run: Absolute directory name for model file, pre-experiment. Not "
|
|
46
|
+
"needed for command line run",
|
|
47
|
+
)
|
|
33
48
|
args = parser.parse_args()
|
|
34
|
-
cwd = args.
|
|
35
|
-
if str(cwd).endswith("
|
|
49
|
+
cwd = args.config_dir.absolute()
|
|
50
|
+
if str(cwd).endswith("sim2seis/model"):
|
|
36
51
|
run_folder = cwd
|
|
37
52
|
else:
|
|
38
53
|
try:
|
|
39
|
-
run_folder = cwd
|
|
54
|
+
run_folder = cwd / "sim2seis" / "model"
|
|
40
55
|
assert run_folder.exists() and run_folder.is_dir()
|
|
41
56
|
except AssertionError as e:
|
|
42
|
-
warn(f"PEM model should be run from the
|
|
57
|
+
warn(f"PEM model should be run from the sim2seis/model folder. {e}")
|
|
43
58
|
run_folder = cwd
|
|
44
59
|
with restore_dir(run_folder):
|
|
45
60
|
pem_fcn(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
61
|
+
config_dir=run_folder,
|
|
62
|
+
pem_config_file_name=args.config_file,
|
|
63
|
+
global_config_dir=args.global_dir,
|
|
64
|
+
global_config_file=args.global_file,
|
|
49
65
|
)
|
|
50
66
|
|
|
51
67
|
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
3
6
|
from ert import (
|
|
4
7
|
ForwardModelStepDocumentation,
|
|
5
8
|
ForwardModelStepJSON,
|
|
@@ -7,6 +10,8 @@ from ert import (
|
|
|
7
10
|
ForwardModelStepValidationError,
|
|
8
11
|
)
|
|
9
12
|
|
|
13
|
+
from fmu.pem.pem_utilities import read_pem_config
|
|
14
|
+
|
|
10
15
|
|
|
11
16
|
class PetroElasticModel(ForwardModelStepPlugin):
|
|
12
17
|
def __init__(self) -> None:
|
|
@@ -14,12 +19,16 @@ class PetroElasticModel(ForwardModelStepPlugin):
|
|
|
14
19
|
name="PEM",
|
|
15
20
|
command=[
|
|
16
21
|
"pem",
|
|
17
|
-
"--
|
|
18
|
-
"<START_DIR>",
|
|
19
|
-
"--configdir",
|
|
22
|
+
"--config-dir",
|
|
20
23
|
"<CONFIG_DIR>",
|
|
21
|
-
"--
|
|
24
|
+
"--config-file",
|
|
22
25
|
"<CONFIG_FILE>",
|
|
26
|
+
"--global-dir",
|
|
27
|
+
"<GLOBAL_DIR>",
|
|
28
|
+
"--global-file",
|
|
29
|
+
"<GLOBAL_FILE>",
|
|
30
|
+
"--model-dir",
|
|
31
|
+
"<MODEL_DIR>",
|
|
23
32
|
],
|
|
24
33
|
)
|
|
25
34
|
|
|
@@ -29,30 +38,13 @@ class PetroElasticModel(ForwardModelStepPlugin):
|
|
|
29
38
|
return fm_step_json
|
|
30
39
|
|
|
31
40
|
def validate_pre_experiment(self, fm_step_json: ForwardModelStepJSON) -> None:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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]
|
|
41
|
+
# Parse YAML parameter file by pydantic pre-experiment to catch errors at an
|
|
42
|
+
# early stage
|
|
43
|
+
config_file = Path(fm_step_json["argList"][3])
|
|
44
|
+
model_dir = Path(fm_step_json["argList"][9])
|
|
53
45
|
try:
|
|
54
46
|
os.chdir(model_dir)
|
|
55
|
-
_ = read_pem_config(
|
|
47
|
+
_ = read_pem_config(config_file)
|
|
56
48
|
except Exception as e:
|
|
57
49
|
raise ForwardModelStepValidationError(f"pem validation failed:\n {str(e)}")
|
|
58
50
|
|
|
@@ -66,7 +58,7 @@ class PetroElasticModel(ForwardModelStepPlugin):
|
|
|
66
58
|
examples="""
|
|
67
59
|
.. code-block:: console
|
|
68
60
|
|
|
69
|
-
FORWARD_MODEL PEM(<
|
|
61
|
+
FORWARD_MODEL PEM(<CONFIG_DIR>=../../sim2seis/model, <CONFIG_FILE>=new_pem.yml, <GLOBAL_DiR>=../../fmuconfig/output, <GLOBAL_FILE>=global_variables.yml, <MODEL_DIR>=/my_fmu_structure/sim2seis/model)
|
|
70
62
|
|
|
71
63
|
""", # noqa: E501,
|
|
72
64
|
)
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
from .density import estimate_bulk_density
|
|
2
2
|
from .effective_pressure import estimate_pressure
|
|
3
3
|
from .estimate_saturated_rock import estimate_saturated_rock
|
|
4
|
-
from .fluid_properties import
|
|
4
|
+
from .fluid_properties import effective_fluid_properties_zoned
|
|
5
5
|
from .mineral_properties import (
|
|
6
6
|
effective_mineral_properties,
|
|
7
7
|
estimate_effective_mineral_properties,
|
|
8
8
|
)
|
|
9
9
|
|
|
10
10
|
__all__ = [
|
|
11
|
-
"
|
|
11
|
+
"effective_fluid_properties_zoned",
|
|
12
12
|
"effective_mineral_properties",
|
|
13
13
|
"estimate_bulk_density",
|
|
14
14
|
"estimate_effective_mineral_properties",
|
fmu/pem/pem_functions/density.py
CHANGED
|
@@ -1,55 +1,49 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
1
|
+
from numpy.ma import MaskedArray
|
|
3
2
|
from rock_physics_open.equinor_utilities.std_functions import rho_b
|
|
4
3
|
|
|
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
4
|
|
|
15
5
|
def estimate_bulk_density(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
6
|
+
porosity: MaskedArray,
|
|
7
|
+
fluid_density: list[MaskedArray],
|
|
8
|
+
mineral_density: MaskedArray,
|
|
9
|
+
*,
|
|
10
|
+
patchy_cement: bool = False,
|
|
11
|
+
cement_fraction: float | None = None,
|
|
12
|
+
cement_density: float | None = None,
|
|
13
|
+
) -> list[MaskedArray]:
|
|
14
|
+
r"""
|
|
22
15
|
Estimate the bulk density per restart date.
|
|
23
16
|
|
|
24
17
|
Args:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
18
|
+
porosity: Initial simulation porosity.
|
|
19
|
+
fluid_density: Effective fluid density per date.
|
|
20
|
+
mineral_density: Effective mineral (matrix) density.
|
|
21
|
+
patchy_cement: Enable patchy_cement mixing.
|
|
22
|
+
cement_fraction: Cement volume fraction within pore space.
|
|
23
|
+
cement_density: Cement density.
|
|
31
24
|
|
|
32
25
|
Returns:
|
|
33
|
-
|
|
26
|
+
list of bulk densities per restart date.
|
|
34
27
|
|
|
35
28
|
Raises:
|
|
36
29
|
ValueError: If fluid_props is an empty list.
|
|
37
30
|
"""
|
|
38
|
-
if
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
31
|
+
if not fluid_density:
|
|
32
|
+
raise ValueError("Fluid properties cannot be an empty list.")
|
|
33
|
+
|
|
34
|
+
if patchy_cement:
|
|
35
|
+
if any(v is None for v in (cement_fraction, cement_density)):
|
|
36
|
+
raise ValueError(
|
|
37
|
+
"cement_fraction and cement_props must be provided when "
|
|
38
|
+
"patchy_cement is True."
|
|
39
|
+
)
|
|
42
40
|
# Cement properties
|
|
43
|
-
|
|
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
|
-
)
|
|
41
|
+
rel_cement_fraction = cement_fraction / porosity
|
|
49
42
|
rho_m = (
|
|
50
|
-
|
|
51
|
-
+ (1 -
|
|
43
|
+
rel_cement_fraction * cement_density
|
|
44
|
+
+ (1 - rel_cement_fraction) * mineral_density
|
|
52
45
|
)
|
|
53
46
|
else:
|
|
54
|
-
rho_m =
|
|
55
|
-
|
|
47
|
+
rho_m = mineral_density
|
|
48
|
+
|
|
49
|
+
return [rho_b(porosity, rho_fl, rho_m) for rho_fl in fluid_density] # type: ignore
|
|
@@ -1,90 +1,195 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
2
4
|
|
|
3
5
|
import numpy as np
|
|
4
6
|
|
|
5
7
|
from fmu.pem.pem_utilities import (
|
|
6
8
|
EffectiveFluidProperties,
|
|
7
|
-
|
|
8
|
-
PemConfig,
|
|
9
|
+
EffectiveMineralProperties,
|
|
9
10
|
PressureProperties,
|
|
10
11
|
SimInitProperties,
|
|
11
12
|
SimRstProperties,
|
|
12
13
|
to_masked_array,
|
|
13
14
|
)
|
|
14
|
-
from fmu.pem.pem_utilities.enum_defs import
|
|
15
|
+
from fmu.pem.pem_utilities.enum_defs import (
|
|
16
|
+
OverburdenPressureTypes,
|
|
17
|
+
RPMType,
|
|
18
|
+
)
|
|
19
|
+
from fmu.pem.pem_utilities.fipnum_pvtnum_utilities import (
|
|
20
|
+
input_num_string_to_list,
|
|
21
|
+
validate_zone_coverage,
|
|
22
|
+
)
|
|
23
|
+
from fmu.pem.pem_utilities.pem_config_validation import (
|
|
24
|
+
OverburdenPressureConstant,
|
|
25
|
+
OverburdenPressureTrend,
|
|
26
|
+
)
|
|
15
27
|
|
|
16
28
|
from .density import estimate_bulk_density
|
|
17
29
|
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from fmu.pem.pem_utilities import RockMatrixProperties
|
|
32
|
+
|
|
18
33
|
|
|
19
34
|
def estimate_pressure(
|
|
20
|
-
|
|
35
|
+
rock_matrix: "RockMatrixProperties",
|
|
36
|
+
overburden_pressure: list[OverburdenPressureTrend | OverburdenPressureConstant],
|
|
21
37
|
sim_init: SimInitProperties,
|
|
22
|
-
sim_rst:
|
|
23
|
-
matrix_props:
|
|
38
|
+
sim_rst: list[SimRstProperties],
|
|
39
|
+
matrix_props: EffectiveMineralProperties,
|
|
24
40
|
fluid_props: list[EffectiveFluidProperties],
|
|
25
|
-
|
|
26
|
-
|
|
41
|
+
sim_dates: list[str],
|
|
42
|
+
fipnum: np.ma.MaskedArray,
|
|
43
|
+
) -> list[PressureProperties]:
|
|
44
|
+
"""Estimate effective and overburden pressure with per-zone overburden pressure
|
|
45
|
+
definitions.
|
|
46
|
+
|
|
27
47
|
Effective pressure is defined as overburden pressure minus formation (or pore)
|
|
28
|
-
pressure multiplied with the Biot factor
|
|
48
|
+
pressure multiplied with the Biot factor. Overburden pressure is zone-aware
|
|
49
|
+
(defined per FIPNUM group) but constant in time, while effective pressure
|
|
50
|
+
varies with time as formation pressure changes.
|
|
51
|
+
|
|
52
|
+
This function now supports zone-based rock physics models. Each zone can have
|
|
53
|
+
its own model type (e.g., Patchy Cement, Friable), which affects bulk density
|
|
54
|
+
calculation for overburden pressure estimation.
|
|
29
55
|
|
|
30
56
|
Args:
|
|
31
|
-
|
|
57
|
+
rock_matrix: rock matrix properties with zone-specific model definitions
|
|
58
|
+
overburden_pressure: list of trend or constant value for overburden pressure
|
|
59
|
+
per FIPNUM zone group
|
|
32
60
|
sim_init: initial properties from simulation model
|
|
33
|
-
sim_rst: restart properties from the simulation model
|
|
34
|
-
matrix_props:
|
|
35
|
-
fluid_props: effective fluid properties
|
|
61
|
+
sim_rst: restart properties from the simulation model (time-dependent)
|
|
62
|
+
matrix_props: effective mineral properties
|
|
63
|
+
fluid_props: effective fluid properties (time-dependent)
|
|
64
|
+
sim_dates: list of dates for simulation
|
|
65
|
+
fipnum: grid parameter with zone/region information
|
|
36
66
|
|
|
37
67
|
Returns:
|
|
38
|
-
|
|
68
|
+
List of PressureProperties for each time step containing effective pressure
|
|
69
|
+
[bar], formation pressure [bar], and overburden_pressure [bar]
|
|
39
70
|
|
|
40
71
|
Raises:
|
|
41
|
-
ValueError: If
|
|
72
|
+
ValueError: If FIPNUM zone definitions are invalid or if effective pressure
|
|
73
|
+
is negative for any cells.
|
|
42
74
|
"""
|
|
43
|
-
#
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
75
|
+
# Validate zone coverage
|
|
76
|
+
fipnum_strings: list[str] = [str(zone.fipnum) for zone in overburden_pressure]
|
|
77
|
+
validate_zone_coverage(fipnum_strings, fipnum, zone_name="FIPNUM")
|
|
78
|
+
|
|
79
|
+
# Get FIPNUM grid data and mask
|
|
80
|
+
fipnum_data = fipnum.data
|
|
81
|
+
fipnum_mask = (
|
|
82
|
+
fipnum.mask
|
|
83
|
+
if hasattr(fipnum, "mask")
|
|
84
|
+
else np.zeros_like(fipnum_data, dtype=bool)
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Get actual FIPNUM values present in grid for use with input_num_string_to_list
|
|
88
|
+
actual_fipnum_values = list(np.unique(fipnum_data[~fipnum_mask]).astype(int))
|
|
89
|
+
|
|
90
|
+
# Calculate bulk density for all time steps (needed for overburden pressure
|
|
91
|
+
# calculation)
|
|
92
|
+
# Bulk density calculation needs to be zone-aware for patchy cement models
|
|
93
|
+
fl_density = [fluid.density for fluid in fluid_props]
|
|
94
|
+
|
|
95
|
+
# Check if any zone uses patchy cement model and needs special handling
|
|
96
|
+
bulk_density = []
|
|
97
|
+
for fl_dens in fl_density:
|
|
98
|
+
bulk_dens_grid = np.ma.masked_array(
|
|
99
|
+
np.zeros(sim_init.poro.shape, dtype=float), mask=fipnum_mask
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Calculate bulk density per zone based on zone-specific model
|
|
103
|
+
for zone_region in rock_matrix.zone_regions:
|
|
104
|
+
# Get all FIPNUM values for this zone using input_num_string_to_list
|
|
105
|
+
fipnum_values = input_num_string_to_list(
|
|
106
|
+
zone_region.fipnum, actual_fipnum_values
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Build combined mask for all FIPNUMs in this zone using vectorized
|
|
110
|
+
# operation
|
|
111
|
+
zone_mask = np.isin(fipnum_data, fipnum_values) & ~fipnum_mask
|
|
112
|
+
|
|
113
|
+
# Check if this zone uses patchy cement model
|
|
114
|
+
is_patchy_cement = zone_region.model.model_name == RPMType.PATCHY_CEMENT
|
|
115
|
+
|
|
116
|
+
if is_patchy_cement:
|
|
117
|
+
# Get cement fraction and density for patchy cement model
|
|
118
|
+
cement_fraction = zone_region.model.parameters.cement_fraction
|
|
119
|
+
cement_density = rock_matrix.minerals[rock_matrix.cement].density
|
|
120
|
+
else:
|
|
121
|
+
cement_fraction = None
|
|
122
|
+
cement_density = None
|
|
123
|
+
|
|
124
|
+
# Calculate bulk density for this zone
|
|
125
|
+
zone_bulk_density = estimate_bulk_density(
|
|
126
|
+
porosity=np.ma.masked_where(~zone_mask, sim_init.poro),
|
|
127
|
+
fluid_density=[np.ma.masked_where(~zone_mask, fl_dens)],
|
|
128
|
+
mineral_density=np.ma.masked_where(~zone_mask, matrix_props.density),
|
|
129
|
+
patchy_cement=is_patchy_cement,
|
|
130
|
+
cement_fraction=cement_fraction,
|
|
131
|
+
cement_density=cement_density,
|
|
54
132
|
)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
133
|
+
|
|
134
|
+
# Merge zone bulk density into full grid
|
|
135
|
+
bulk_dens_grid[zone_mask] = zone_bulk_density[0][zone_mask]
|
|
136
|
+
|
|
137
|
+
bulk_density.append(bulk_dens_grid)
|
|
138
|
+
|
|
139
|
+
# Calculate overburden pressure per zone (time-independent)
|
|
140
|
+
# Initialize overburden pressure grid
|
|
141
|
+
overburden_pressure_grid = np.ma.masked_array(
|
|
142
|
+
np.full(sim_init.depth.shape, np.nan, dtype=float), mask=fipnum_mask
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
for zone in overburden_pressure:
|
|
146
|
+
# Get all FIPNUM values for this zone using input_num_string_to_list
|
|
147
|
+
fipnum_values = input_num_string_to_list(zone.fipnum, actual_fipnum_values)
|
|
148
|
+
|
|
149
|
+
# Build combined mask for all FIPNUMs in this zone using vectorized operation
|
|
150
|
+
mask_cells = np.isin(fipnum_data, fipnum_values) & ~fipnum_mask
|
|
151
|
+
|
|
152
|
+
if zone.type == OverburdenPressureTypes.CONSTANT:
|
|
153
|
+
# Constant overburden pressure for this zone
|
|
154
|
+
overburden_pressure_grid[mask_cells] = zone.value
|
|
155
|
+
else: # zone.type == OverburdenPressureTypes.TREND
|
|
156
|
+
# Calculate overburden pressure from trend for this zone (already in Pa)
|
|
157
|
+
zone_ovb_pres = overburden_pressure_from_trend(
|
|
158
|
+
inter=zone.intercept,
|
|
159
|
+
grad=zone.gradient,
|
|
160
|
+
depth=sim_init.depth,
|
|
67
161
|
)
|
|
68
|
-
|
|
69
|
-
|
|
162
|
+
overburden_pressure_grid[mask_cells] = zone_ovb_pres[mask_cells]
|
|
163
|
+
|
|
164
|
+
# Calculate effective pressure for each time step
|
|
165
|
+
# Formation pressure changes with time, but overburden pressure is constant
|
|
166
|
+
eff_pres = [
|
|
167
|
+
estimate_effective_pressure(
|
|
168
|
+
formation_pressure=sim_date.pressure,
|
|
169
|
+
bulk_density=dens,
|
|
170
|
+
reference_overburden_pressure=overburden_pressure_grid,
|
|
171
|
+
)
|
|
172
|
+
for (sim_date, dens) in zip(sim_rst, bulk_density)
|
|
173
|
+
]
|
|
174
|
+
|
|
70
175
|
# Sanity check on results - effective pressure should not be negative
|
|
71
176
|
for i, pres in enumerate(eff_pres):
|
|
72
177
|
if np.any(pres.effective_pressure < 0.0):
|
|
73
178
|
raise ValueError(
|
|
74
179
|
f"effective pressure calculation: formation pressure exceeds "
|
|
75
|
-
f"overburden pressure for date {
|
|
180
|
+
f"overburden pressure for date {sim_dates[i]}, \n"
|
|
76
181
|
f"minimum effective pressure is {np.min(pres.effective_pressure):.2f} "
|
|
77
182
|
f"bar, the number of cells with negative effective pressure is "
|
|
78
183
|
f"{np.sum(pres.effective_pressure < 0.0)}"
|
|
79
184
|
)
|
|
80
|
-
|
|
185
|
+
|
|
81
186
|
return eff_pres
|
|
82
187
|
|
|
83
188
|
|
|
84
189
|
def estimate_effective_pressure(
|
|
85
190
|
formation_pressure: np.ma.MaskedArray,
|
|
86
191
|
bulk_density: np.ma.MaskedArray,
|
|
87
|
-
reference_overburden_pressure:
|
|
192
|
+
reference_overburden_pressure: np.ma.MaskedArray | float,
|
|
88
193
|
biot_coeff: float = 1.0,
|
|
89
194
|
) -> PressureProperties:
|
|
90
195
|
"""Estimate effective pressure from reference overburden pressure, formation
|
|
@@ -111,14 +216,14 @@ def estimate_effective_pressure(
|
|
|
111
216
|
)
|
|
112
217
|
effective_pressure = reference_overburden_pressure - biot_coeff * formation_pressure
|
|
113
218
|
return PressureProperties(
|
|
114
|
-
formation_pressure=formation_pressure
|
|
115
|
-
effective_pressure=effective_pressure
|
|
116
|
-
overburden_pressure=reference_overburden_pressure
|
|
219
|
+
formation_pressure=formation_pressure,
|
|
220
|
+
effective_pressure=effective_pressure,
|
|
221
|
+
overburden_pressure=reference_overburden_pressure,
|
|
117
222
|
)
|
|
118
223
|
|
|
119
224
|
|
|
120
225
|
def _verify_ovb_press(
|
|
121
|
-
ref_pres:
|
|
226
|
+
ref_pres: float | np.ma.MaskedArray, reference_cube: np.ma.MaskedArray
|
|
122
227
|
) -> np.ma.MaskedArray:
|
|
123
228
|
if isinstance(ref_pres, float):
|
|
124
229
|
return to_masked_array(ref_pres, reference_cube)
|