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,161 @@
1
+ from pathlib import Path
2
+ from typing import List, Tuple
3
+
4
+ import numpy as np
5
+ import xtgeo
6
+
7
+ from .pem_class_definitions import SimInitProperties, SimRstProperties
8
+ from .pem_config_validation import PemConfig
9
+ from .utils import restore_dir
10
+
11
+
12
+ def read_geogrid(root_dir: Path, config: PemConfig) -> dict:
13
+ """Not in use? Read porosity from geo-grid
14
+
15
+ Args:
16
+ root_dir: start dir for PEM script run
17
+ config: PEM specific parameters
18
+
19
+ Returns:
20
+ Dict object with porosity
21
+ """
22
+ with restore_dir(root_dir.joinpath(config.paths.rel_path_geogrid)):
23
+ return {"poro": xtgeo.gridproperty_from_file("geogrid--phit.roff").values}
24
+
25
+
26
+ def read_init_properties(
27
+ property_file: Path, sim_grid: xtgeo.Grid
28
+ ) -> SimInitProperties:
29
+ """Read initial properties from INIT file
30
+ Args:
31
+ property_file: Full path to the .INIT file
32
+ sim_grid: The simulation grid to use for reading properties
33
+ Returns:
34
+ SimInitProperties: The loaded initial grid properties
35
+ """
36
+ INIT_PROPS = ["PORO", "DEPTH", "NTG"]
37
+ sim_init_props = xtgeo.gridproperties_from_file(
38
+ property_file, fformat="init", names=INIT_PROPS, grid=sim_grid
39
+ )
40
+ props_dict = {
41
+ sim_init_props[name].name.lower(): sim_init_props[name].values
42
+ for name in INIT_PROPS
43
+ }
44
+ return SimInitProperties(**props_dict)
45
+
46
+
47
+ def create_rst_list(
48
+ rst_props: xtgeo.GridProperties,
49
+ seis_dates: List[str],
50
+ rst_prop_names: List[str],
51
+ ) -> List[SimRstProperties]:
52
+ """Create list of SimRstProperties from raw restart properties
53
+ Args:
54
+ rst_props: Raw restart properties
55
+ seis_dates: List of dates to process
56
+ rst_prop_names: List of property names to include
57
+ Returns:
58
+ List[SimRstProperties]: List of processed restart properties by date
59
+ """
60
+ return [
61
+ SimRstProperties(
62
+ **{
63
+ name.lower(): rst_props[name + "_" + date].values
64
+ for name in rst_prop_names
65
+ if name + "_" + date in rst_props.names
66
+ }
67
+ )
68
+ for date in seis_dates
69
+ ]
70
+
71
+
72
+ def read_sim_grid_props(
73
+ egrid_file: Path,
74
+ init_property_file: Path,
75
+ restart_property_file: Path,
76
+ seis_dates: List[str],
77
+ ) -> Tuple[xtgeo.Grid, SimInitProperties, List[SimRstProperties]]:
78
+ """Read grid and properties from simulation run, both initial and restart properties
79
+
80
+ Args:
81
+ egrid_file: Path to the EGRID file
82
+ init_property_file: Path to the INIT file
83
+ restart_property_file: Path to the UNRST file
84
+ seis_dates: List of dates for which to read restart properties
85
+
86
+ Returns:
87
+ sim_grid: grid definition for eclipse input
88
+ init_props: object with initial properties of simulation grid
89
+ rst_list: list with time-dependent simulation properties
90
+ """
91
+ sim_grid = xtgeo.grid_from_file(egrid_file)
92
+
93
+ init_props = read_init_properties(init_property_file, sim_grid)
94
+
95
+ RST_PROPS = ["SWAT", "SGAS", "SOIL", "RS", "RV", "PRESSURE", "SALT"]
96
+
97
+ # Restart properties - set strict to False, False in case RV is not included in
98
+ # the UNRST file
99
+ rst_props = xtgeo.gridproperties_from_file(
100
+ restart_property_file,
101
+ fformat="unrst",
102
+ names=RST_PROPS,
103
+ dates=seis_dates,
104
+ grid=sim_grid,
105
+ strict=(False, False),
106
+ )
107
+
108
+ rst_list = create_rst_list(rst_props, seis_dates, RST_PROPS)
109
+
110
+ return sim_grid, init_props, rst_list
111
+
112
+
113
+ def read_ntg_grid(ntg_grid_file: Path) -> np.ma.MaskedArray:
114
+ """Read PEM specific NTG property
115
+ Args:
116
+ ntg_grid_file: path to the NTG grid file
117
+ Returns:
118
+ net to gross property from simgrid adapted to PEM definition
119
+ """
120
+ return xtgeo.gridproperty_from_file(ntg_grid_file).values
121
+
122
+
123
+ def import_fractions(root_dir: Path, config: PemConfig) -> list:
124
+ """Import volume fractions
125
+
126
+ Args:
127
+ root_dir (str): model directory, relative paths refer to it
128
+ config (PemConfig): configuration file with PEM parameters
129
+
130
+ Returns:
131
+ list: fraction properties
132
+ """
133
+ with restore_dir(
134
+ root_dir.joinpath(config.rock_matrix.volume_fractions.rel_path_fractions)
135
+ ):
136
+ try:
137
+ grd = xtgeo.grid_from_file(
138
+ config.rock_matrix.volume_fractions.fractions_grid_file_name,
139
+ )
140
+ except ValueError as exc:
141
+ raise ImportError(
142
+ f"{__file__}: failed to import volume fractions file "
143
+ f"{config.rock_matrix.volume_fractions.fractions_grid_file_name}"
144
+ ) from exc
145
+ try:
146
+ fracs = config.rock_matrix.fraction_names
147
+ grid_props = [
148
+ xtgeo.gridproperty_from_file(
149
+ file,
150
+ name=name,
151
+ grid=grd,
152
+ )
153
+ for name in fracs
154
+ for file in config.rock_matrix.volume_fractions.fractions_prop_file_names # noqa: E501
155
+ ]
156
+ except ValueError as exc:
157
+ raise ImportError(
158
+ f"{__file__}: failed to import volume fractions files "
159
+ f"{config.rock_matrix.volume_fractions.fractions_prop_file_names}"
160
+ ) from exc
161
+ return [grid_prop.values for grid_prop in grid_props]
@@ -0,0 +1,113 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import Optional
3
+
4
+ import numpy as np
5
+ from numpy.ma import MaskedArray
6
+
7
+
8
+ # Eclipse simulator file classes - SimInitProperties and time step SimRstProperties
9
+ @dataclass
10
+ class SimInitProperties:
11
+ poro: MaskedArray
12
+ depth: MaskedArray
13
+ ntg: MaskedArray
14
+ ntg_pem: Optional[MaskedArray] = None
15
+
16
+ @property
17
+ def delta_z(self) -> MaskedArray:
18
+ """Estimate delta depth in the vertical direction"""
19
+
20
+ def _verify_delta_z(inp_arr: MaskedArray) -> None:
21
+ if not isinstance(inp_arr, MaskedArray) or inp_arr.dtype.kind != "f":
22
+ raise TypeError(
23
+ f"input to estimate_delta_z must be a 3D numpy masked "
24
+ f"array with float data, is {type(inp_arr)}."
25
+ )
26
+ if np.ndim(inp_arr) != 3:
27
+ raise ValueError(
28
+ f"{__file__}: 3-dimensional array must be input to "
29
+ f"estimate_delta_z. Depth difference is calculated along the "
30
+ f"third axis"
31
+ )
32
+ return
33
+
34
+ _verify_delta_z(self.depth)
35
+ d_z = np.zeros_like(self.depth)
36
+ d_z[:, :, 1:] = self.depth.data[:, :, 1:] - self.depth.data[:, :, 0:-1]
37
+ d_z[...] = np.clip(d_z, 0.0, a_max=None)
38
+ delta_z: MaskedArray = MaskedArray(d_z, mask=self.depth.mask)
39
+ return delta_z
40
+
41
+
42
+ @dataclass
43
+ class SimRstProperties:
44
+ swat: MaskedArray
45
+ sgas: MaskedArray
46
+ soil: MaskedArray
47
+ rs: MaskedArray
48
+ pressure: MaskedArray
49
+ temp: MaskedArray | None = None
50
+ rv: MaskedArray | None = None
51
+ salt: MaskedArray | None = None
52
+
53
+
54
+ # Elastic properties for matrix, i.e. mixed minerals and volume fractions
55
+ @dataclass
56
+ class MatrixProperties:
57
+ bulk_modulus: MaskedArray
58
+ shear_modulus: MaskedArray
59
+ dens: MaskedArray
60
+
61
+
62
+ # Separate class for dry rock, i.e. with porosity, can use MatrixProperties as base
63
+ # class
64
+ @dataclass
65
+ class DryRockProperties(MatrixProperties):
66
+ pass
67
+
68
+
69
+ # Acoustic properties for mixed fluids. If non-Newtonian fluids are to be considered,
70
+ # shear modulus and vs must be added
71
+ @dataclass
72
+ class EffectiveFluidProperties:
73
+ bulk_modulus: MaskedArray
74
+ dens: MaskedArray
75
+
76
+ @property
77
+ def vp(self):
78
+ return np.sqrt(self.bulk_modulus / self.dens)
79
+
80
+
81
+ # Pressure properties - overburden, formation and effective (strictly speaking
82
+ # differential) pressure
83
+ @dataclass
84
+ class PressureProperties:
85
+ formation_pressure: MaskedArray
86
+ effective_pressure: MaskedArray
87
+ overburden_pressure: MaskedArray
88
+
89
+
90
+ # Seismic two-way time
91
+ @dataclass
92
+ class TwoWayTime:
93
+ twtpp: MaskedArray
94
+ twtss: MaskedArray
95
+ twtps: MaskedArray
96
+
97
+
98
+ # For isotropic elastic properties, only three independent components are needed
99
+ # to be defined, others can be derived from them, but this construction is needed
100
+ # to have all properties recognised by dataclasses.asdict()
101
+ @dataclass
102
+ class SaturatedRockProperties:
103
+ vp: MaskedArray
104
+ vs: MaskedArray
105
+ dens: MaskedArray
106
+ ai: MaskedArray = field(init=False)
107
+ si: MaskedArray = field(init=False)
108
+ vpvs: MaskedArray = field(init=False)
109
+
110
+ def __post_init__(self):
111
+ self.ai = self.vp * self.dens
112
+ self.si = self.vs * self.dens
113
+ self.vpvs = self.vp / self.vs