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
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)