resqpy 4.14.2__py3-none-any.whl → 5.1.6__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 (67) hide show
  1. resqpy/__init__.py +1 -1
  2. resqpy/fault/_gcs_functions.py +10 -10
  3. resqpy/fault/_grid_connection_set.py +277 -113
  4. resqpy/grid/__init__.py +2 -3
  5. resqpy/grid/_defined_geometry.py +3 -3
  6. resqpy/grid/_extract_functions.py +8 -2
  7. resqpy/grid/_grid.py +95 -12
  8. resqpy/grid/_grid_types.py +22 -7
  9. resqpy/grid/_points_functions.py +1 -1
  10. resqpy/grid/_regular_grid.py +6 -2
  11. resqpy/grid_surface/__init__.py +17 -38
  12. resqpy/grid_surface/_blocked_well_populate.py +5 -5
  13. resqpy/grid_surface/_find_faces.py +1413 -253
  14. resqpy/lines/_polyline.py +24 -33
  15. resqpy/model/_catalogue.py +9 -0
  16. resqpy/model/_forestry.py +18 -14
  17. resqpy/model/_hdf5.py +11 -3
  18. resqpy/model/_model.py +85 -10
  19. resqpy/model/_xml.py +38 -13
  20. resqpy/multi_processing/wrappers/grid_surface_mp.py +92 -37
  21. resqpy/olio/read_nexus_fault.py +8 -2
  22. resqpy/olio/relperm.py +1 -1
  23. resqpy/olio/transmission.py +8 -8
  24. resqpy/olio/triangulation.py +36 -30
  25. resqpy/olio/vector_utilities.py +340 -6
  26. resqpy/olio/volume.py +0 -20
  27. resqpy/olio/wellspec_keywords.py +19 -13
  28. resqpy/olio/write_hdf5.py +1 -1
  29. resqpy/olio/xml_et.py +12 -0
  30. resqpy/property/__init__.py +6 -4
  31. resqpy/property/_collection_add_part.py +4 -3
  32. resqpy/property/_collection_create_xml.py +4 -2
  33. resqpy/property/_collection_get_attributes.py +4 -0
  34. resqpy/property/attribute_property_set.py +311 -0
  35. resqpy/property/grid_property_collection.py +11 -11
  36. resqpy/property/property_collection.py +79 -31
  37. resqpy/property/property_common.py +3 -8
  38. resqpy/rq_import/_add_surfaces.py +34 -14
  39. resqpy/rq_import/_grid_from_cp.py +2 -2
  40. resqpy/rq_import/_import_nexus.py +75 -48
  41. resqpy/rq_import/_import_vdb_all_grids.py +64 -52
  42. resqpy/rq_import/_import_vdb_ensemble.py +12 -13
  43. resqpy/surface/_mesh.py +4 -0
  44. resqpy/surface/_surface.py +593 -118
  45. resqpy/surface/_tri_mesh.py +13 -10
  46. resqpy/surface/_tri_mesh_stencil.py +4 -4
  47. resqpy/surface/_triangulated_patch.py +71 -51
  48. resqpy/time_series/_any_time_series.py +7 -4
  49. resqpy/time_series/_geologic_time_series.py +1 -1
  50. resqpy/unstructured/_hexa_grid.py +6 -2
  51. resqpy/unstructured/_prism_grid.py +13 -5
  52. resqpy/unstructured/_pyramid_grid.py +6 -2
  53. resqpy/unstructured/_tetra_grid.py +6 -2
  54. resqpy/unstructured/_unstructured_grid.py +6 -2
  55. resqpy/well/_blocked_well.py +1986 -1946
  56. resqpy/well/_deviation_survey.py +3 -3
  57. resqpy/well/_md_datum.py +11 -21
  58. resqpy/well/_trajectory.py +10 -5
  59. resqpy/well/_wellbore_frame.py +10 -2
  60. resqpy/well/blocked_well_frame.py +3 -3
  61. resqpy/well/well_object_funcs.py +7 -9
  62. resqpy/well/well_utils.py +33 -0
  63. {resqpy-4.14.2.dist-info → resqpy-5.1.6.dist-info}/METADATA +8 -9
  64. {resqpy-4.14.2.dist-info → resqpy-5.1.6.dist-info}/RECORD +66 -66
  65. {resqpy-4.14.2.dist-info → resqpy-5.1.6.dist-info}/WHEEL +1 -1
  66. resqpy/grid/_moved_functions.py +0 -15
  67. {resqpy-4.14.2.dist-info → resqpy-5.1.6.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
- # self.face_index_map = np.array([[0, 1], [4, 2], [5, 3]], dtype = int)
55
- self.face_index_map = np.array([[0, 1], [2, 4], [5, 3]], dtype = int) # order: top, base, J-, I+, J+, I-
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
- # self.face_index_inverse_map = np.array([[0, 0], [0, 1], [1, 1], [2, 1], [1, 0], [2, 0]], dtype = int)
58
- self.face_index_inverse_map = np.array([[0, 0], [0, 1], [1, 0], [2, 1], [1, 1], [2, 0]], dtype = int)
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 = 'int32'
269
+ dtype = np.int32
270
270
  elif keyword in ['DEADCELL', 'LIVECELL']:
271
- dtype = 'bool' # could use the default dtype of 64 bit integer
271
+ dtype = bool # could use the default dtype of 64 bit integer
272
272
  else:
273
- dtype = 'float' # convert to 64 bit; could omit but RESQML states 64 bit
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 = 'float')
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
- # if decoarsen_array[k0, j0, i0] < 0:
492
+ # if decoarsen_array[k0, j0, i0] < 0:
493
493
  if kid[k0, j0, i0] == 0:
494
- # assert not kid_mask[k0, j0, i0]
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.NaN):
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