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.
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 +2 -1
  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 +1349 -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.5.dist-info}/METADATA +8 -9
  64. {resqpy-4.14.2.dist-info → resqpy-5.1.5.dist-info}/RECORD +66 -66
  65. {resqpy-4.14.2.dist-info → resqpy-5.1.5.dist-info}/WHEEL +1 -1
  66. resqpy/grid/_moved_functions.py +0 -15
  67. {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
- # 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