resqpy 4.14.3__tar.gz → 4.15.0__tar.gz

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 (200) hide show
  1. {resqpy-4.14.3 → resqpy-4.15.0}/PKG-INFO +1 -1
  2. {resqpy-4.14.3 → resqpy-4.15.0}/pyproject.toml +1 -1
  3. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/__init__.py +1 -1
  4. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/property/__init__.py +3 -2
  5. resqpy-4.15.0/resqpy/property/attribute_property_set.py +264 -0
  6. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/surface/_surface.py +117 -55
  7. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/time_series/_any_time_series.py +3 -1
  8. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/time_series/_geologic_time_series.py +1 -1
  9. {resqpy-4.14.3 → resqpy-4.15.0}/LICENSE +0 -0
  10. {resqpy-4.14.3 → resqpy-4.15.0}/README.md +0 -0
  11. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/crs.py +0 -0
  12. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/derived_model/__init__.py +0 -0
  13. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/derived_model/_add_edges_per_column_property_array.py +0 -0
  14. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/derived_model/_add_faults.py +0 -0
  15. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/derived_model/_add_one_blocked_well_property.py +0 -0
  16. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/derived_model/_add_one_grid_property_array.py +0 -0
  17. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/derived_model/_add_single_cell_grid.py +0 -0
  18. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/derived_model/_add_wells_from_ascii_file.py +0 -0
  19. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/derived_model/_add_zone_by_layer_property.py +0 -0
  20. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/derived_model/_coarsened_grid.py +0 -0
  21. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/derived_model/_common.py +0 -0
  22. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/derived_model/_copy_grid.py +0 -0
  23. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/derived_model/_drape_to_surface.py +0 -0
  24. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/derived_model/_extract_box.py +0 -0
  25. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/derived_model/_extract_box_for_well.py +0 -0
  26. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/derived_model/_fault_throw_scaling.py +0 -0
  27. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/derived_model/_gather_ensemble.py +0 -0
  28. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/derived_model/_interpolated_grid.py +0 -0
  29. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/derived_model/_local_depth_adjustment.py +0 -0
  30. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/derived_model/_refined_grid.py +0 -0
  31. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/derived_model/_tilted_grid.py +0 -0
  32. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/derived_model/_unsplit_grid.py +0 -0
  33. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/derived_model/_zonal_grid.py +0 -0
  34. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/derived_model/_zone_layer_ranges_from_array.py +0 -0
  35. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/fault/__init__.py +0 -0
  36. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/fault/_gcs_functions.py +0 -0
  37. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/fault/_grid_connection_set.py +0 -0
  38. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/grid/__init__.py +0 -0
  39. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/grid/_cell_properties.py +0 -0
  40. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/grid/_connection_sets.py +0 -0
  41. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/grid/_create_grid_xml.py +0 -0
  42. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/grid/_defined_geometry.py +0 -0
  43. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/grid/_extract_functions.py +0 -0
  44. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/grid/_face_functions.py +0 -0
  45. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/grid/_faults.py +0 -0
  46. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/grid/_grid.py +0 -0
  47. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/grid/_grid_types.py +0 -0
  48. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/grid/_intervals_info.py +0 -0
  49. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/grid/_moved_functions.py +0 -0
  50. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/grid/_pillars.py +0 -0
  51. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/grid/_pixel_maps.py +0 -0
  52. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/grid/_points_functions.py +0 -0
  53. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/grid/_regular_grid.py +0 -0
  54. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/grid/_transmissibility.py +0 -0
  55. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/grid/_write_hdf5_from_caches.py +0 -0
  56. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/grid/_write_nexus_corp.py +0 -0
  57. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/grid/_xyz.py +0 -0
  58. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/grid_surface/__init__.py +0 -0
  59. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/grid_surface/_blocked_well_populate.py +0 -0
  60. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/grid_surface/_find_faces.py +0 -0
  61. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/grid_surface/_grid_skin.py +0 -0
  62. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/grid_surface/_grid_surface.py +0 -0
  63. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/grid_surface/_trajectory_intersects.py +0 -0
  64. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/grid_surface/grid_surface_cuda.py +0 -0
  65. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/lines/__init__.py +0 -0
  66. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/lines/_common.py +0 -0
  67. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/lines/_polyline.py +0 -0
  68. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/lines/_polyline_set.py +0 -0
  69. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/model/__init__.py +0 -0
  70. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/model/_catalogue.py +0 -0
  71. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/model/_context.py +0 -0
  72. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/model/_forestry.py +0 -0
  73. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/model/_grids.py +0 -0
  74. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/model/_hdf5.py +0 -0
  75. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/model/_model.py +0 -0
  76. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/model/_xml.py +0 -0
  77. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/multi_processing/__init__.py +0 -0
  78. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/multi_processing/_multiprocessing.py +0 -0
  79. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/multi_processing/wrappers/__init__.py +0 -0
  80. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/multi_processing/wrappers/blocked_well_mp.py +0 -0
  81. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/multi_processing/wrappers/grid_surface_mp.py +0 -0
  82. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/multi_processing/wrappers/mesh_mp.py +0 -0
  83. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/olio/__init__.py +0 -0
  84. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/olio/ab_toolbox.py +0 -0
  85. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/olio/base.py +0 -0
  86. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/olio/box_utilities.py +0 -0
  87. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/olio/class_dict.py +0 -0
  88. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/olio/consolidation.py +0 -0
  89. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/olio/data/build.py +0 -0
  90. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/olio/data/properties.json +0 -0
  91. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/olio/dataframe.py +0 -0
  92. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/olio/exceptions.py +0 -0
  93. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/olio/factors.py +0 -0
  94. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/olio/fine_coarse.py +0 -0
  95. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/olio/grid_functions.py +0 -0
  96. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/olio/intersection.py +0 -0
  97. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/olio/keyword_files.py +0 -0
  98. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/olio/load_data.py +0 -0
  99. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/olio/point_inclusion.py +0 -0
  100. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/olio/random_seed.py +0 -0
  101. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/olio/read_nexus_fault.py +0 -0
  102. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/olio/relperm.py +0 -0
  103. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/olio/simple_lines.py +0 -0
  104. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/olio/time.py +0 -0
  105. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/olio/trademark.py +0 -0
  106. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/olio/transmission.py +0 -0
  107. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/olio/triangulation.py +0 -0
  108. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/olio/uuid.py +0 -0
  109. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/olio/vdb.py +0 -0
  110. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/olio/vector_utilities.py +0 -0
  111. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/olio/volume.py +0 -0
  112. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/olio/wellspec_keywords.py +0 -0
  113. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/olio/write_data.py +0 -0
  114. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/olio/write_hdf5.py +0 -0
  115. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/olio/xml_et.py +0 -0
  116. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/olio/xml_namespaces.py +0 -0
  117. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/olio/zmap_reader.py +0 -0
  118. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/organize/__init__.py +0 -0
  119. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/organize/_utils.py +0 -0
  120. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/organize/boundary_feature.py +0 -0
  121. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/organize/boundary_feature_interpretation.py +0 -0
  122. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/organize/earth_model_interpretation.py +0 -0
  123. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/organize/fault_interpretation.py +0 -0
  124. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/organize/fluid_boundary_feature.py +0 -0
  125. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/organize/frontier_feature.py +0 -0
  126. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/organize/generic_interpretation.py +0 -0
  127. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/organize/genetic_boundary_feature.py +0 -0
  128. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/organize/geobody_boundary_interpretation.py +0 -0
  129. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/organize/geobody_feature.py +0 -0
  130. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/organize/geobody_interpretation.py +0 -0
  131. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/organize/geologic_unit_feature.py +0 -0
  132. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/organize/horizon_interpretation.py +0 -0
  133. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/organize/organization_feature.py +0 -0
  134. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/organize/rock_fluid_unit_feature.py +0 -0
  135. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/organize/structural_organization_interpretation.py +0 -0
  136. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/organize/tectonic_boundary_feature.py +0 -0
  137. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/organize/wellbore_feature.py +0 -0
  138. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/organize/wellbore_interpretation.py +0 -0
  139. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/property/_collection_add_part.py +0 -0
  140. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/property/_collection_create_xml.py +0 -0
  141. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/property/_collection_get_attributes.py +0 -0
  142. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/property/_collection_support.py +0 -0
  143. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/property/_property.py +0 -0
  144. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/property/grid_property_collection.py +0 -0
  145. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/property/property_collection.py +0 -0
  146. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/property/property_common.py +0 -0
  147. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/property/property_kind.py +0 -0
  148. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/property/string_lookup.py +0 -0
  149. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/property/well_interval_property.py +0 -0
  150. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/property/well_interval_property_collection.py +0 -0
  151. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/property/well_log.py +0 -0
  152. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/property/well_log_collection.py +0 -0
  153. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/rq_import/__init__.py +0 -0
  154. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/rq_import/_add_ab_properties.py +0 -0
  155. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/rq_import/_add_surfaces.py +0 -0
  156. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/rq_import/_grid_from_cp.py +0 -0
  157. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/rq_import/_import_nexus.py +0 -0
  158. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/rq_import/_import_vdb_all_grids.py +0 -0
  159. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/rq_import/_import_vdb_ensemble.py +0 -0
  160. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/strata/__init__.py +0 -0
  161. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/strata/_binary_contact_interpretation.py +0 -0
  162. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/strata/_geologic_unit_interpretation.py +0 -0
  163. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/strata/_strata_common.py +0 -0
  164. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/strata/_stratigraphic_column.py +0 -0
  165. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/strata/_stratigraphic_column_rank.py +0 -0
  166. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/strata/_stratigraphic_unit_feature.py +0 -0
  167. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/strata/_stratigraphic_unit_interpretation.py +0 -0
  168. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/surface/__init__.py +0 -0
  169. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/surface/_base_surface.py +0 -0
  170. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/surface/_combined_surface.py +0 -0
  171. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/surface/_mesh.py +0 -0
  172. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/surface/_pointset.py +0 -0
  173. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/surface/_tri_mesh.py +0 -0
  174. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/surface/_tri_mesh_stencil.py +0 -0
  175. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/surface/_triangulated_patch.py +0 -0
  176. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/time_series/__init__.py +0 -0
  177. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/time_series/_from_nexus_summary.py +0 -0
  178. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/time_series/_functions.py +0 -0
  179. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/time_series/_time_duration.py +0 -0
  180. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/time_series/_time_series.py +0 -0
  181. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/unstructured/__init__.py +0 -0
  182. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/unstructured/_hexa_grid.py +0 -0
  183. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/unstructured/_prism_grid.py +0 -0
  184. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/unstructured/_pyramid_grid.py +0 -0
  185. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/unstructured/_tetra_grid.py +0 -0
  186. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/unstructured/_unstructured_grid.py +0 -0
  187. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/weights_and_measures/__init__.py +0 -0
  188. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/weights_and_measures/nexus_units.py +0 -0
  189. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/weights_and_measures/weights_and_measures.py +0 -0
  190. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/well/__init__.py +0 -0
  191. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/well/_blocked_well.py +0 -0
  192. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/well/_deviation_survey.py +0 -0
  193. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/well/_md_datum.py +0 -0
  194. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/well/_trajectory.py +0 -0
  195. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/well/_wellbore_frame.py +0 -0
  196. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/well/_wellbore_marker.py +0 -0
  197. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/well/_wellbore_marker_frame.py +0 -0
  198. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/well/blocked_well_frame.py +0 -0
  199. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/well/well_object_funcs.py +0 -0
  200. {resqpy-4.14.3 → resqpy-4.15.0}/resqpy/well/well_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: resqpy
3
- Version: 4.14.3
3
+ Version: 4.15.0
4
4
  Summary: Python API for working with RESQML models
5
5
  Home-page: https://github.com/bp/resqpy
6
6
  License: MIT
@@ -9,7 +9,7 @@ build-backend = "poetry.masonry.api"
9
9
 
10
10
  [tool.poetry]
11
11
  name = "resqpy"
12
- version = "4.14.3" # Set at build time
12
+ version = "4.15.0" # Set at build time
13
13
  description = "Python API for working with RESQML models"
14
14
  authors = ["BP"]
15
15
  license = "MIT"
@@ -28,6 +28,6 @@
28
28
 
29
29
  import logging
30
30
 
31
- __version__ = "4.14.3" # Set at build time
31
+ __version__ = "4.15.0" # Set at build time
32
32
  log = logging.getLogger(__name__)
33
33
  log.info(f"Imported resqpy version {__version__}")
@@ -1,8 +1,8 @@
1
1
  """Collections of properties for grids, wellbore frames, grid connection sets etc."""
2
2
 
3
3
  __all__ = [
4
- 'PropertyCollection', 'Property', 'WellLog', 'WellIntervalProperty', 'WellIntervalPropertyCollection',
5
- 'WellLogCollection', 'StringLookup', 'PropertyKind', 'GridPropertyCollection',
4
+ 'PropertyCollection', 'Property', 'AttributePropertySet', 'ApsProperty', 'WellLog', 'WellIntervalProperty',
5
+ 'WellIntervalPropertyCollection', 'WellLogCollection', 'StringLookup', 'PropertyKind', 'GridPropertyCollection',
6
6
  'property_over_time_series_from_collection', 'property_collection_for_keyword', 'infer_property_kind',
7
7
  'write_hdf5_and_create_xml_for_active_property', 'reformat_column_edges_to_resqml_format',
8
8
  'reformat_column_edges_from_resqml_format', 'same_property_kind', 'selective_version_of_collection',
@@ -30,6 +30,7 @@ from .property_common import property_collection_for_keyword, \
30
30
  from .property_kind import PropertyKind, create_transmisibility_multiplier_property_kind
31
31
  from .string_lookup import StringLookup
32
32
  from .property_collection import PropertyCollection
33
+ from .attribute_property_set import AttributePropertySet, ApsProperty
33
34
  from .grid_property_collection import GridPropertyCollection
34
35
  from ._property import Property
35
36
  from .well_interval_property import WellIntervalProperty
@@ -0,0 +1,264 @@
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 support_uuid(self):
164
+ """The uuid of the supporting representation for this property."""
165
+ return self.aps.support_uuid_for_part(self.part)
166
+
167
+ @property
168
+ def string_lookup_uuid(self):
169
+ """The uuid of the string lookup table for a categorical property (otherwise None)."""
170
+ return self.aps.string_lookup_uuid_for_part(self.part)
171
+
172
+ @property
173
+ def time_series_uuid(self):
174
+ """The uuid of the time series for this property (may be None)."""
175
+ return self.aps.time_series_uuid_for_part(self.part)
176
+
177
+ @property
178
+ def local_property_kind_uuid(self):
179
+ """The uuid of the local property kind for this property (may be None)."""
180
+ return self.aps.local_property_kind_uuid(self.part)
181
+
182
+
183
+ class AttributePropertySet(rqp.PropertyCollection):
184
+ """Class for set of RESQML properties for any supporting representation, using attribute syntax."""
185
+
186
+ def __init__(self, model = None, support = None, property_set_uuid = None, realization = None, key_mode = 'pk'):
187
+ """Initialise an empty property set, optionally populate properties from a supporting representation.
188
+
189
+ arguments:
190
+ model (Model, optional): required if property_set_uuid is not None
191
+ support (optional): a grid.Grid object, or a well.BlockedWell, or a well.WellboreFrame object which belongs to a
192
+ resqpy.Model which includes associated properties; if this argument is given, and property_set_root is None,
193
+ the properties in the support's parent model which are for this representation (ie. have this object as the
194
+ supporting representation) are added to this collection as part of the initialisation
195
+ property_set_uuid (optional): if present, the collection is populated with the properties defined in the xml tree
196
+ of the property set
197
+ realization (integer, optional): if present, the single realisation (within an ensemble) that this collection is for;
198
+ if None, then the collection is either covering a whole ensemble (individual properties can each be flagged with a
199
+ realisation number), or is for properties that do not have multiple realizations
200
+ key_mode (str, default 'pk'): either 'pk' (for property kind) or 'title', identifying the basis of property attribute keys
201
+
202
+ note:
203
+ at present, if the collection is being initialised from a property set, the support argument must also be specified;
204
+ also for now, if not initialising from a property set, all properties related to the support are included, whether
205
+ the relationship is supporting representation or some other relationship;
206
+
207
+ :meta common:
208
+ """
209
+
210
+ assert key_mode in ['pk', 'title']
211
+ assert property_set_uuid is None or model is not None
212
+ assert support is None or model is None or support.model is model
213
+ if property_set_uuid is None:
214
+ property_set_root = None
215
+ else:
216
+ property_set_root = model.root_for_uuid(property_set_uuid)
217
+
218
+ super().__init__(support = support, property_set_root = property_set_root, realization = realization)
219
+ self.key_mode = key_mode
220
+ self._make_attributes()
221
+
222
+ def keys(self):
223
+ """Iterator over property keys within the set."""
224
+ for p in self.parts():
225
+ yield self._key(p)
226
+
227
+ def properties(self):
228
+ """Iterator over ApsProperty members of the set."""
229
+ for k in self.keys():
230
+ yield getattr(self, k)
231
+
232
+ def items(self):
233
+ """Iterator over (key, ApsProperty) members of the set."""
234
+ for k in self.keys():
235
+ yield (k, getattr(self, k))
236
+
237
+ def _key(self, part):
238
+ """Returns the key (attribute name) for a given part."""
239
+ if self.key_mode == 'pk':
240
+ key = self.property_kind_for_part(part)
241
+ facet = self.facet_for_part(part)
242
+ if facet is not None:
243
+ key += f'_{facet}'
244
+ else:
245
+ key = self.citation_title_for_part(part)
246
+ key = key.replace(' ', '_')
247
+ ti = self.time_index_for_part(part)
248
+ if ti is not None:
249
+ key += f'_t{ti}'
250
+ r = self.realization_for_part(part)
251
+ if r is not None:
252
+ key += f'_r{r}'
253
+ return key
254
+
255
+ def _make_attributes(self):
256
+ """Setup individual properties with attribute style read access to metadata."""
257
+ for part in self.parts():
258
+ key = self._key(part)
259
+ aps_property = ApsProperty(self, part)
260
+ setattr(self, key, aps_property)
261
+
262
+ def __len__(self):
263
+ """Returns the number of properties in the set."""
264
+ return self.number_of_parts()
@@ -272,48 +272,86 @@ class Surface(rqsb.BaseSurface):
272
272
 
273
273
  self.model = parent_model
274
274
 
275
- def triangles_and_points(self):
276
- """Returns arrays representing combination of all the patches in the surface.
275
+ def number_of_patches(self):
276
+ """Returns the number of patches present in the surface."""
277
+
278
+ self.extract_patches(self.root)
279
+ return len(self.patch_list)
280
+
281
+ def triangles_and_points(self, patch = None):
282
+ """Returns arrays representing one patch or a combination of all the patches in the surface.
283
+
284
+ arguments:
285
+ patch (int, optional): patch index; if None, combined arrays for all patches are returned
277
286
 
278
287
  returns:
279
288
  tuple (triangles, points):
280
289
  triangles (int array of shape[:, 3]): integer indices into points array,
281
- being the nodes of the corners of the triangles;
290
+ being the nodes of the corners of the triangles;
282
291
  points (float array of shape[:, 3]): flat array of xyz points, indexed by triangles
283
292
 
284
293
  :meta common:
285
294
  """
286
295
 
287
- if self.triangles is not None:
288
- return (self.triangles, self.points)
289
296
  self.extract_patches(self.root)
290
- points_offset = 0
291
- for triangulated_patch in self.patch_list:
292
- (t, p) = triangulated_patch.triangles_and_points()
293
- if points_offset == 0:
294
- self.triangles = t.copy()
295
- self.points = p.copy()
296
- else:
297
- self.triangles = np.concatenate((self.triangles, t.copy() + points_offset))
298
- self.points = np.concatenate((self.points, p.copy()))
299
- points_offset += p.shape[0]
300
- return (self.triangles, self.points)
297
+ if patch is None:
298
+ if self.triangles is None:
299
+ points_offset = 0
300
+ for triangulated_patch in self.patch_list:
301
+ (t, p) = triangulated_patch.triangles_and_points()
302
+ if points_offset == 0:
303
+ self.triangles = t.copy()
304
+ self.points = p.copy()
305
+ else:
306
+ self.triangles = np.concatenate((self.triangles, t.copy() + points_offset))
307
+ self.points = np.concatenate((self.points, p.copy()))
308
+ points_offset += p.shape[0]
309
+ return (self.triangles, self.points)
310
+ assert 0 <= patch < len(self.patch_list), \
311
+ ValueError(f'patch index {patch} out of range for surface with {len(self.patch_list)} patches')
312
+ return self.patch_list[patch].triangles_and_points()
301
313
 
302
- def triangle_count(self):
303
- """Return the numner of triangles in this surface."""
314
+ def triangle_count(self, patch = None):
315
+ """Return the numner of triangles in this surface, or in one patch.
316
+
317
+ arguments:
318
+ patch (int, optional): patch index; if None, a combined triangle count for all patches is returned
319
+
320
+ returns:
321
+ int being the number of trianges in the patch (if specified) or in all the patches
322
+ """
304
323
 
305
324
  self.extract_patches(self.root)
306
- if not self.patch_list:
307
- return 0
308
- return np.sum([tp.triangle_count for tp in self.patch_list])
325
+ if patch is None:
326
+ if not self.patch_list:
327
+ return 0
328
+ return np.sum([tp.triangle_count for tp in self.patch_list])
329
+ assert 0 <= patch < len(self.patch_list), \
330
+ ValueError(f'patch index {patch} out of range for surface with {len(self.patch_list)} patches in triangle_count')
331
+ return self.patch_list[patch].triangle_count
332
+
333
+ def node_count(self, patch = None):
334
+ """Return the number of nodes (points) used in this surface, or in one patch.
335
+
336
+ arguments:
337
+ patch (int, optional): patch index; if None, a combined node count for all patches is returned
338
+
339
+ returns:
340
+ int being the number of nodes in the patch (if specified) or in all the patches
309
341
 
310
- def node_count(self):
311
- """Return the number of nodes (points) used in this surface."""
342
+ note:
343
+ a multi patch surface might have more than one node colocated; this method will treat such coincident nodes
344
+ as separate nodes
345
+ """
312
346
 
313
347
  self.extract_patches(self.root)
314
- if not self.patch_list:
315
- return 0
316
- return np.sum([tp.node_count for tp in self.patch_list])
348
+ if patch is None:
349
+ if not self.patch_list:
350
+ return 0
351
+ return np.sum([tp.node_count for tp in self.patch_list])
352
+ assert 0 <= patch < len(self.patch_list), \
353
+ ValueError(f'patch index {patch} out of range for surface with {len(self.patch_list)} patches in node_count')
354
+ return self.patch_list[patch].node_count
317
355
 
318
356
  def change_crs(self, required_crs):
319
357
  """Changes the crs of the surface, also sets a new uuid if crs changed.
@@ -381,7 +419,7 @@ class Surface(rqsb.BaseSurface):
381
419
  self.uuid = bu.new_uuid()
382
420
 
383
421
  def set_to_split_surface(self, large_surface, line, delta_xyz):
384
- """Populate this (empty) surface with a version of a larger surface split by an xy line.
422
+ """Populate this (empty) surface with a version of a larger surface split by a straight xy line.
385
423
 
386
424
  arguments:
387
425
  large_surface (Surface): the larger surface, a copy of which is to be split
@@ -404,8 +442,8 @@ class Surface(rqsb.BaseSurface):
404
442
  tp[:, 1] = len(p) + 1
405
443
  tp[:, 2] = np.arange(len(p), dtype = int)
406
444
  cw = vec.clockwise_triangles(pp, tp)
407
- pai = np.where(cw >= 0.0, True, False) # bool mask over p
408
- pbi = np.where(cw <= 0.0, True, False) # bool mask over p
445
+ pai = (cw >= 0.0) # bool mask over p
446
+ pbi = (cw <= 0.0) # bool mask over p
409
447
  tap = pai[t]
410
448
  tbp = pbi[t]
411
449
  ta = np.any(tap, axis = 1) # bool array over t
@@ -435,17 +473,24 @@ class Surface(rqsb.BaseSurface):
435
473
 
436
474
  self.set_from_triangles_and_points(t_combo, p_combo)
437
475
 
438
- def distinct_edges(self):
439
- """Returns a numpy int array of shape (N, 2) being the ordered node pairs of distinct edges of triangles."""
476
+ def distinct_edges(self, patch = None):
477
+ """Returns a numpy int array of shape (N, 2) being the ordered node pairs of distinct edges of triangles.
478
+
479
+ arguments:
480
+ patch (int, optional): patch index; if None, a combination of edges for all patches is returned
481
+ """
440
482
 
441
- triangles, _ = self.triangles_and_points()
483
+ triangles, _ = self.triangles_and_points(patch = patch)
442
484
  assert triangles is not None
443
485
  unique_edges, _ = triangulate.edges(triangles)
444
486
  return unique_edges
445
487
 
446
- def distinct_edges_and_counts(self):
488
+ def distinct_edges_and_counts(self, patch = None):
447
489
  """Returns unique edges as pairs of point indices, and a count of uses of each edge.
448
490
 
491
+ arguments:
492
+ patch (int, optional): patch index; if None, combined results for all patches are returned
493
+
449
494
  returns:
450
495
  numpy int array of shape (N, 2), numpy int array of shape (N,)
451
496
  where 2D array is list-like sorted points index pairs for unique edges
@@ -453,25 +498,26 @@ class Surface(rqsb.BaseSurface):
453
498
 
454
499
  notes:
455
500
  first entry in each pair is always the lower of the two point indices;
456
- for well formed surfaces, the count should everywhere be zero or one;
501
+ for well formed surfaces, the count should everywhere be one or two;
457
502
  the function does not attempt to detect coincident points
458
503
  """
459
504
 
460
- triangles, _ = self.triangles_and_points()
505
+ triangles, _ = self.triangles_and_points(patch = patch)
461
506
  assert triangles is not None
462
507
  return triangulate.edges(triangles)
463
508
 
464
- def edge_lengths(self, required_uom = None):
509
+ def edge_lengths(self, required_uom = None, patch = None):
465
510
  """Returns float array of shape (N, 3) being triangle edge lengths.
466
511
 
467
512
  arguments:
468
513
  required_uom (str, optional): the required length uom for the resulting edge lengths; default is crs xy units
514
+ patch (int, optional): patch index; if None, edge lengths for all patches are returned
469
515
 
470
516
  returns:
471
517
  numpy float array of shape (N, 3) where N is the number of triangles
472
518
  """
473
519
 
474
- t, p = self.triangles_and_points()
520
+ t, p = self.triangles_and_points(patch = patch)
475
521
  crs = rqc.Crs(self.model, uuid = self.crs_uuid)
476
522
  if required_uom is None:
477
523
  required_uom = crs.xy_units
@@ -495,6 +541,20 @@ class Surface(rqsb.BaseSurface):
495
541
  self.triangles = triangles.copy()
496
542
  self.points = points.copy()
497
543
 
544
+ def set_multi_patch_from_triangles_and_points(self, triangles_and_points_list):
545
+ """Populate this (empty) Surface object from a list of paits: array of triangle corner indices, array of points."""
546
+
547
+ self.patch_list = []
548
+ self.trianges = None
549
+ self.points = None
550
+ for patch, entry in enumerate(triangles_and_points_list):
551
+ assert len(entry) == 2, 'expecting pair of arrays (triangles, points) for each patch'
552
+ triangles, points = entry
553
+ tri_patch = rqstp.TriangulatedPatch(self.model, patch_index = patch, crs_uuid = self.crs_uuid)
554
+ tri_patch.set_from_triangles_and_points(triangles, points)
555
+ self.patch_list.append(tri_patch)
556
+ self.uuid = bu.new_uuid()
557
+
498
558
  def set_from_point_set(self,
499
559
  point_set,
500
560
  convexity_parameter = 5.0,
@@ -927,10 +987,10 @@ class Surface(rqsb.BaseSurface):
927
987
  for patch in self.patch_list:
928
988
  patch.vertical_rescale_points(ref_depth, scaling_factor)
929
989
 
930
- def line_intersection(self, line_p, line_v, line_segment = False):
990
+ def line_intersection(self, line_p, line_v, line_segment = False, patch = None):
931
991
  """Returns x,y,z of an intersection point of straight line with the surface, or None if no intersection found."""
932
992
 
933
- t, p = self.triangles_and_points()
993
+ t, p = self.triangles_and_points(patch = patch)
934
994
  tp = p[t]
935
995
  intersects = meet.line_triangles_intersects(line_p, line_v, tp, line_segment = line_segment)
936
996
  indices = meet.intersects_indices(intersects)
@@ -938,21 +998,22 @@ class Surface(rqsb.BaseSurface):
938
998
  return None
939
999
  return intersects[indices[0]]
940
1000
 
941
- def sample_z_at_xy_points(self, points, multiple_handling = 'any'):
1001
+ def sample_z_at_xy_points(self, points, multiple_handling = 'any', patch = None):
942
1002
  """Returns interpolated z values for an array of xy values.
943
1003
 
944
1004
  arguments:
945
1005
  points (numpy float array of shape (..., 2 or 3)): xy points to sample surface at (z values ignored)
946
1006
  multiple_handling (str, default 'any'): one of 'any', 'minimum', 'maximum', 'exception'
1007
+ patch (int, optional): patch index; if None, results are for the full surface
947
1008
 
948
1009
  returns:
949
1010
  numpy float array of shape points.shape[:-1] being z values interpolated from the surface z values
950
1011
 
951
1012
  notes:
952
1013
  points must be in the same crs as the surface;
953
- NaN will be set for any points that do not intersect with the surface in the xy projection;
954
- multiple_handling argument controls behaviour when one sample point intersects surface more than
955
- once: 'any' a random one of the intersection z values is returned; 'minimum' or 'maximum': the
1014
+ NaN will be set for any points that do not intersect with the patch or surface in the xy projection;
1015
+ multiple_handling argument controls behaviour when one sample point intersects more than once:
1016
+ 'any' a random one of the intersection z values is returned; 'minimum' or 'maximum': the
956
1017
  numerical min or max of the z values is returned; 'exception': a ValueError is raised
957
1018
  """
958
1019
 
@@ -964,7 +1025,7 @@ class Surface(rqsb.BaseSurface):
964
1025
  else:
965
1026
  sample_xy = np.zeros((points.size // 2, 3), dtype = float)
966
1027
  sample_xy[:, :2] = points.reshape((-1, 2))
967
- t, p = self.triangles_and_points()
1028
+ t, p = self.triangles_and_points(patch = patch)
968
1029
  p_list = vec.points_in_triangles_njit(sample_xy, p[t], 1)
969
1030
  vertical = np.array((0.0, 0.0, 1.0), dtype = float)
970
1031
  z = np.full(sample_xy.shape[0], np.NaN, dtype = float)
@@ -983,17 +1044,18 @@ class Surface(rqsb.BaseSurface):
983
1044
  raise ValueError(f'multiple {self.title} surface intersections at xy: {sample_xy[p_index]}')
984
1045
  return z.reshape(points.shape[:-1])
985
1046
 
986
- def normal_vectors(self, add_as_property: bool = False) -> np.ndarray:
987
- """Returns the normal vectors for each triangle in the surface.
988
-
1047
+ def normal_vectors(self, add_as_property: bool = False, patch = None) -> np.ndarray:
1048
+ """Returns the normal vectors for each triangle in the patch or surface.
1049
+
989
1050
  arguments:
990
- add_as_property (bool): if True, face_surface_normal_vectors_array is added as a property to the model.
1051
+ add_as_property (bool): if True, face_surface_normal_vectors_array is added as a property to the model
1052
+ patch (int, optional): patch index; if None, normal vectors for triangles in all patches are returned
991
1053
 
992
1054
  returns:
993
- normal_vectors_array (np.ndarray): the normal vectors corresponding to each triangle in the surface.
1055
+ normal_vectors_array (np.ndarray): the normal vectors corresponding to each triangle in the surface
994
1056
  """
995
1057
  crs = rqc.Crs(self.model, uuid = self.crs_uuid)
996
- triangles, points = self.triangles_and_points()
1058
+ triangles, points = self.triangles_and_points(patch = patch)
997
1059
  if crs.xy_units != crs.z_units:
998
1060
  points = points.copy()
999
1061
  wam.convert_lengths(points[:, 2], crs.z_units, crs.xy_units)
@@ -1053,13 +1115,13 @@ class Surface(rqsb.BaseSurface):
1053
1115
  return ep
1054
1116
 
1055
1117
  def resampled_surface(self, title = None):
1056
- """Creates a new triangulated set which is a resampled version of the current triangulated set. Each existing triangle in the tset is divided equally into 4 new triangles.
1057
-
1118
+ """Creates a new surface which is a refined version of this surface; each triangle is divided equally into 4 new triangles.
1119
+
1058
1120
  arguments:
1059
- title (str): a new title for the output triangulated set, if None the title will have the same title as the input triangulated set
1060
-
1121
+ title (str): title for the output triangulated set, if None the title will be inherited from the input surface
1122
+
1061
1123
  returns:
1062
- resqpy.surface.Surface object, with extra_metadata ('resampled from surface': <uuid>), where uuid is the origin surface uuid
1124
+ resqpy.surface.Surface object, with extra_metadata ('resampled from surface': uuid), where uuid is for the original surface uuid
1063
1125
  """
1064
1126
  rt, rp = self.triangles_and_points()
1065
1127
  edge1 = np.mean(rp[rt[:]][:, ::2, :], axis = 1)
@@ -34,8 +34,10 @@ class AnyTimeSeries(BaseResqpy):
34
34
  dt_text = rqet.find_tag_text(child, 'DateTime')
35
35
  assert dt_text, 'missing DateTime field in xml for time series'
36
36
  year_offset = rqet.find_tag_int(child, 'YearOffset')
37
- if year_offset:
37
+ if year_offset is not None:
38
38
  assert self.timeframe == 'geologic'
39
+ if year_offset > 0:
40
+ log.warning(f'positive year offset in xml indicates future geological time: {year_offset}')
39
41
  self.timestamps.append(year_offset) # todo: trim and check timestamp
40
42
  else:
41
43
  assert self.timeframe == 'human'
@@ -29,7 +29,7 @@ class GeologicTimeSeries(ats.AnyTimeSeries):
29
29
 
30
30
  note:
31
31
  if instantiating from an existing RESQML time series, its Time entries must all have YearOffset data
32
- which should be large negative integers
32
+ which should be large negative integers (or zero if reaching the current era)
33
33
 
34
34
  :meta common:
35
35
  """
File without changes
File without changes
File without changes