resqpy 4.14.2__py3-none-any.whl → 5.1.5__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 +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
resqpy/lines/_polyline.py CHANGED
@@ -25,25 +25,22 @@ class Polyline(rql_c._BasePolyline):
25
25
 
26
26
  resqml_type = 'PolylineRepresentation'
27
27
 
28
- def __init__(
29
- self,
30
- parent_model,
31
- uuid = None,
32
- set_bool = None, #: DEPRECATED
33
- set_coord = None,
34
- set_crs = None,
35
- is_closed = None,
36
- title = None,
37
- rep_int_root = None,
38
- originator = None,
39
- extra_metadata = None):
28
+ def __init__(self,
29
+ parent_model,
30
+ uuid = None,
31
+ set_coord = None,
32
+ set_crs = None,
33
+ is_closed = None,
34
+ title = None,
35
+ rep_int_root = None,
36
+ originator = None,
37
+ extra_metadata = None):
40
38
  """Initialises a new polyline object.
41
39
 
42
40
  arguments:
43
41
  parent_model (model.Model object): the model which the new PolylineRepresentation belongs to
44
42
  uuid (uuid.UUID, optional): the uuid of an existing RESQML PolylineRepresentation from which
45
43
  to initialise the resqpy Polyline
46
- set_bool (boolean, optional): DEPRECATED: synonym for is_closed argument
47
44
  set_coord (numpy float array, optional): an ordered set of xyz values used to define a new polyline;
48
45
  last dimension of array must have extent 3; ignored if uuid is not None
49
46
  set_crs (uuid.UUID, optional): the uuid of a crs to be used when initialising from coordinates;
@@ -65,10 +62,6 @@ class Polyline(rql_c._BasePolyline):
65
62
  """
66
63
 
67
64
  self.model = parent_model
68
- if set_bool is not None:
69
- warnings.warn('DEPRECATED: use is_closed argument instead of set_bool, in Polyline initialisation')
70
- if is_closed is None:
71
- is_closed = set_bool
72
65
  self.isclosed = is_closed
73
66
  self.nodepatch = None
74
67
  self.crs_uuid = set_crs
@@ -466,22 +459,20 @@ class Polyline(rql_c._BasePolyline):
466
459
  if cache and self.centre is not None:
467
460
  return self.centre
468
461
  assert mode in ['weighted', 'sampled']
469
- if mode == 'sampled': # this mode is deprecated as it simply approximates the weighted mode
470
- sample_points = self.equidistant_points(n, in_xy = in_xy)
471
- centre = np.mean(sample_points, axis = 0)
472
- else: # 'weighted'
473
- sum = np.zeros(3)
474
- seg_count = len(self.coordinates) - 1
475
- if self.isclosed:
476
- seg_count += 1
477
- d = 2 if in_xy else 3
478
- p1 = np.zeros(3)
479
- p2 = np.zeros(3)
480
- for seg_index in range(seg_count):
481
- successor = (seg_index + 1) % len(self.coordinates)
482
- p1[:d], p2[:d] = self.coordinates[seg_index, :d], self.coordinates[successor, :d]
483
- sum += (p1 + p2) * vu.naive_length(p2 - p1)
484
- centre = sum / (2.0 * self.full_length(in_xy = in_xy))
462
+ if mode != 'weighted': # ignore any other mode, ie. sampled
463
+ warnings.warn('DEPRECATED: weighted mode is only mode now supported for Polyline.balanced_centre()')
464
+ sum = np.zeros(3)
465
+ seg_count = len(self.coordinates) - 1
466
+ if self.isclosed:
467
+ seg_count += 1
468
+ d = 2 if in_xy else 3
469
+ p1 = np.zeros(3)
470
+ p2 = np.zeros(3)
471
+ for seg_index in range(seg_count):
472
+ successor = (seg_index + 1) % len(self.coordinates)
473
+ p1[:d], p2[:d] = self.coordinates[seg_index, :d], self.coordinates[successor, :d]
474
+ sum += (p1 + p2) * vu.naive_length(p2 - p1)
475
+ centre = sum / (2.0 * self.full_length(in_xy = in_xy))
485
476
  if cache:
486
477
  self.centre = centre
487
478
  return centre
@@ -553,6 +553,15 @@ def _citation_title_for_part(model, part): # duplicate functionality to title_f
553
553
  return title
554
554
 
555
555
 
556
+ def _source_for_part(model, part):
557
+ """Returns the source string from the part's extra metadata, if present, else None."""
558
+
559
+ part_extra = rqet.load_metadata_from_xml(_root_for_part(model, part))
560
+ if not part_extra:
561
+ return None
562
+ return part_extra.get('source')
563
+
564
+
556
565
  def _root_for_time_series(model, uuid = None):
557
566
  """Return root for time series part."""
558
567
 
resqpy/model/_forestry.py CHANGED
@@ -200,8 +200,8 @@ def _load_epc(model, epc_file, full_load = True, epc_subdir = None, copy_from =
200
200
  def _add_uuid_soft_relations(model, uuid_int, part):
201
201
  if "EpcExternalPart" in part:
202
202
  return
203
- value = model.rels_forest.get(rqet.rels_part_name_for_part(part))
204
- if value is not None:
203
+ rels_part = rqet.rels_part_name_for_part(part)
204
+ if rels_part in model.rels_forest:
205
205
  rels_root = m_c._root_for_part(model, rqet.rels_part_name_for_part(part), is_rels = True)
206
206
  if rels_root is not None:
207
207
  for relation_node in rels_root:
@@ -214,9 +214,10 @@ def _add_uuid_soft_relations(model, uuid_int, part):
214
214
  if relation_uuid_str is None:
215
215
  return # probably HDF5 external resource
216
216
  relation_uuid_int = _hex_to_int(relation_uuid_str)
217
- value = model.uuid_rels_dict.get(uuid_int)
218
- if value is not None and relation_uuid_int not in value[0] and relation_uuid_int not in value[1]:
219
- value[2].add(relation_uuid_int)
217
+ rels_sets = model.uuid_rels_dict.get(uuid_int)
218
+ if rels_sets is not None and relation_uuid_int not in rels_sets[
219
+ 0] and relation_uuid_int not in rels_sets[1]:
220
+ rels_sets[2].add(relation_uuid_int)
220
221
 
221
222
 
222
223
  def _add_uuid_relations(model, uuid_int, part):
@@ -685,17 +686,23 @@ def _copy_referenced_parts(model, other_model, realization, consolidate, force,
685
686
  resident_uuid_int = model.consolidation.map[ref_uuid_int]
686
687
  assert resident_uuid_int is not None
687
688
  # find referring node for ref_uuid_int and modify its reference to resident_uuid_int
688
- if reference_node_dict is None:
689
+ if reference_node_dict is None: # now mapping uuid int to list of nodes
689
690
  ref_nodes = rqet.list_obj_references(root_node)
690
691
  reference_node_dict = {}
691
692
  for ref_node in ref_nodes:
692
693
  uuid_node = rqet.find_tag(ref_node, 'UUID')
693
694
  uuid_int = bu.uuid_from_string(uuid_node.text).int
694
- reference_node_dict[uuid_int] = uuid_node
695
- uuid_node = reference_node_dict[ref_uuid_int]
696
- uuid_node.text = str(bu.uuid_from_int(resident_uuid_int))
697
- reference_node_dict.pop(ref_uuid_int)
698
- reference_node_dict[resident_uuid_int] = uuid_node
695
+ if uuid_int in reference_node_dict:
696
+ reference_node_dict[uuid_int].append(uuid_node)
697
+ else:
698
+ reference_node_dict[uuid_int] = [uuid_node]
699
+ uuid_node_list = reference_node_dict.pop(ref_uuid_int)
700
+ for uuid_node in uuid_node_list:
701
+ uuid_node.text = str(bu.uuid_from_int(resident_uuid_int))
702
+ if resident_uuid_int in reference_node_dict:
703
+ reference_node_dict[resident_uuid_int] += uuid_node_list
704
+ else:
705
+ reference_node_dict[resident_uuid_int] = uuid_node_list
699
706
 
700
707
 
701
708
  def _copy_relationships_for_present_targets(model, other_model, consolidate, force, resident_uuid, root_node):
@@ -719,9 +726,6 @@ def _copy_relationships_for_present_targets(model, other_model, consolidate, for
719
726
  continue
720
727
  else:
721
728
  continue
722
- if not force and resident_related_part in m_c._parts_list_filtered_by_related_uuid(
723
- model, m_c._list_of_parts(model), resident_uuid):
724
- continue
725
729
  related_node = m_c._root_for_part(model, resident_related_part)
726
730
  assert related_node is not None
727
731
 
resqpy/model/_hdf5.py CHANGED
@@ -262,11 +262,11 @@ def _h5_array_element(model,
262
262
  if dtype is None:
263
263
  return result
264
264
  if result.size == 1:
265
- if dtype is float or (isinstance(dtype, str) and dtype.startswith('float')):
265
+ if dtype is float or (isinstance(dtype, str) and ('float' in dtype)):
266
266
  return float(result)
267
- elif dtype is int or (isinstance(dtype, str) and dtype.startswith('int')):
267
+ elif dtype is int or (isinstance(dtype, str) and ('int' in dtype)):
268
268
  return int(result)
269
- elif dtype is bool or (isinstance(dtype, str) and dtype.startswith('bool')):
269
+ elif dtype is bool or (isinstance(dtype, str) and ('bool' in dtype)):
270
270
  return bool(result)
271
271
  return np.array(result, dtype = dtype)
272
272
 
@@ -286,6 +286,14 @@ def _h5_overwrite_array_slice(model, h5_key_pair, slice_tuple, array_slice):
286
286
  dset[slice_tuple] = array_slice
287
287
 
288
288
 
289
+ def _h5_overwrite_array(model, h5_key_pair, array):
290
+ """Overwrites (updates) the whole of an hdf5 array."""
291
+
292
+ h5_root = _h5_access(model, h5_key_pair[0], mode = 'a')
293
+ dset = h5_root[h5_key_pair[1]]
294
+ dset[...] = array
295
+
296
+
289
297
  def h5_clear_filename_cache(model):
290
298
  """Clears the cached filenames associated with all ext uuids."""
291
299
 
resqpy/model/_model.py CHANGED
@@ -1108,6 +1108,66 @@ class Model():
1108
1108
 
1109
1109
  return m_c._citation_title_for_part(self, part)
1110
1110
 
1111
+ def source_for_part(self, part):
1112
+ """Returns the source string from the part's extra metadata, if present, else None.
1113
+
1114
+ arguments:
1115
+ part (str): the part for which the source information is required
1116
+
1117
+ returns:
1118
+ str being the text of the source field in the xml extra metadata of the part, or None
1119
+ """
1120
+
1121
+ return m_c._source_for_part(self, part)
1122
+
1123
+ def set_source_for_part(self, part, source):
1124
+ """Sets the source string in the part's extra metadata.
1125
+
1126
+ arguments:
1127
+ part (str): the part for which the source information is to be set
1128
+ source (str): text for the extra metadata source item
1129
+
1130
+ notes:
1131
+ this function adds the source item to the in-memory xml extra metadata;
1132
+ any previous text for the source item (if present) will be replaced;
1133
+ it will be included in the epc if store_epc() is subsequently called
1134
+ """
1135
+
1136
+ m_x._create_source(source, root = m_c._root_for_part(self, part))
1137
+ self.set_modified()
1138
+
1139
+ def source_for_obj(self, obj):
1140
+ """Returns the source string from the object's extra metadata, if present, else None.
1141
+
1142
+ arguments:
1143
+ obj (BaseResqpy): any high level resqpy object (eg. Surface)
1144
+
1145
+ returns:
1146
+ str being the text of the source extra metadata item for the object, or None
1147
+ """
1148
+
1149
+ return m_c._source_for_part(self, obj.part)
1150
+
1151
+ def set_source_for_obj(self, obj, source):
1152
+ """Sets the source string in the object's extra metadata.
1153
+
1154
+ arguments:
1155
+ part (str): the part for which the source information is to be set
1156
+ source (str): text for the extra metadata source item
1157
+
1158
+ notes:
1159
+ this function adds the source item to the in-memory xml extra metadata as well as
1160
+ the object's extra_metadata dictionary
1161
+ any previous text for the source item (if present) will be replaced;
1162
+ it will be included in the epc if store_epc() is subsequently called
1163
+ """
1164
+
1165
+ m_x._create_source(source, root = obj.root)
1166
+ if not hasattr(obj, 'extra_metadata') or obj.extra_metadata is None:
1167
+ obj.extra_metadata = {}
1168
+ obj.extra_metadata['source'] = str(source)
1169
+ self.set_modified()
1170
+
1111
1171
  def root_for_time_series(self, uuid = None):
1112
1172
  """Return root for time series part.
1113
1173
 
@@ -1280,7 +1340,8 @@ class Model():
1280
1340
  an hdf5 file name is cached once determined for a given ext uuid; to clear the cache,
1281
1341
  call the h5_clear_filename_cache() method
1282
1342
  """
1283
-
1343
+ if isinstance(override, bool):
1344
+ warnings.warn('DEPRECATED: boolean override argument to Model.h5_file_name(); use string instead')
1284
1345
  return m_h._h5_file_name(self, uuid = uuid, override = override, file_must_exist = file_must_exist)
1285
1346
 
1286
1347
  def h5_access(self, uuid = None, mode = 'r', override = 'default', file_path = None):
@@ -1306,7 +1367,8 @@ class Model():
1306
1367
  an exception will be raised if the hdf5 file cannot be opened; note that sometimes another
1307
1368
  piece of code accessing the file might cause a 'resource unavailable' exception
1308
1369
  """
1309
-
1370
+ if isinstance(override, bool):
1371
+ warnings.warn('DEPRECATED: boolean override argument to Model.h5_access(); use string instead')
1310
1372
  return m_h._h5_access(self, uuid = uuid, mode = mode, override = override, file_path = file_path)
1311
1373
 
1312
1374
  def h5_release(self):
@@ -1339,7 +1401,7 @@ class Model():
1339
1401
  cache_array = False,
1340
1402
  object = None,
1341
1403
  array_attribute = None,
1342
- dtype = 'float',
1404
+ dtype = None,
1343
1405
  required_shape = None):
1344
1406
  """Returns one element from an hdf5 array and/or caches the array.
1345
1407
 
@@ -1415,6 +1477,21 @@ class Model():
1415
1477
 
1416
1478
  return m_h._h5_overwrite_array_slice(self, h5_key_pair, slice_tuple, array_slice)
1417
1479
 
1480
+ def h5_overwrite_array(self, h5_key_pair, array):
1481
+ """Overwrites (updates) the whole of an hdf5 array.
1482
+
1483
+ arguments:
1484
+ h5_key_pair (uuid, string): the uuid of the hdf5 ext part and the hdf5 internal path to the
1485
+ required hdf5 array
1486
+ array (numpy array of shape to match existing hdf5 dataset): the data to write
1487
+
1488
+ notes:
1489
+ this method naively updates an hdf5 array without using mpi to look after parallel updates;
1490
+ metadata (such as uuid or property min, max values) is not modified in any way by the method
1491
+ """
1492
+
1493
+ return m_h._h5_overwrite_array(self, h5_key_pair, array)
1494
+
1418
1495
  def h5_clear_filename_cache(self):
1419
1496
  """Clears the cached filenames associated with all ext uuids."""
1420
1497
 
@@ -2023,6 +2100,11 @@ class Model():
2023
2100
  if other_model is self:
2024
2101
  return part
2025
2102
  assert part is not None
2103
+ # check whether already existing in this model
2104
+ if part in self.parts_forest.keys():
2105
+ return part
2106
+ if m_c._type_of_part(other_model, part) == 'obj_EpcExternalPartReference':
2107
+ return None
2026
2108
  if realization is not None:
2027
2109
  assert isinstance(realization, int) and realization >= 0
2028
2110
  if force:
@@ -2033,13 +2115,6 @@ class Model():
2033
2115
  self_h5_file_name = self.h5_file_name(file_must_exist = False)
2034
2116
  hdf5_copy_needed = not os.path.samefile(self_h5_file_name, other_h5_file_name)
2035
2117
 
2036
- # check whether already existing in this model
2037
- if part in self.parts_forest.keys():
2038
- return part
2039
-
2040
- if m_c._type_of_part(other_model, part) == 'obj_EpcExternalPartReference':
2041
- return None
2042
-
2043
2118
  return m_f._copy_part_from_other_model(self,
2044
2119
  other_model,
2045
2120
  part,
resqpy/model/_xml.py CHANGED
@@ -420,22 +420,39 @@ def _create_supporting_representation(model,
420
420
 
421
421
 
422
422
  def _create_source(source, root = None):
423
- """Create an extra meta data node holding information on the source of the data, optionally add to root."""
423
+ """Create an extra meta data node holding information on the source of the data, optionally add to root.
424
424
 
425
- emd_node = rqet.Element(ns['resqml2'] + 'ExtraMetadata')
426
- emd_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'NameValuePair')
427
- emd_node.text = rqet.null_xml_text
425
+ note:
426
+ if the root already contains a 'source' extra metadata item, its text field is updated and the
427
+ existing extra metadata xml node is returned
428
+ """
428
429
 
429
- name_node = rqet.SubElement(emd_node, ns['resqml2'] + 'Name')
430
- name_node.set(ns['xsi'] + 'type', ns['xsd'] + 'string')
431
- name_node.text = 'source'
430
+ emd_node = None
431
+ if root is not None:
432
+ emd_node = rqet.find_metadata_item_node_in_xml(root, 'source')
432
433
 
433
- value_node = rqet.SubElement(emd_node, ns['resqml2'] + 'Value')
434
- value_node.set(ns['xsi'] + 'type', ns['xsd'] + 'string')
435
- value_node.text = source
434
+ if emd_node is None:
436
435
 
437
- if root is not None:
438
- root.append(emd_node)
436
+ emd_node = rqet.Element(ns['resqml2'] + 'ExtraMetadata')
437
+ emd_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'NameValuePair')
438
+ emd_node.text = rqet.null_xml_text
439
+
440
+ name_node = rqet.SubElement(emd_node, ns['resqml2'] + 'Name')
441
+ name_node.set(ns['xsi'] + 'type', ns['xsd'] + 'string')
442
+ name_node.text = 'source'
443
+
444
+ value_node = rqet.SubElement(emd_node, ns['resqml2'] + 'Value')
445
+ value_node.set(ns['xsi'] + 'type', ns['xsd'] + 'string')
446
+ value_node.text = str(source)
447
+
448
+ if root is not None:
449
+ root.append(emd_node)
450
+
451
+ else:
452
+
453
+ value_node = rqet.find_tag(emd_node, 'Value')
454
+ assert value_node is not None
455
+ value_node.text = str(source)
439
456
 
440
457
  return emd_node
441
458
 
@@ -457,7 +474,9 @@ def _create_patch(model,
457
474
  assert ext_uuid is not None
458
475
  else:
459
476
  assert const_count is not None and const_count > 0
460
- if hdf5_type.endswith('Hdf5Array'):
477
+ if isinstance(const_value, bool):
478
+ hdf5_type = 'BooleanConstantArray' # not actually stored in hdf5
479
+ elif hdf5_type.endswith('Hdf5Array'):
461
480
  hdf5_type = hdf5_type[:-9] + 'ConstantArray'
462
481
 
463
482
  lxt = str(xsd_type).lower()
@@ -488,6 +507,7 @@ def _create_patch(model,
488
507
  outer_values_node.text = rqet.null_xml_text
489
508
 
490
509
  if discrete and const_value is None:
510
+
491
511
  if null_value is None:
492
512
  if str(xsd_type).startswith('u'):
493
513
  null_value = 4294967295 # 2^32 - 1, used as default even for 64 bit data!
@@ -507,6 +527,11 @@ def _create_patch(model,
507
527
 
508
528
  else:
509
529
 
530
+ # TODO: handle bool const_value as special case
531
+ if isinstance(const_value, bool):
532
+ const_value = str(const_value).lower()
533
+ xsd_type = 'boolean'
534
+
510
535
  const_value_node = rqet.SubElement(outer_values_node, ns['resqml2'] + 'Value')
511
536
  const_value_node.set(ns['xsi'] + 'type', ns['xsd'] + xsd_type)
512
537
  const_value_node.text = str(const_value)