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
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
from dataclasses import astuple
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
import xtgeo
|
|
6
|
+
|
|
7
|
+
from .pem_class_definitions import SaturatedRockProperties
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def update_inactive_grid_cells(
|
|
11
|
+
grid: xtgeo.grid3d.Grid,
|
|
12
|
+
props: list[SaturatedRockProperties],
|
|
13
|
+
) -> xtgeo.grid3d.Grid:
|
|
14
|
+
"""
|
|
15
|
+
Update the grid mask based on the mask of the properties
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
grid: original grid
|
|
19
|
+
props: list of saturated rock properties
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
Grid with the same geometry, but with updated mask for inactive cells
|
|
23
|
+
"""
|
|
24
|
+
# Make sure that the 'props' are of type SaturatedRockProperties
|
|
25
|
+
for prop in props:
|
|
26
|
+
if not isinstance(prop, SaturatedRockProperties):
|
|
27
|
+
raise ValueError(
|
|
28
|
+
f"Expected 'props' to be of type SaturatedRockProperties, got "
|
|
29
|
+
f"{type(prop)}"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
grid_mask = grid.get_actnum(asmasked=True)
|
|
33
|
+
|
|
34
|
+
init_mask = np.zeros_like(grid.actnum_array).astype(bool)
|
|
35
|
+
|
|
36
|
+
for prop in props:
|
|
37
|
+
for prop_arr in astuple(prop): # noqa: type
|
|
38
|
+
init_mask = np.logical_or(init_mask, prop_arr.mask.astype(bool))
|
|
39
|
+
|
|
40
|
+
# To match the logic in xtgeo grid actnum, the mask must be inverted
|
|
41
|
+
init_mask = np.logical_not(init_mask)
|
|
42
|
+
|
|
43
|
+
if not np.all(init_mask == grid.actnum_array.astype(bool)):
|
|
44
|
+
warnings.warn(
|
|
45
|
+
f"There are undefined values in PEM results: "
|
|
46
|
+
f"{np.sum(np.logical_xor(init_mask, grid.actnum_array.astype(bool)))} "
|
|
47
|
+
f"cells are added to the model's inactive cells. \nPlease investigate the "
|
|
48
|
+
f"PEM's intermediate and final results for a cause."
|
|
49
|
+
)
|
|
50
|
+
init_mask = np.logical_and(init_mask, grid.actnum_array.astype(bool))
|
|
51
|
+
grid_mask.values = init_mask
|
|
52
|
+
grid.set_actnum(grid_mask)
|
|
53
|
+
|
|
54
|
+
return grid
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from contextlib import contextmanager
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import List, Optional, Tuple, Union
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import xtgeo
|
|
8
|
+
|
|
9
|
+
from .pem_class_definitions import MatrixProperties
|
|
10
|
+
from .pem_config_validation import PemConfig
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@contextmanager
|
|
14
|
+
def restore_dir(path: Path) -> None:
|
|
15
|
+
"""restore_dir run block of code from a given path, restore original path
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
path: path where the call is made from
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
None
|
|
22
|
+
"""
|
|
23
|
+
old_pwd = Path.cwd()
|
|
24
|
+
os.chdir(path)
|
|
25
|
+
try:
|
|
26
|
+
yield
|
|
27
|
+
finally:
|
|
28
|
+
os.chdir(old_pwd)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def to_masked_array(
|
|
32
|
+
value: Union[float, int], masked_array: np.ma.MaskedArray
|
|
33
|
+
) -> np.ma.MaskedArray:
|
|
34
|
+
"""Create a masked array with a constant value from an int or float and a template
|
|
35
|
+
masked array
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
value: constant value for the returned masked array
|
|
39
|
+
masked_array: template for shape and mask of returned masked array
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
constant value masked array
|
|
43
|
+
"""
|
|
44
|
+
return np.ma.MaskedArray(value * np.ones_like(masked_array), mask=masked_array.mask)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def filter_and_one_dim(
|
|
48
|
+
*args: np.ma.MaskedArray, return_numpy_array: bool = False
|
|
49
|
+
) -> tuple[np.ma.MaskedArray, ...]:
|
|
50
|
+
"""Filters multiple masked arrays by removing masked values and flattens them to 1D.
|
|
51
|
+
|
|
52
|
+
Typically used in preparation for calling the rock-physics library.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
*args: One or more masked arrays of identical shape. Each array contains data
|
|
56
|
+
with some values potentially masked as invalid.
|
|
57
|
+
return_numpy_array: If True, returns regular numpy arrays instead of
|
|
58
|
+
masked arrays for the filtered data. Defaults to False.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
tuple containing:
|
|
62
|
+
- mask: Boolean array of the same shape as inputs where True indicates
|
|
63
|
+
positions that were masked in any of the input arrays
|
|
64
|
+
- filtered arrays: One or more 1D arrays containing only the unmasked values
|
|
65
|
+
from each input array, in their original order
|
|
66
|
+
"""
|
|
67
|
+
if not np.all([isinstance(arg, np.ma.MaskedArray) for arg in args]):
|
|
68
|
+
raise ValueError(f"{__file__}: all inputs should be numpy masked arrays")
|
|
69
|
+
mask = args[0].mask
|
|
70
|
+
for i in range(1, len(args)):
|
|
71
|
+
mask = np.logical_or(mask, args[i].mask)
|
|
72
|
+
if return_numpy_array:
|
|
73
|
+
out_args = [arg[~mask].data for arg in args]
|
|
74
|
+
else:
|
|
75
|
+
out_args = [arg[~mask] for arg in args]
|
|
76
|
+
return mask, *out_args
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def reverse_filter_and_restore(
|
|
80
|
+
mask: np.ndarray, *args: np.ndarray
|
|
81
|
+
) -> Tuple[np.ma.MaskedArray, ...]:
|
|
82
|
+
"""Restores 1D filtered arrays back to their original shape with masking.
|
|
83
|
+
|
|
84
|
+
Typically called with results returned from the rock-physics library.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
mask: Boolean array where True indicates positions that should be masked
|
|
88
|
+
in the restored arrays.
|
|
89
|
+
*args: One or more 1D numpy arrays containing the filtered values to be
|
|
90
|
+
restored. Each array should contain exactly enough values to fill
|
|
91
|
+
the unmasked positions in the mask.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
tuple of masked arrays where:
|
|
95
|
+
- Each array has the same shape as the input mask
|
|
96
|
+
- Unmasked positions contain values from the input args
|
|
97
|
+
- Masked positions (where mask is True) contain zeros and are masked
|
|
98
|
+
- All returned arrays share the same mask
|
|
99
|
+
"""
|
|
100
|
+
out_args: list[np.ma.MaskedArray] = []
|
|
101
|
+
for arg in args:
|
|
102
|
+
tmp = np.zeros(mask.shape)
|
|
103
|
+
tmp[~mask] = arg
|
|
104
|
+
out_args.append(np.ma.MaskedArray(tmp, mask=mask))
|
|
105
|
+
|
|
106
|
+
return tuple(out_args)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _verify_export_inputs(props, grid, dates, file_format=None):
|
|
110
|
+
if file_format is not None and file_format not in ["roff", "grdecl"]:
|
|
111
|
+
raise ValueError(
|
|
112
|
+
f'{__file__}: output file format must be one of "roff", "grdecl", is '
|
|
113
|
+
f"{file_format}"
|
|
114
|
+
)
|
|
115
|
+
if not isinstance(grid, xtgeo.grid3d.Grid):
|
|
116
|
+
raise ValueError(
|
|
117
|
+
f"{__file__}: model grid is not an xtgeo 3D grid, type: {type(grid)}"
|
|
118
|
+
)
|
|
119
|
+
if isinstance(props, list):
|
|
120
|
+
if isinstance(dates, list):
|
|
121
|
+
if len(props) == len(dates):
|
|
122
|
+
return props, dates
|
|
123
|
+
raise ValueError(
|
|
124
|
+
f"{__file__}: length of property list does not match the number of "
|
|
125
|
+
f"simulation model "
|
|
126
|
+
f"dates: {len(props)} vs. {len(dates)}"
|
|
127
|
+
)
|
|
128
|
+
if dates is None:
|
|
129
|
+
return props, [""] * len(props)
|
|
130
|
+
raise ValueError(
|
|
131
|
+
f"{__file__}: unknown input type, time_steps should be None or list, is "
|
|
132
|
+
f"{type(dates)}"
|
|
133
|
+
)
|
|
134
|
+
if isinstance(props, dict):
|
|
135
|
+
props = [
|
|
136
|
+
props,
|
|
137
|
+
]
|
|
138
|
+
if dates is None:
|
|
139
|
+
return props, [
|
|
140
|
+
"",
|
|
141
|
+
]
|
|
142
|
+
if isinstance(dates, list) and len(dates) == 1:
|
|
143
|
+
return props, dates
|
|
144
|
+
raise ValueError(
|
|
145
|
+
f"{__file__}: single length property list does not match the number of "
|
|
146
|
+
f"simulation model "
|
|
147
|
+
f"dates: {len(dates)}"
|
|
148
|
+
)
|
|
149
|
+
raise ValueError(
|
|
150
|
+
f"{__file__}: unknown input types, result_props should be list or dict, is "
|
|
151
|
+
f"{type(props)}, time_steps should be None or list, is {type(dates)}"
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def ntg_to_shale_fraction(
|
|
156
|
+
ntg: np.ma.MaskedArray, por: np.ma.MaskedArray
|
|
157
|
+
) -> np.ma.MaskedArray:
|
|
158
|
+
"""Calculate sand and shale fraction from N/G property
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
ntg: net-to-gross property [fraction]
|
|
162
|
+
por: total porosity [fraction]
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
shale fraction
|
|
166
|
+
"""
|
|
167
|
+
clip_ntg: np.ma.MaskedArray = np.ma.clip(np.ma.MaskedArray(ntg), 0.0, 1.0) # type: ignore[assignment]
|
|
168
|
+
vsh: np.ma.MaskedArray = np.ma.MaskedArray(1.0 - clip_ntg)
|
|
169
|
+
return (vsh / (1.0 - por)).clip(0.0, 1.0)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def get_shale_fraction(
|
|
173
|
+
vol_fractions: List[np.ma.MaskedArray],
|
|
174
|
+
fraction_names: list[str],
|
|
175
|
+
shale_fraction_names: Optional[str | list[str]],
|
|
176
|
+
) -> Optional[np.ma.MaskedArray]:
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
vol_fractions: volume fractions, already verified that there is consistency
|
|
181
|
+
between named fractions and available fractions in property file
|
|
182
|
+
fraction_names: names of the volume fractions
|
|
183
|
+
shale_fraction_names: Names of fractions that should be considered shale
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
sum of volume fractions that are defined as shale, None if there are no defined
|
|
187
|
+
shale fractions
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
if not shale_fraction_names:
|
|
191
|
+
return None
|
|
192
|
+
|
|
193
|
+
if isinstance(shale_fraction_names, str):
|
|
194
|
+
shale_fraction_names = [shale_fraction_names]
|
|
195
|
+
|
|
196
|
+
sh_list: list[np.ma.MaskedArray] = []
|
|
197
|
+
for shale_name in shale_fraction_names:
|
|
198
|
+
try:
|
|
199
|
+
idx = fraction_names.index(shale_name)
|
|
200
|
+
sh_list.append(vol_fractions[idx])
|
|
201
|
+
except ValueError:
|
|
202
|
+
raise ValueError(f"unknown shale fraction: {shale_name}")
|
|
203
|
+
|
|
204
|
+
# Note that masked elements are set to 0 internally.
|
|
205
|
+
return np.ma.sum(sh_list, axis=0)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def estimate_cement(
|
|
209
|
+
bulk_modulus: float | int,
|
|
210
|
+
shear_modulus: float | int,
|
|
211
|
+
density: float | int,
|
|
212
|
+
grid: np.ma.MaskedArray,
|
|
213
|
+
) -> MatrixProperties:
|
|
214
|
+
"""Creates masked arrays filled with constant cement properties, matching the shape
|
|
215
|
+
and mask of the input grid.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
bulk_modulus: Bulk modulus of the cement
|
|
219
|
+
shear_modulus: Shear modulus of the cement
|
|
220
|
+
density: Density of the cement
|
|
221
|
+
grid: Template array that defines the shape and mask for the output arrays
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
cement properties as MatrixProperties containing constant-valued masked arrays
|
|
225
|
+
"""
|
|
226
|
+
cement_k = to_masked_array(bulk_modulus, grid)
|
|
227
|
+
cement_mu = to_masked_array(shear_modulus, grid)
|
|
228
|
+
cement_rho = to_masked_array(density, grid)
|
|
229
|
+
return MatrixProperties(
|
|
230
|
+
bulk_modulus=cement_k, shear_modulus=cement_mu, dens=cement_rho
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def update_dict_list(base_list: List[dict], add_list: List[dict]) -> List[dict]:
|
|
235
|
+
"""Update/add new key/value pairs to dicts in list
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
base_list: original list of dicts
|
|
239
|
+
add_list: list of dicts to be added
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
combined list of dicts
|
|
243
|
+
"""
|
|
244
|
+
_verify_update_inputs(base_list, add_list)
|
|
245
|
+
for i, item in enumerate(add_list):
|
|
246
|
+
base_list[i].update(item)
|
|
247
|
+
return base_list
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _verify_update_inputs(base, add_list):
|
|
251
|
+
if not isinstance(base, list) and isinstance(add_list, list):
|
|
252
|
+
raise TypeError(f"{__file__}: inputs are not lists")
|
|
253
|
+
if not len(base) == len(add_list):
|
|
254
|
+
raise ValueError(
|
|
255
|
+
f"{__file__}: mismatch in list lengths: base list: {len(base)} vs. added "
|
|
256
|
+
f"list: {len(add_list)}"
|
|
257
|
+
)
|
|
258
|
+
if not (
|
|
259
|
+
all(isinstance(item, dict) for item in base)
|
|
260
|
+
and all(isinstance(item, dict) for item in add_list)
|
|
261
|
+
):
|
|
262
|
+
raise TypeError(f"{__file__}: all items in input lists are not dict")
|
fmu/pem/run_pem.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from fmu.pem import pem_functions as pem_fcns
|
|
5
|
+
from fmu.pem import pem_utilities as pem_utils
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def pem_fcn(
|
|
9
|
+
start_dir: Path,
|
|
10
|
+
rel_path_pem: Path,
|
|
11
|
+
pem_config_file_name: Path,
|
|
12
|
+
run_from_rms=False,
|
|
13
|
+
proj=None,
|
|
14
|
+
) -> None:
|
|
15
|
+
"""
|
|
16
|
+
Run script for extended petro elastic module within sim2seis. Parameters in
|
|
17
|
+
yaml-file control the selections made in the PEM.
|
|
18
|
+
|
|
19
|
+
"""
|
|
20
|
+
# Read and validate all PEM parameters
|
|
21
|
+
config = pem_utils.read_pem_config(
|
|
22
|
+
start_dir.joinpath(rel_path_pem, pem_config_file_name)
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# Read necessary part of global configurations and parameters
|
|
26
|
+
config.update_with_global(
|
|
27
|
+
pem_utils.get_global_params_and_dates(
|
|
28
|
+
start_dir, config.paths.rel_path_fmu_config
|
|
29
|
+
)
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Import Eclipse simulation grid - INIT and RESTART
|
|
33
|
+
egrid_file = start_dir / config.paths.rel_path_simgrid / "ECLIPSE.EGRID"
|
|
34
|
+
init_property_file = start_dir / config.paths.rel_path_simgrid / "ECLIPSE.INIT"
|
|
35
|
+
restart_property_file = start_dir / config.paths.rel_path_simgrid / "ECLIPSE.UNRST"
|
|
36
|
+
|
|
37
|
+
sim_grid, constant_props, time_step_props = pem_utils.read_sim_grid_props(
|
|
38
|
+
egrid_file,
|
|
39
|
+
init_property_file,
|
|
40
|
+
restart_property_file,
|
|
41
|
+
config.global_params.seis_dates,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# Calculate rock properties - fluids and minerals
|
|
45
|
+
# Fluid properties calculated for all time-steps
|
|
46
|
+
fluid_properties = pem_fcns.effective_fluid_properties(
|
|
47
|
+
time_step_props, config.fluids
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Effective mineral (matrix) properties - one set valid for all time-steps
|
|
51
|
+
vsh, matrix_properties = pem_fcns.effective_mineral_properties(
|
|
52
|
+
start_dir, config, constant_props
|
|
53
|
+
)
|
|
54
|
+
# VSH is exported with other constant results, add it to the constant properties
|
|
55
|
+
constant_props.ntg_pem = vsh
|
|
56
|
+
|
|
57
|
+
# Estimate effective pressure
|
|
58
|
+
eff_pres = pem_fcns.estimate_pressure(
|
|
59
|
+
config, constant_props, time_step_props, matrix_properties, fluid_properties
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Estimate saturated rock properties
|
|
63
|
+
sat_rock_props = pem_fcns.estimate_saturated_rock(
|
|
64
|
+
config, constant_props, eff_pres, matrix_properties, fluid_properties
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Delta and cumulative time estimates (only TWT properties are kept)
|
|
68
|
+
sum_delta_time = pem_utils.delta_cumsum_time.estimate_sum_delta_time(
|
|
69
|
+
constant_props, sat_rock_props, config
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Calculate difference properties. Possible properties are all that vary with time
|
|
73
|
+
diff_props, diff_date_strs = pem_utils.calculate_diff_properties(
|
|
74
|
+
[time_step_props, eff_pres, sat_rock_props, sum_delta_time], config
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# As a precaution, update the grid mask for inactive cells, based on the saturated
|
|
78
|
+
# rock properties
|
|
79
|
+
sim_grid = pem_utils.update_inactive_grid_cells(sim_grid, sat_rock_props)
|
|
80
|
+
|
|
81
|
+
# Save results to disk or RMS project according to settings in the PEM config
|
|
82
|
+
pem_utils.save_results(
|
|
83
|
+
start_dir,
|
|
84
|
+
run_from_rms,
|
|
85
|
+
config,
|
|
86
|
+
proj,
|
|
87
|
+
sim_grid,
|
|
88
|
+
eff_pres,
|
|
89
|
+
sat_rock_props,
|
|
90
|
+
diff_props,
|
|
91
|
+
diff_date_strs,
|
|
92
|
+
matrix_properties,
|
|
93
|
+
fluid_properties,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Restore original path
|
|
97
|
+
os.chdir(start_dir)
|
|
98
|
+
return
|
fmu/pem/version.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
|
5
|
+
|
|
6
|
+
TYPE_CHECKING = False
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from typing import Tuple
|
|
9
|
+
from typing import Union
|
|
10
|
+
|
|
11
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
12
|
+
else:
|
|
13
|
+
VERSION_TUPLE = object
|
|
14
|
+
|
|
15
|
+
version: str
|
|
16
|
+
__version__: str
|
|
17
|
+
__version_tuple__: VERSION_TUPLE
|
|
18
|
+
version_tuple: VERSION_TUPLE
|
|
19
|
+
|
|
20
|
+
__version__ = version = '0.0.1'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 0, 1)
|