resqpy 4.14.2__py3-none-any.whl → 5.1.5__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- resqpy/__init__.py +1 -1
- resqpy/fault/_gcs_functions.py +10 -10
- resqpy/fault/_grid_connection_set.py +277 -113
- resqpy/grid/__init__.py +2 -3
- resqpy/grid/_defined_geometry.py +3 -3
- resqpy/grid/_extract_functions.py +2 -1
- resqpy/grid/_grid.py +95 -12
- resqpy/grid/_grid_types.py +22 -7
- resqpy/grid/_points_functions.py +1 -1
- resqpy/grid/_regular_grid.py +6 -2
- resqpy/grid_surface/__init__.py +17 -38
- resqpy/grid_surface/_blocked_well_populate.py +5 -5
- resqpy/grid_surface/_find_faces.py +1349 -253
- resqpy/lines/_polyline.py +24 -33
- resqpy/model/_catalogue.py +9 -0
- resqpy/model/_forestry.py +18 -14
- resqpy/model/_hdf5.py +11 -3
- resqpy/model/_model.py +85 -10
- resqpy/model/_xml.py +38 -13
- resqpy/multi_processing/wrappers/grid_surface_mp.py +92 -37
- resqpy/olio/read_nexus_fault.py +8 -2
- resqpy/olio/relperm.py +1 -1
- resqpy/olio/transmission.py +8 -8
- resqpy/olio/triangulation.py +36 -30
- resqpy/olio/vector_utilities.py +340 -6
- resqpy/olio/volume.py +0 -20
- resqpy/olio/wellspec_keywords.py +19 -13
- resqpy/olio/write_hdf5.py +1 -1
- resqpy/olio/xml_et.py +12 -0
- resqpy/property/__init__.py +6 -4
- resqpy/property/_collection_add_part.py +4 -3
- resqpy/property/_collection_create_xml.py +4 -2
- resqpy/property/_collection_get_attributes.py +4 -0
- resqpy/property/attribute_property_set.py +311 -0
- resqpy/property/grid_property_collection.py +11 -11
- resqpy/property/property_collection.py +79 -31
- resqpy/property/property_common.py +3 -8
- resqpy/rq_import/_add_surfaces.py +34 -14
- resqpy/rq_import/_grid_from_cp.py +2 -2
- resqpy/rq_import/_import_nexus.py +75 -48
- resqpy/rq_import/_import_vdb_all_grids.py +64 -52
- resqpy/rq_import/_import_vdb_ensemble.py +12 -13
- resqpy/surface/_mesh.py +4 -0
- resqpy/surface/_surface.py +593 -118
- resqpy/surface/_tri_mesh.py +13 -10
- resqpy/surface/_tri_mesh_stencil.py +4 -4
- resqpy/surface/_triangulated_patch.py +71 -51
- resqpy/time_series/_any_time_series.py +7 -4
- resqpy/time_series/_geologic_time_series.py +1 -1
- resqpy/unstructured/_hexa_grid.py +6 -2
- resqpy/unstructured/_prism_grid.py +13 -5
- resqpy/unstructured/_pyramid_grid.py +6 -2
- resqpy/unstructured/_tetra_grid.py +6 -2
- resqpy/unstructured/_unstructured_grid.py +6 -2
- resqpy/well/_blocked_well.py +1986 -1946
- resqpy/well/_deviation_survey.py +3 -3
- resqpy/well/_md_datum.py +11 -21
- resqpy/well/_trajectory.py +10 -5
- resqpy/well/_wellbore_frame.py +10 -2
- resqpy/well/blocked_well_frame.py +3 -3
- resqpy/well/well_object_funcs.py +7 -9
- resqpy/well/well_utils.py +33 -0
- {resqpy-4.14.2.dist-info → resqpy-5.1.5.dist-info}/METADATA +8 -9
- {resqpy-4.14.2.dist-info → resqpy-5.1.5.dist-info}/RECORD +66 -66
- {resqpy-4.14.2.dist-info → resqpy-5.1.5.dist-info}/WHEEL +1 -1
- resqpy/grid/_moved_functions.py +0 -15
- {resqpy-4.14.2.dist-info → resqpy-5.1.5.dist-info}/LICENSE +0 -0
@@ -0,0 +1,311 @@
|
|
1
|
+
"""Class handling set of RESQML properties using attribute syntax for properties and their metadata."""
|
2
|
+
|
3
|
+
# Nexus is a registered trademark of the Halliburton Company
|
4
|
+
|
5
|
+
import logging
|
6
|
+
|
7
|
+
log = logging.getLogger(__name__)
|
8
|
+
|
9
|
+
import numpy as np
|
10
|
+
import numpy.ma as ma
|
11
|
+
|
12
|
+
import resqpy.property as rqp
|
13
|
+
|
14
|
+
|
15
|
+
class ApsProperty:
|
16
|
+
"""Class holding a single property with attribute style read access to metadata items."""
|
17
|
+
|
18
|
+
# note: this class could be private to the AttributePropertySet class
|
19
|
+
|
20
|
+
def __init__(self, aps, part):
|
21
|
+
"""Initialise a single property from a property set with attribute style read access to metadata items."""
|
22
|
+
self.aps = aps
|
23
|
+
self._part = part
|
24
|
+
self.key = aps._key(part)
|
25
|
+
|
26
|
+
# NB. the following are read-only attributes
|
27
|
+
|
28
|
+
@property
|
29
|
+
def part(self):
|
30
|
+
"""The part (string) identifier for this property."""
|
31
|
+
return self._part
|
32
|
+
|
33
|
+
@part.setter
|
34
|
+
def part(self, value):
|
35
|
+
self._part = value
|
36
|
+
|
37
|
+
@property
|
38
|
+
def node(self):
|
39
|
+
"""The xml root node for this property."""
|
40
|
+
return self.aps.node_for_part(self.part)
|
41
|
+
|
42
|
+
@property
|
43
|
+
def uuid(self):
|
44
|
+
"""The uuid for this property."""
|
45
|
+
return self.aps.uuid_for_part(self.part)
|
46
|
+
|
47
|
+
@property
|
48
|
+
def array_ref(self):
|
49
|
+
"""The cached numpy array of values for this property."""
|
50
|
+
return self.aps.cached_part_array_ref(self.part)
|
51
|
+
|
52
|
+
@property
|
53
|
+
def values(self):
|
54
|
+
"""A copy of the numpy array of values for this property."""
|
55
|
+
return self.array_ref.copy()
|
56
|
+
|
57
|
+
@property
|
58
|
+
def property_kind(self):
|
59
|
+
"""The property kind of this property."""
|
60
|
+
return self.aps.property_kind_for_part(self.part)
|
61
|
+
|
62
|
+
@property
|
63
|
+
def facet_type(self):
|
64
|
+
"""The facet type for this property (may be None)."""
|
65
|
+
return self.aps.facet_type_for_part(self.part)
|
66
|
+
|
67
|
+
@property
|
68
|
+
def facet(self):
|
69
|
+
"""The facet value for this property (may be None)."""
|
70
|
+
return self.aps.facet_for_part(self.part)
|
71
|
+
|
72
|
+
@property
|
73
|
+
def indexable(self):
|
74
|
+
"""The indexable element for this property (synonymous with indexable_element)."""
|
75
|
+
return self.aps.indexable_for_part(self.part)
|
76
|
+
|
77
|
+
@property
|
78
|
+
def indexable_element(self):
|
79
|
+
"""The indexable element for this property (synonymous with indexable)."""
|
80
|
+
return self.indexable
|
81
|
+
|
82
|
+
@property
|
83
|
+
def is_continuous(self):
|
84
|
+
"""Boolean indicating whether this property is continuous."""
|
85
|
+
return self.aps.continuous_for_part(self.part)
|
86
|
+
|
87
|
+
@property
|
88
|
+
def is_categorical(self):
|
89
|
+
"""Boolean indicating whether this property is categorical."""
|
90
|
+
return self.aps.part_is_categorical(self.part)
|
91
|
+
|
92
|
+
@property
|
93
|
+
def is_discrete(self):
|
94
|
+
"""Boolean indicating whether this property is discrete (False for categorical properties)."""
|
95
|
+
return not (self.is_continuous or self.is_categorical)
|
96
|
+
|
97
|
+
@property
|
98
|
+
def is_points(self):
|
99
|
+
"""Boolean indicating whether this is a points property."""
|
100
|
+
return self.aps.points_for_part(self.part)
|
101
|
+
|
102
|
+
@property
|
103
|
+
def count(self):
|
104
|
+
"""The count (number of sub-elements per element, usually 1) for this property."""
|
105
|
+
return self.aps.count_for_part(self.part)
|
106
|
+
|
107
|
+
@property
|
108
|
+
def uom(self):
|
109
|
+
"""The unit of measure for this property (will be None for discrete or categorical properties)."""
|
110
|
+
return self.aps.uom_for_part(self.part)
|
111
|
+
|
112
|
+
@property
|
113
|
+
def null_value(self):
|
114
|
+
"""The null value for this property (will be None for continuous properties, for which NaN is always the null value)."""
|
115
|
+
return self.aps.null_value_for_part(self.part)
|
116
|
+
|
117
|
+
@property
|
118
|
+
def realization(self):
|
119
|
+
"""The realisation number for this property (may be None)."""
|
120
|
+
return self.aps.realization_for_part(self.part)
|
121
|
+
|
122
|
+
@property
|
123
|
+
def time_index(self):
|
124
|
+
"""The time index for this property (may be None)."""
|
125
|
+
return self.aps.time_index_for_part(self.part)
|
126
|
+
|
127
|
+
@property
|
128
|
+
def title(self):
|
129
|
+
"""The citation title for this property (synonymous with citation_title)."""
|
130
|
+
return self.aps.citation_title_for_part(self.part)
|
131
|
+
|
132
|
+
@property
|
133
|
+
def citation_title(self):
|
134
|
+
"""The citation title for this property (synonymous with title)."""
|
135
|
+
return self.title
|
136
|
+
|
137
|
+
@property
|
138
|
+
def min_value(self):
|
139
|
+
"""The minimum value for this property, as stored in xml metadata."""
|
140
|
+
return self.aps.minimum_value_for_part(self.part)
|
141
|
+
|
142
|
+
@property
|
143
|
+
def max_value(self):
|
144
|
+
"""The maximum value for this property, as stored in xml metadata."""
|
145
|
+
return self.aps.maximum_value_for_part(self.part)
|
146
|
+
|
147
|
+
@property
|
148
|
+
def constant_value(self):
|
149
|
+
"""The constant value for this property, as stored in xml metadata (usually None)."""
|
150
|
+
return self.aps.constant_value_for_part(self.part)
|
151
|
+
|
152
|
+
@property
|
153
|
+
def extra(self):
|
154
|
+
"""The extra metadata for this property (synonymous with extra_metadata)."""
|
155
|
+
return self.aps.extra_metadata_for_part(self.part)
|
156
|
+
|
157
|
+
@property
|
158
|
+
def extra_metadata(self):
|
159
|
+
"""The extra metadata for this property (synonymous with extra)."""
|
160
|
+
return self.extra
|
161
|
+
|
162
|
+
@property
|
163
|
+
def source(self):
|
164
|
+
"""The source extra metadata value for this property (or None)."""
|
165
|
+
return self.aps.source_for_part(self.part)
|
166
|
+
|
167
|
+
@property
|
168
|
+
def support_uuid(self):
|
169
|
+
"""The uuid of the supporting representation for this property."""
|
170
|
+
return self.aps.support_uuid_for_part(self.part)
|
171
|
+
|
172
|
+
@property
|
173
|
+
def string_lookup_uuid(self):
|
174
|
+
"""The uuid of the string lookup table for a categorical property (otherwise None)."""
|
175
|
+
return self.aps.string_lookup_uuid_for_part(self.part)
|
176
|
+
|
177
|
+
@property
|
178
|
+
def time_series_uuid(self):
|
179
|
+
"""The uuid of the time series for this property (may be None)."""
|
180
|
+
return self.aps.time_series_uuid_for_part(self.part)
|
181
|
+
|
182
|
+
@property
|
183
|
+
def local_property_kind_uuid(self):
|
184
|
+
"""The uuid of the local property kind for this property (may be None)."""
|
185
|
+
return self.aps.local_property_kind_uuid(self.part)
|
186
|
+
|
187
|
+
|
188
|
+
class AttributePropertySet(rqp.PropertyCollection):
|
189
|
+
"""Class for set of RESQML properties for any supporting representation, using attribute syntax."""
|
190
|
+
|
191
|
+
def __init__(self,
|
192
|
+
model = None,
|
193
|
+
support = None,
|
194
|
+
property_set_uuid = None,
|
195
|
+
realization = None,
|
196
|
+
key_mode = 'pk',
|
197
|
+
indexable = None,
|
198
|
+
multiple_handling = 'warn'):
|
199
|
+
"""Initialise an empty property set, optionally populate properties from a supporting representation.
|
200
|
+
|
201
|
+
arguments:
|
202
|
+
model (Model, optional): required if property_set_uuid is not None
|
203
|
+
support (optional): a grid.Grid object, or a well.BlockedWell, or a well.WellboreFrame object which belongs to a
|
204
|
+
resqpy.Model which includes associated properties; if this argument is given, and property_set_root is None,
|
205
|
+
the properties in the support's parent model which are for this representation (ie. have this object as the
|
206
|
+
supporting representation) are added to this collection as part of the initialisation
|
207
|
+
property_set_uuid (optional): if present, the collection is populated with the properties defined in the xml tree
|
208
|
+
of the property set
|
209
|
+
realization (integer, optional): if present, the single realisation (within an ensemble) that this collection is for;
|
210
|
+
if None, then the collection is either covering a whole ensemble (individual properties can each be flagged with a
|
211
|
+
realisation number), or is for properties that do not have multiple realizations
|
212
|
+
key_mode (str, default 'pk'): either 'pk' (for property kind) or 'title', identifying the basis of property attribute keys
|
213
|
+
indexable (str, optional): if present and key_mode is 'pk', properties with indexable element other than this will
|
214
|
+
have their indexable element included in their key
|
215
|
+
multiple_handling (str, default 'warn'): either 'ignore', 'warn' ,or 'exception'; if 'warn' or 'ignore', and properties
|
216
|
+
exist that generate the same key, then only the first is visible in the attribute property set (and a warning is given
|
217
|
+
for each of the others in the case of 'warn'); if 'exception', a KeyError is raised if there are any duplicate keys
|
218
|
+
|
219
|
+
note:
|
220
|
+
at present, if the collection is being initialised from a property set, the support argument must also be specified;
|
221
|
+
also for now, if not initialising from a property set, all properties related to the support are included, whether
|
222
|
+
the relationship is supporting representation or some other relationship;
|
223
|
+
|
224
|
+
:meta common:
|
225
|
+
"""
|
226
|
+
|
227
|
+
assert key_mode in ['pk', 'title']
|
228
|
+
assert property_set_uuid is None or model is not None
|
229
|
+
assert support is None or model is None or support.model is model
|
230
|
+
if property_set_uuid is None:
|
231
|
+
property_set_root = None
|
232
|
+
else:
|
233
|
+
property_set_root = model.root_for_uuid(property_set_uuid)
|
234
|
+
assert multiple_handling in ['ignore', 'warn', 'exception']
|
235
|
+
|
236
|
+
super().__init__(support = support, property_set_root = property_set_root, realization = realization)
|
237
|
+
self.key_mode = key_mode
|
238
|
+
self.indexable_mode = indexable
|
239
|
+
self.multiple_handling = multiple_handling
|
240
|
+
self._make_attributes()
|
241
|
+
|
242
|
+
def keys(self):
|
243
|
+
"""Iterator over property keys within the set."""
|
244
|
+
for p in self.parts():
|
245
|
+
yield self._key(p)
|
246
|
+
|
247
|
+
def properties(self):
|
248
|
+
"""Iterator over ApsProperty members of the set."""
|
249
|
+
for k in self.keys():
|
250
|
+
yield getattr(self, k)
|
251
|
+
|
252
|
+
def items(self):
|
253
|
+
"""Iterator over (key, ApsProperty) members of the set."""
|
254
|
+
for k in self.keys():
|
255
|
+
yield (k, getattr(self, k))
|
256
|
+
|
257
|
+
def _key(self, part):
|
258
|
+
"""Returns the key (attribute name) for a given part."""
|
259
|
+
return make_aps_key(self.key_mode,
|
260
|
+
property_kind = self.property_kind_for_part(part),
|
261
|
+
title = self.citation_title_for_part(part),
|
262
|
+
facet = self.facet_for_part(part),
|
263
|
+
time_index = self.time_index_for_part(part),
|
264
|
+
realization = self.realization_for_part(part),
|
265
|
+
indexable_mode = self.indexable_mode,
|
266
|
+
indexable = self.indexable_for_part(part))
|
267
|
+
|
268
|
+
def _make_attributes(self):
|
269
|
+
"""Setup individual properties with attribute style read access to metadata."""
|
270
|
+
for part in self.parts():
|
271
|
+
key = self._key(part)
|
272
|
+
if getattr(self, key, None) is not None:
|
273
|
+
if self.multiple_handling == 'warn':
|
274
|
+
log.warning(f'duplicate key in AttributePropertySet; only first instance included: {key}')
|
275
|
+
continue
|
276
|
+
if self.multiple_handling == 'ignore':
|
277
|
+
continue
|
278
|
+
raise KeyError(f'duplicate key in attribute property set: {key}')
|
279
|
+
aps_property = ApsProperty(self, part)
|
280
|
+
setattr(self, key, aps_property)
|
281
|
+
|
282
|
+
def __len__(self):
|
283
|
+
"""Returns the number of properties in the set."""
|
284
|
+
return self.number_of_parts()
|
285
|
+
|
286
|
+
|
287
|
+
def make_aps_key(key_mode,
|
288
|
+
property_kind = None,
|
289
|
+
title = None,
|
290
|
+
facet = None,
|
291
|
+
time_index = None,
|
292
|
+
realization = None,
|
293
|
+
indexable_mode = None,
|
294
|
+
indexable = None):
|
295
|
+
"""Contructs the key (attribute name) for a property based on metadata items."""
|
296
|
+
if key_mode == 'pk':
|
297
|
+
assert property_kind is not None
|
298
|
+
key = property_kind
|
299
|
+
if indexable_mode is not None and indexable is not None and indexable != indexable_mode:
|
300
|
+
key += f'_{indexable}'
|
301
|
+
if facet is not None:
|
302
|
+
key += f'_{facet}'
|
303
|
+
else:
|
304
|
+
assert title is not None
|
305
|
+
key = title
|
306
|
+
key = key.replace(' ', '_')
|
307
|
+
if time_index is not None:
|
308
|
+
key += f'_t{time_index}'
|
309
|
+
if realization is not None:
|
310
|
+
key += f'_r{realization}'
|
311
|
+
return key
|
@@ -51,11 +51,11 @@ class GridPropertyCollection(rqp.PropertyCollection):
|
|
51
51
|
|
52
52
|
# NB: RESQML documentation is not clear which order is correct; should be kept consistent with same data in fault.py
|
53
53
|
# face_index_map maps from (axis, p01) to face index value in range 0..5
|
54
|
-
#
|
55
|
-
self.face_index_map = np.array([[0, 1], [2, 4], [5, 3]], dtype =
|
54
|
+
# self.face_index_map = np.array([[0, 1], [4, 2], [5, 3]], dtype = np.int8)
|
55
|
+
self.face_index_map = np.array([[0, 1], [2, 4], [5, 3]], dtype = np.int8) # order: top, base, J-, I+, J+, I-
|
56
56
|
# and the inverse, maps from 0..5 to (axis, p01)
|
57
|
-
#
|
58
|
-
self.face_index_inverse_map = np.array([[0, 0], [0, 1], [1, 0], [2, 1], [1, 1], [2, 0]], dtype =
|
57
|
+
# self.face_index_inverse_map = np.array([[0, 0], [0, 1], [1, 1], [2, 1], [1, 0], [2, 0]], dtype = np.int8)
|
58
|
+
self.face_index_inverse_map = np.array([[0, 0], [0, 1], [1, 0], [2, 1], [1, 1], [2, 0]], dtype = np.int8)
|
59
59
|
|
60
60
|
def _copy_support_to_grid_attributes(self):
|
61
61
|
# following three pseudonyms are for backward compatibility
|
@@ -266,11 +266,11 @@ class GridPropertyCollection(rqp.PropertyCollection):
|
|
266
266
|
dtype = None
|
267
267
|
if keyword[0].upper() == 'I' or keyword in ['KID', 'UID', 'UNPACK', 'DAD']:
|
268
268
|
# coerce to integer values (vdb stores integer data as reals!)
|
269
|
-
dtype =
|
269
|
+
dtype = np.int32
|
270
270
|
elif keyword in ['DEADCELL', 'LIVECELL']:
|
271
|
-
dtype =
|
271
|
+
dtype = bool # could use the default dtype of 64 bit integer
|
272
272
|
else:
|
273
|
-
dtype =
|
273
|
+
dtype = np.float32
|
274
274
|
discrete = False
|
275
275
|
import_array = vdbase.grid_static_property(grid_name, keyword, dtype = dtype)
|
276
276
|
assert import_array is not None
|
@@ -335,7 +335,7 @@ class GridPropertyCollection(rqp.PropertyCollection):
|
|
335
335
|
time_index = timestep
|
336
336
|
keyword = keyword.upper()
|
337
337
|
try:
|
338
|
-
import_array = vdbase.grid_recurrent_property_for_timestep(grid_name, keyword, timestep, dtype =
|
338
|
+
import_array = vdbase.grid_recurrent_property_for_timestep(grid_name, keyword, timestep, dtype = np.float32)
|
339
339
|
assert import_array is not None
|
340
340
|
except Exception:
|
341
341
|
# could raise an exception (as for static properties)
|
@@ -489,9 +489,9 @@ class GridPropertyCollection(rqp.PropertyCollection):
|
|
489
489
|
for k0 in range(self.grid.nk):
|
490
490
|
for j0 in range(self.grid.nj):
|
491
491
|
for i0 in range(self.grid.ni):
|
492
|
-
#
|
492
|
+
# if decoarsen_array[k0, j0, i0] < 0:
|
493
493
|
if kid[k0, j0, i0] == 0:
|
494
|
-
#
|
494
|
+
# assert not kid_mask[k0, j0, i0]
|
495
495
|
ke = k0 + 1
|
496
496
|
while ke < self.grid.nk and kid_mask[ke, j0, i0]:
|
497
497
|
ke += 1
|
@@ -824,7 +824,7 @@ def _coarsening_sum(coarsening, a, axis = None):
|
|
824
824
|
return a_coarsened
|
825
825
|
|
826
826
|
|
827
|
-
def _coarsening_weighted_mean(coarsening, a, fine_weight, coarse_weight = None, zero_weight_result = np.
|
827
|
+
def _coarsening_weighted_mean(coarsening, a, fine_weight, coarse_weight = None, zero_weight_result = np.nan):
|
828
828
|
a_coarsened = np.empty(tuple(coarsening.coarse_extent_kji))
|
829
829
|
assert a.shape == tuple(coarsening.fine_extent_kji)
|
830
830
|
assert fine_weight.shape == a.shape
|