def test_dtype_size(tmp_path): filenames = ['dtype_16', 'dtype_32', 'dtype_64'] byte_sizes = [2, 4, 8] dtypes = [np.float16, np.float32, np.float64] hdf5_sizes = [] extent_kji = (1000, 100, 100) a = np.random.random(extent_kji) for filename, dtype in zip(filenames, dtypes): epc = os.path.join(tmp_path, filename + '.epc') h5_file = epc[:-4] + '.h5' model = rq.new_model(epc) grid = grr.RegularGrid(model, extent_kji=extent_kji) grid.create_xml() pc = rqp.PropertyCollection() pc.set_support(support_uuid=grid.uuid, model=model) pc.add_cached_array_to_imported_list( cached_array=a, source_info='random', keyword='NTG', property_kind='net to gross ratio', indexable_element='cells', uom='m3/m3') pc.write_hdf5_for_imported_list(dtype=dtype) model.store_epc() model.h5_release() hdf5_sizes.append(os.path.getsize(h5_file)) assert hdf5_sizes[0] < hdf5_sizes[1] < hdf5_sizes[2] for i, (byte_size, hdf5_size) in enumerate(zip(byte_sizes, hdf5_sizes)): array_size = byte_size * a.size # following may need to be modified if using hdf5 compression assert array_size < hdf5_size < array_size + 100000
def test_create_property_set_per_timestep_true(tmp_path): # Arrange current_filename = os.path.split(getsourcefile(lambda: 0))[0] base_folder = os.path.dirname(os.path.dirname(current_filename)) ensemble_dir = f'{base_folder}/test_data/wren' epc_file = f'{tmp_path}/test.epc' no_parts_expected = [36, 21, 21, 21] # Act import_vdb_ensemble(epc_file, ensemble_dir, create_property_set_per_timestep=True) model = rq.Model(epc_file) grid = model.grid() property_set_uuids = model.uuids(obj_type='PropertySet', title='time index', title_mode='contains') # Assert assert len(property_set_uuids) == 4 for uuid in property_set_uuids: property_set_root = model.root_for_uuid(uuid=uuid) title = rqet.citation_title_for_node(property_set_root).split()[-1] property_set = rqp.PropertyCollection( support=grid, property_set_root=property_set_root) assert property_set.number_of_parts() == no_parts_expected[int(title)]
def _set_mesh_from_df(self): """Creates Mesh object; called before writing to hdf5 or creating xml.""" # note: actual data is stored in related Property if realization number is present, directly in Mesh otherwise assert self.n_rows == len(self.df) assert self.n_cols == len(self.df.columns) if self.mesh is None: origin = (0.0, 0.0, 0.0) dxyz_dij = np.array([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]) crs_uuids = self.model.uuids(obj_type='LocalDepth3dCrs') if len(crs_uuids) == 0: crs = rqc.Crs(self.model) crs.create_xml() crs_uuid = crs.uuid else: # use any available crs crs_uuid = crs_uuids[0] if self.realization is None: self.mesh = rqs.Mesh(self.model, mesh_flavour='reg&z', ni=self.n_cols, nj=self.n_rows, dxyz_dij=dxyz_dij, origin=origin, z_values=np.array(self.df), crs_uuid=crs_uuid) else: self.mesh = rqs.Mesh(self.model, mesh_flavour='regular', ni=self.n_cols, nj=self.n_rows, dxyz_dij=dxyz_dij, origin=origin, crs_uuid=crs_uuid) self.mesh.write_hdf5() mesh_root = self.mesh.create_xml(title=self.title) rqet.create_metadata_xml(mesh_root, {'dataframe': 'true'}) if self.realization is not None: self.pc = rqp.PropertyCollection() self.pc.set_support(support=self.mesh) dataframe_pk_uuid = self.model.uuid(obj_type='PropertyKind', title='dataframe') if dataframe_pk_uuid is None: dataframe_pk = rqp.PropertyKind(self.model, title='dataframe', example_uom='Euc') dataframe_pk.create_xml() dataframe_pk_uuid = dataframe_pk.uuid self.pc.add_cached_array_to_imported_list( np.array(self.df), 'dataframe', self.title, uom='Euc', property_kind='dataframe', local_property_kind_uuid=dataframe_pk_uuid, realization=self.realization, indexable_element='nodes') self.pc.write_hdf5_for_imported_list() self.pc.create_xml_for_imported_list_and_add_parts_to_model()
def test_create_property_set_per_realization_true(tmp_path): # Arrange current_filename = os.path.split(getsourcefile(lambda: 0))[0] base_folder = os.path.dirname(os.path.dirname(current_filename)) ensemble_dir = f'{base_folder}/test_data/wren' epc_file = f'{tmp_path}/test.epc' # Act import_vdb_ensemble(epc_file, ensemble_dir, create_property_set_per_realization=True) model = rq.Model(epc_file) grid = model.grid() property_set_uuids = model.uuids(obj_type='PropertySet', title='realization', title_mode='contains') # Assert assert len(property_set_uuids) == 3 for uuid in property_set_uuids: property_set_root = model.root_for_uuid(uuid=uuid) property_set = rqp.PropertyCollection( support=grid, property_set_root=property_set_root) assert property_set.number_of_parts() == 46
def _create_grid_xml(grid, ijk, ext_uuid = None, add_as_part = True, add_relationships = True, write_active = True, write_geometry = True): """Function that returns an xml representation containing grid information""" if grid.grid_representation and not write_geometry: rqet.create_metadata_xml(node = ijk, extra_metadata = {'grid_flavour': grid.grid_representation}) ni_node = rqet.SubElement(ijk, ns['resqml2'] + 'Ni') ni_node.set(ns['xsi'] + 'type', ns['xsd'] + 'positiveInteger') ni_node.text = str(grid.extent_kji[2]) nj_node = rqet.SubElement(ijk, ns['resqml2'] + 'Nj') nj_node.set(ns['xsi'] + 'type', ns['xsd'] + 'positiveInteger') nj_node.text = str(grid.extent_kji[1]) nk_node = rqet.SubElement(ijk, ns['resqml2'] + 'Nk') nk_node.set(ns['xsi'] + 'type', ns['xsd'] + 'positiveInteger') nk_node.text = str(grid.extent_kji[0]) if grid.k_gaps: kg_node = rqet.SubElement(ijk, ns['resqml2'] + 'KGaps') kg_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'KGaps') kg_node.text = '\n' kgc_node = rqet.SubElement(kg_node, ns['resqml2'] + 'Count') kgc_node.set(ns['xsi'] + 'type', ns['xsd'] + 'positiveInteger') kgc_node.text = str(grid.k_gaps) assert grid.k_gap_after_array.ndim == 1 and grid.k_gap_after_array.size == grid.nk - 1 kgal_node = rqet.SubElement(kg_node, ns['resqml2'] + 'GapAfterLayer') kgal_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'BooleanHdf5Array') kgal_node.text = '\n' kgal_values = rqet.SubElement(kgal_node, ns['resqml2'] + 'Values') kgal_values.set(ns['xsi'] + 'type', ns['eml'] + 'Hdf5Dataset') kgal_values.text = '\n' grid.model.create_hdf5_dataset_ref(ext_uuid, grid.uuid, 'GapAfterLayer', root = kgal_values) if grid.stratigraphic_column_rank_uuid is not None and grid.stratigraphic_units is not None: assert grid.model.type_of_uuid( grid.stratigraphic_column_rank_uuid) == 'obj_StratigraphicColumnRankInterpretation' strata_node = rqet.SubElement(ijk, ns['resqml2'] + 'IntervalStratigraphicUnits') strata_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'IntervalStratigraphicUnits') strata_node.text = '\n' ui_node = rqet.SubElement(strata_node, ns['resqml2'] + 'UnitIndices') ui_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'IntegerHdf5Array') ui_node.text = '\n' ui_null = rqet.SubElement(ui_node, ns['resqml2'] + 'NullValue') ui_null.set(ns['xsi'] + 'type', ns['xsd'] + 'integer') ui_null.text = '-1' ui_values = rqet.SubElement(ui_node, ns['resqml2'] + 'Values') ui_values.set(ns['xsi'] + 'type', ns['eml'] + 'Hdf5Dataset') ui_values.text = '\n' grid.model.create_hdf5_dataset_ref(ext_uuid, grid.uuid, 'unitIndices', root = ui_values) grid.model.create_ref_node('StratigraphicOrganization', grid.model.title(uuid = grid.stratigraphic_column_rank_uuid), grid.stratigraphic_column_rank_uuid, content_type = 'StratigraphicColumnRankInterpretation', root = strata_node) if grid.parent_window is not None: pw_node = rqet.SubElement(ijk, ns['resqml2'] + 'ParentWindow') pw_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'IjkParentWindow') pw_node.text = '\n' assert grid.parent_grid_uuid is not None parent_grid_root = grid.model.root(uuid = grid.parent_grid_uuid) if parent_grid_root is None: pg_title = 'ParentGrid' else: pg_title = rqet.citation_title_for_node(parent_grid_root) grid.model.create_ref_node('ParentGrid', pg_title, grid.parent_grid_uuid, content_type = 'obj_IjkGridRepresentation', root = pw_node) for axis in range(3): regrid_node = rqet.SubElement(pw_node, 'KJI'[axis] + 'Regrid') regrid_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'Regrid') regrid_node.text = '\n' if grid.is_refinement: if grid.parent_window.within_coarse_box is None: iiopg = 0 # InitialIndexOnParentGrid else: iiopg = grid.parent_window.within_coarse_box[0, axis] else: if grid.parent_window.within_fine_box is None: iiopg = 0 else: iiopg = grid.parent_window.within_fine_box[0, axis] iiopg_node = rqet.SubElement(regrid_node, ns['resqml2'] + 'InitialIndexOnParentGrid') iiopg_node.set(ns['xsi'] + 'type', ns['xsd'] + 'nonNegativeInteger') iiopg_node.text = str(iiopg) if grid.parent_window.fine_extent_kji[axis] == grid.parent_window.coarse_extent_kji[axis]: continue # one-to-noe mapping intervals_node = rqet.SubElement(regrid_node, ns['resqml2'] + 'Intervals') intervals_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'Intervals') intervals_node.text = '\n' if grid.parent_window.constant_ratios[axis] is not None: interval_count = 1 else: if grid.is_refinement: interval_count = grid.parent_window.coarse_extent_kji[axis] else: interval_count = grid.parent_window.fine_extent_kji[axis] ic_node = rqet.SubElement(intervals_node, ns['resqml2'] + 'IntervalCount') ic_node.set(ns['xsi'] + 'type', ns['xsd'] + 'positiveInteger') ic_node.text = str(interval_count) pcpi_node = rqet.SubElement(intervals_node, ns['resqml2'] + 'ParentCountPerInterval') pcpi_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'IntegerHdf5Array') pcpi_node.text = '\n' pcpi_values = rqet.SubElement(pcpi_node, ns['resqml2'] + 'Values') pcpi_values.set(ns['xsi'] + 'type', ns['eml'] + 'Hdf5Dataset') pcpi_values.text = '\n' grid.model.create_hdf5_dataset_ref(ext_uuid, grid.uuid, 'KJI'[axis] + 'Regrid/ParentCountPerInterval', root = pcpi_values) ccpi_node = rqet.SubElement(intervals_node, ns['resqml2'] + 'ChildCountPerInterval') ccpi_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'IntegerHdf5Array') ccpi_node.text = '\n' ccpi_values = rqet.SubElement(ccpi_node, ns['resqml2'] + 'Values') ccpi_values.set(ns['xsi'] + 'type', ns['eml'] + 'Hdf5Dataset') ccpi_values.text = '\n' grid.model.create_hdf5_dataset_ref(ext_uuid, grid.uuid, 'KJI'[axis] + 'Regrid/ChildCountPerInterval', root = ccpi_values) if grid.is_refinement and not grid.parent_window.equal_proportions[axis]: ccw_node = rqet.SubElement(intervals_node, ns['resqml2'] + 'ChildCellWeights') ccw_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'DoubleHdf5Array') ccw_node.text = rqet.null_xml_text ccw_values_node = rqet.SubElement(ccw_node, ns['resqml2'] + 'Values') ccw_values_node.set(ns['xsi'] + 'type', ns['resqml2'] + 'Hdf5Dataset') ccw_values_node.text = rqet.null_xml_text grid.model.create_hdf5_dataset_ref(ext_uuid, grid.uuid, 'KJI'[axis] + 'Regrid/ChildCellWeights', root = ccw_values_node) # todo: handle omit and cell overlap functionality as part of parent window refining or coarsening if write_geometry: __add_geometry_xml(ext_uuid, grid, ijk) if add_as_part: __add_as_part(add_relationships, ext_uuid, grid, ijk, write_geometry) if (write_active and grid.active_property_uuid is not None and grid.model.part(uuid = grid.active_property_uuid) is None): # TODO: replace following with call to rprop.write_hdf5_and_create_xml_for_active_property() active_collection = rprop.PropertyCollection() active_collection.set_support(support = grid) active_collection.create_xml(None, None, 'ACTIVE', 'active', p_uuid = grid.active_property_uuid, discrete = True, add_min_max = False, find_local_property_kinds = True) return ijk
def _write_hdf5_from_caches(grid, file = None, mode = 'a', geometry = True, imported_properties = None, write_active = None, stratigraphy = True, expand_const_arrays = False): """Create or append to an hdf5 file. Writes datasets for the grid geometry (and parent grid mapping) and properties from cached arrays. """ # NB: when writing a new geometry, all arrays must be set up and exist as the appropriate attributes prior to calling this function # if saving properties, active cell array should be added to imported_properties based on logical negation of inactive attribute # xml is not created here for property objects if write_active is None: write_active = geometry if geometry: grid.cache_all_geometry_arrays() if not file: file = grid.model.h5_file_name() h5_reg = rwh5.H5Register(grid.model) if stratigraphy and grid.stratigraphic_units is not None: h5_reg.register_dataset(grid.uuid, 'unitIndices', grid.stratigraphic_units, dtype = 'uint32') if geometry: __write_geometry(grid, h5_reg) if write_active and grid.inactive is not None: if imported_properties is None: imported_properties = rprop.PropertyCollection() imported_properties.set_support(support = grid) else: filtered_list = [] for entry in imported_properties.imported_list: if (entry[2].upper() == 'ACTIVE' or entry[10] == 'active') and entry[14] == 'cells': continue # keyword or property kind filtered_list.append(entry) imported_properties.imported_list = filtered_list # might have unintended side effects elsewhere active_mask = np.logical_not(grid.inactive) imported_properties.add_cached_array_to_imported_list(active_mask, 'active cell mask', 'ACTIVE', discrete = True, property_kind = 'active') if imported_properties is not None and imported_properties.imported_list is not None: for entry in imported_properties.imported_list: tail = 'points_patch0' if entry[18] else 'values_patch0' # expand constant arrays if required if not hasattr(imported_properties, entry[3]) and expand_const_arrays and entry[17] is not None: value = float(entry[17]) if isinstance(entry[17], str) else entry[17] assert entry[14] == 'cells' and entry[15] == 1 imported_properties.__dict__[entry[3]] = np.full(grid.extent_kji, value) if hasattr(imported_properties, entry[3]): # otherwise constant array not being expanded h5_reg.register_dataset(entry[0], tail, imported_properties.__dict__[entry[3]]) if entry[10] == 'active': grid.active_property_uuid = entry[0] h5_reg.write(file, mode = mode)
def pinchout_connection_set(grid, skip_inactive=True, compute_transmissibility=False, add_to_model=False, realization=None): """Returns (and caches) a GridConnectionSet representing juxtaposition across pinched out cells. arguments: skip_inactive (boolean, default True): if True, then cell face pairs involving an inactive cell will be omitted from the results compute_transmissibilities (boolean, default False): if True, then transmissibilities will be computed for the cell face pairs (unless already existing as a cached attribute of the grid) add_to_model (boolean, default False): if True, the connection set is written to hdf5 and xml is created; if compute_transmissibilty is True then the transmissibility property is also added realization (int, optional): if present, is used as the realization number when adding transmissibility property to model; ignored if compute_transmissibility is False returns: GridConnectionSet, numpy float array of shape (count,) transmissibilities (or None), where count is the number of cell face pairs in the grid connection set, which contains entries for all juxtaposed K faces separated logically by pinched out (zero thickness) cells; if there are no pinchouts (or no qualifying connections) then (None, None) will be returned """ if not hasattr(grid, 'pgcs') or grid.pgcs_skip_inactive != skip_inactive: grid.pgcs = rqf.pinchout_connection_set(grid, skip_inactive=skip_inactive) grid.pgcs_skip_inactive = skip_inactive if grid.pgcs is None: return None, None new_tr = False if compute_transmissibility and not hasattr(grid, 'array_pgcs_transmissibility'): grid.array_pgcs_transmissibility = grid.pgcs.tr_property_array() new_tr = True tr = grid.array_pgcs_transmissibility if hasattr( grid, 'array_pgcs_transmissibility') else None if add_to_model: if grid.model.uuid(uuid=grid.pgcs.uuid) is None: grid.pgcs.write_hdf5() grid.pgcs.create_xml() if new_tr: tr_pc = rprop.PropertyCollection() tr_pc.set_support(support=grid.pgcs) tr_pc.add_cached_array_to_imported_list( tr, 'computed for faces across pinchouts', 'pinchout transmissibility', discrete=False, uom='m3.cP/(kPa.d)' if grid.xy_units() == 'm' else 'bbl.cP/(psi.d)', property_kind='transmissibility', realization=realization, indexable_element='faces', count=1) tr_pc.write_hdf5_for_imported_list() tr_pc.create_xml_for_imported_list_and_add_parts_to_model() return grid.pgcs, tr
def fault_connection_set(grid, skip_inactive=True, compute_transmissibility=False, add_to_model=False, realization=None, inherit_features_from=None, title='fault juxtaposition set'): """Returns (and caches) a GridConnectionSet representing juxtaposition across faces with split pillars. arguments: skip_inactive (boolean, default True): if True, then cell face pairs involving an inactive cell will be omitted from the results compute_transmissibilities (boolean, default False): if True, then transmissibilities will be computed for the cell face pairs (unless already existing as a cached attribute of the grid) add_to_model (boolean, default False): if True, the connection set is written to hdf5 and xml is created; if compute_transmissibilty is True then the transmissibility property is also added realization (int, optional): if present, is used as the realization number when adding transmissibility property to model; ignored if compute_transmissibility is False inherit_features_from (GridConnectionSet, optional): if present, the features (named faults) are inherited from this grid connection set based on a match of either cell face in a juxtaposed pair title (string, default 'fault juxtaposition set'): the citation title to use if adding to model returns: GridConnectionSet, numpy float array of shape (count,) transmissibilities (or None), where count is the number of cell face pairs in the grid connection set, which contains entries for all juxtaposed faces with a split pillar as an edge; if the grid does not have split pillars (ie. is unfaulted) or there are no qualifying connections, (None, None) is returned """ if not hasattr(grid, 'fgcs') or grid.fgcs_skip_inactive != skip_inactive: grid.fgcs, grid.fgcs_fractional_area = rqtr.fault_connection_set( grid, skip_inactive=skip_inactive) grid.fgcs_skip_inactive = skip_inactive if grid.fgcs is None: return None, None new_tr = False if compute_transmissibility and not hasattr(grid, 'array_fgcs_transmissibility'): grid.array_fgcs_transmissibility = grid.fgcs.tr_property_array( grid.fgcs_fractional_area) new_tr = True tr = grid.array_fgcs_transmissibility if hasattr( grid, 'array_fgcs_transmissibility') else None if inherit_features_from is not None: grid.fgcs.inherit_features(inherit_features_from) grid.fgcs.clean_feature_list() if add_to_model: if grid.model.uuid(uuid=grid.fgcs.uuid) is None: grid.fgcs.write_hdf5() grid.fgcs.create_xml(title=title) if new_tr: tr_pc = rprop.PropertyCollection() tr_pc.set_support(support=grid.fgcs) tr_pc.add_cached_array_to_imported_list( grid.array_fgcs_transmissibility, 'computed for faces with split pillars', 'fault transmissibility', discrete=False, uom='m3.cP/(kPa.d)' if grid.xy_units() == 'm' else 'bbl.cP/(psi.d)', property_kind='transmissibility', realization=realization, indexable_element='faces', count=1) tr_pc.write_hdf5_for_imported_list() tr_pc.create_xml_for_imported_list_and_add_parts_to_model() return grid.fgcs, tr
def test_gcs_property_inheritance(tmp_path): epc = os.path.join(tmp_path, 'gcs_prop_inherit.epc') model = rq.Model(epc, new_epc=True, create_basics=True, create_hdf5_ext=True) # create a grid g = grr.RegularGrid(model, extent_kji=(5, 3, 3), dxyz=(10.0, 10.0, 1.0)) g.write_hdf5() g.create_xml(title='unsplit grid') # define an L shaped (in plan view) fault j_faces = np.zeros((g.nk, g.nj - 1, g.ni), dtype=bool) j_faces[:, 0, 1:] = True i_faces = np.zeros((g.nk, g.nj, g.ni - 1), dtype=bool) i_faces[:, 1:, 0] = True gcs = rqf.GridConnectionSet( model, grid=g, j_faces=j_faces, i_faces=i_faces, feature_name='L fault', create_organizing_objects_where_needed=True, create_transmissibility_multiplier_property=False) # check that connection set has the right number of cell face pairs assert gcs.count == g.nk * ((g.nj - 1) + (g.ni - 1)) # create a transmissibility multiplier property tm = np.arange(gcs.count).astype(float) if gcs.property_collection is None: gcs.property_collection = rqp.PropertyCollection() gcs.property_collection.set_support(support=gcs) pc = gcs.property_collection pc.add_cached_array_to_imported_list( tm, 'unit test', 'TMULT', uom='Euc', # actually a ratio of transmissibilities property_kind='transmissibility multiplier', local_property_kind_uuid=None, realization=None, indexable_element='faces') # write gcs which should also write property collection and create a local property kind gcs.write_hdf5() gcs.create_xml(write_new_properties=True) # check that a local property kind has materialised pk_uuid = model.uuid(obj_type='PropertyKind', title='transmissibility multiplier') assert pk_uuid is not None # check that we can create a surface object for the gcs surf = gcs.surface() assert surf is not None t, _ = surf.triangles_and_points() assert t.shape == (2 * gcs.count, 3) # create a derived grid connection set using a layer range thin_gcs, thin_indices = gcs.filtered_by_layer_range(min_k0=1, max_k0=3, return_indices=True) assert thin_gcs is not None and thin_indices is not None assert thin_gcs.count == 3 * ((g.nj - 1) + (g.ni - 1)) # inherit the transmissibility multiplier property thin_gcs.inherit_properties_for_selected_indices(gcs, thin_indices) thin_gcs.write_hdf5() thin_gcs.create_xml() # by default will include write of new properties # check that the inheritance has worked assert thin_gcs.property_collection is not None and thin_gcs.property_collection.number_of_parts( ) > 0 thin_pc = thin_gcs.property_collection tm_part = thin_pc.singleton(property_kind='transmissibility multiplier') assert tm_part is not None thin_tm = thin_pc.cached_part_array_ref(tm_part) assert thin_tm is not None and thin_tm.ndim == 1 assert thin_tm.size == thin_gcs.count assert_array_almost_equal(thin_tm, tm[thin_indices]) # check that get_combined...() method can execute using property collection b_a, i_a, f_a = gcs.get_combined_fault_mask_index_value_arrays( min_k=1, max_k=3, property_name='Transmissibility multiplier', ref_k=2) assert b_a is not None and i_a is not None and f_a is not None # check that transmissibility multiplier values have been sampled correctly from property array assert f_a.shape == (g.nj, g.ni, 2, 2) assert np.count_nonzero( np.isnan(f_a)) == 4 * g.nj * g.ni - 2 * ((g.nj - 1) + (g.ni - 1)) assert np.nanmax(f_a) > np.nanmin(f_a) restore = np.seterr(all='ignore') assert np.all(np.logical_or(np.isnan(f_a), f_a >= np.nanmin(thin_tm))) assert np.all(np.logical_or(np.isnan(f_a), f_a <= np.nanmax(thin_tm))) np.seterr(**restore)
def test_property_collection(example_model_and_crs): # Create a WellboreMarkerFrame object # Load example model from a fixture model, crs = example_model_and_crs # Create a trajectory well_name = 'Banoffee' elevation = 100 datum = resqpy.well.MdDatum(parent_model=model, crs_uuid=crs.uuid, location=(0, 0, -elevation), md_reference='kelly bushing') mds = np.array([300.0, 310.0, 330.0]) zs = mds - elevation source_dataframe = pd.DataFrame({ 'MD': mds, 'X': [150.0, 165.0, 180.0], 'Y': [240.0, 260.0, 290.0], 'Z': zs, }) trajectory = resqpy.well.Trajectory(parent_model=model, data_frame=source_dataframe, well_name=well_name, md_datum=datum, length_uom='m') trajectory.write_hdf5() trajectory.create_xml() trajectory_uuid = trajectory.uuid # Create features and interpretations horizon_feature_1 = rqo.GeneticBoundaryFeature( parent_model=model, kind='horizon', feature_name='horizon_feature_1') horizon_feature_1.create_xml() horizon_interp_1 = rqo.HorizonInterpretation( parent_model=model, title='horizon_interp_1', genetic_boundary_feature=horizon_feature_1, sequence_stratigraphy_surface='flooding', boundary_relation_list=['conformable']) horizon_interp_1.create_xml() woc_feature_1 = rqo.FluidBoundaryFeature(parent_model=model, kind='water oil contact', feature_name='woc_1') # fluid boundary feature does not have an associated interpretation woc_feature_1.create_xml() fault_feature_1 = rqo.TectonicBoundaryFeature( parent_model=model, kind='fault', feature_name='fault_feature_1') fault_feature_1.create_xml() fault_interp_1 = rqo.FaultInterpretation( parent_model=model, title='fault_interp_1', tectonic_boundary_feature=fault_feature_1, is_normal=True, maximum_throw=15) fault_interp_1.create_xml() df = pd.DataFrame({ 'MD': [400.0, 410.0, 430.0], 'Boundary_Feature_Type': ['horizon', 'water oil contact', 'fault'], 'Marker_Citation_Title': ['marker_horizon_1', 'marker_woc_1', 'marker_fault_1'], 'Interp_Citation_Title': ['horizon_interp_1', None, 'fault_interp_1'], }) # Create a wellbore marker frame from a dataframe wellbore_marker_frame = resqpy.well.WellboreMarkerFrame.from_dataframe( parent_model=model, dataframe=df, trajectory_uuid=trajectory_uuid, title='WBF1', originator='Human', extra_metadata={'target_reservoir': 'treacle'}) wellbore_marker_frame.write_hdf5() wellbore_marker_frame.create_xml() # create a property collection for the wellbore marker frame and add a couple of properties pc = rqp.PropertyCollection(support=wellbore_marker_frame) assert pc is not None node_prop = np.array([123.45, -456.78, 987.65]) pc.add_cached_array_to_imported_list(node_prop, source_info='unit test', keyword='node prop', uom='m', property_kind='length', indexable_element='nodes') interval_prop = np.array([3, 1], dtype=int) pc.add_cached_array_to_imported_list(interval_prop, source_info='unit test', keyword='interval prop', discrete=True, null_value=-1, property_kind='discrete', indexable_element='intervals') pc.write_hdf5_for_imported_list() pc.create_xml_for_imported_list_and_add_parts_to_model() del pc # reload the property collection pc = rqp.PropertyCollection(support=wellbore_marker_frame) node_prop_part = model.part(obj_type='ContinuousProperty', title='node prop') interval_prop_part = model.part(obj_type='DiscreteProperty', title='interval prop') # check the property arrays assert pc is not None assert pc.number_of_parts() == 2 assert node_prop_part is not None assert interval_prop_part is not None assert node_prop_part in pc.parts() assert interval_prop_part in pc.parts() assert_array_almost_equal(pc.cached_part_array_ref(node_prop_part), [123.45, -456.78, 987.65]) int_prop_array = pc.cached_part_array_ref(interval_prop_part) assert int_prop_array.size == 2 assert np.all(int_prop_array == (3, 1))
def grid_columns_property_from_gcs_property(model, gcs_property_uuid, null_value=np.nan, title=None, multiple_handling='default'): """Derives a new grid columns property (map) from a single-grid gcs property using values for k faces. arguments: model (Model): the model in which the existing objects are to be found and the new property added gcs_property_uuid (UUID): the uuid of the existing grid connection set property null_value (float or int, default NaN): the value to use in columns where no K faces are present in the gcs title (str, optional): the title for the new grid property; defaults to that of the gcs property multiple_handling (str, default 'mean'): one of 'default', 'mean', 'min', 'max', 'min_k', 'max_k', 'exception'; determines how a value is generated when more than one K face is present in the gcs for a column returns: uuid of the newly created Property (RESQML ContinuousProperty, DiscreteProperty, CategoricalProperty or PointsProperty) notes: the grid connection set which is the support for gcs_property must involve only one grid; the resulting columns grid property is of the same class as the original gcs property; the write_hdf() and create_xml() methods are called by this function, for the new property, which is added to the model; the default multiple handling mode is mean for continuous data, any for discrete (inc categorical); in the case of discrete (including categorical) data, a null_value of NaN will be changed to -1 """ assert multiple_handling in [ 'default', 'mean', 'min', 'max', 'any', 'exception' ] assert gcs_property_uuid is not None and bu.is_uuid(gcs_property_uuid) gcs_property = rqp.Property(model, uuid=gcs_property_uuid) assert gcs_property is not None support_uuid = gcs_property.collection.support_uuid assert support_uuid is not None assert model.type_of_uuid( support_uuid, strip_obj=True) == 'GridConnectionSetRepresentation' gcs = rqf.GridConnectionSet(model, uuid=support_uuid) gcs_prop_array = gcs_property.array_ref() if gcs_property.is_continuous(): dtype = float if multiple_handling == 'default': multiple_handling = 'mean' else: dtype = int if null_value == np.nan: null_value = -1 elif type(null_value) is float: null_value = int(null_value) if multiple_handling == 'default': multiple_handling = 'any' assert multiple_handling != 'mean', 'mean specified as multiple handling for non-continuous property' assert gcs.number_of_grids( ) == 1, 'only single grid gcs supported for grid columns property derivation' grid = gcs.grid_list[0] if gcs_property.is_points(): map_shape = (grid.nj, grid.ni, 3) else: map_shape = (grid.nj, grid.ni) assert gcs_property.count() == 1 map = np.full(map_shape, null_value, dtype=dtype) count_per_col = np.zeros((grid.nj, grid.ni), dtype=int) cells_and_faces = gcs.list_of_cell_face_pairs_for_feature_index(None) assert cells_and_faces is not None and len(cells_and_faces) == 2 cell_pairs, face_pairs = cells_and_faces if cell_pairs is None or len(cell_pairs) == 0: log.warning( 'no faces found for grid connection set property {gcs_property.title}' ) return None assert len(cell_pairs) == len(face_pairs) assert len(cell_pairs) == len(gcs_prop_array) for index in range(len(cell_pairs)): if face_pairs[index, 0, 0] != 0 or face_pairs[index, 1, 0] != 0: continue # not a K face col_j, col_i = cell_pairs[index, 0, 1:] # assume paired cell is in same column! if count_per_col[col_j, col_i] == 0 or multiple_handling == 'any': map[col_j, col_i] = gcs_prop_array[index] elif multiple_handling == 'mean': map[col_j, col_i] = (((map[col_j, col_i] * count_per_col[col_j, col_i]) + gcs_prop_array[index]) / float(count_per_col[col_j, col_i] + 1)) elif multiple_handling == 'min': if gcs_prop_array[index] < map[col_j, col_i]: map[col_j, col_i] = gcs_prop_array[index] elif multiple_handling == 'max': if gcs_prop_array[index] > map[col_j, col_i]: map[col_j, col_i] = gcs_prop_array[index] else: raise ValueError( 'multiple grid connection set K faces found for column') count_per_col[col_j, col_i] += 1 # create an empty PropertyCollection and add the map data as a new property time_index = gcs_property.time_index() pc = rqp.PropertyCollection() pc.set_support(support=grid) pc.add_cached_array_to_imported_list( map, source_info=f'{gcs.title} property {gcs_property.title} map', keyword=title if title else gcs_property.title, discrete=not gcs_property.is_continuous(), uom=gcs_property.uom(), time_index=time_index, null_value=None if gcs_property.is_continuous() else null_value, property_kind=gcs_property.property_kind(), local_property_kind_uuid=gcs_property.local_property_kind_uuid(), facet_type=gcs_property.facet_type(), facet=gcs_property.facet(), realization=None, # do we want to preserve the realisation number? indexable_element='columns', points=gcs_property.is_points()) time_series_uuid = pc.write_hdf5_for_imported_list() string_lookup_uuid = gcs_property.string_lookup_uuid() time_series_uuid = None if time_index is None else gcs_property.time_series_uuid( ) new_uuids = pc.create_xml_for_imported_list_and_add_parts_to_model( time_series_uuid=time_series_uuid, string_lookup_uuid=string_lookup_uuid) assert len(new_uuids) == 1 return new_uuids[0]
def from_unsplit_grid(cls, parent_model, grid_uuid, inherit_properties = True, title = None, extra_metadata = {}, write_active = None): """Creates a new (unstructured) HexaGrid from an existing resqpy unsplit (IJK) Grid without K gaps. arguments: parent_model (model.Model object): the model which this grid is part of grid_uuid (uuid.UUID): the uuid of an IjkGridRepresentation from which the hexa grid will be created inherit_properties (boolean, default True): if True, properties will be created for the new grid title (str, optional): citation title for the new grid extra_metadata (dict, optional): dictionary of extra metadata items to add to the grid write_active (boolean, optional): if True (or None and inactive property is established) then an active cell property is created (in addition to any inherited properties) returns: a newly created HexaGrid object note: this method includes the writing of hdf5 data, creation of xml for the new grid and adding it as a part """ import resqpy.grid as grr # establish existing IJK grid ijk_grid = grr.Grid(parent_model, uuid = grid_uuid, find_properties = inherit_properties) assert ijk_grid is not None assert not ijk_grid.has_split_coordinate_lines, 'IJK grid has split coordinate lines (faults)' assert not ijk_grid.k_gaps, 'IJK grid has K gaps' ijk_grid.cache_all_geometry_arrays() ijk_points = ijk_grid.points_ref(masked = False) if title is None: title = ijk_grid.title # make empty unstructured hexa grid hexa_grid = cls(parent_model, title = title, extra_metadata = extra_metadata) # derive hexa grid attributes from ijk grid hexa_grid.crs_uuid = ijk_grid.crs_uuid hexa_grid.set_cell_count(ijk_grid.cell_count()) if ijk_grid.inactive is not None: hexa_grid.inactive = ijk_grid.inactive.reshape((hexa_grid.cell_count,)) hexa_grid.all_inactive = np.all(hexa_grid.inactive) if hexa_grid.all_inactive: log.warning(f'all cells marked as inactive for unstructured hexa grid {hexa_grid.title}') else: hexa_grid.all_inactive = False # inherit points (nodes) in IJK grid order, ie. K cycling fastest, then I, then J hexa_grid.points_cached = ijk_points.reshape((-1, 3)) # setup faces per cell # ordering of faces (in nodes per face): all K faces, then all J faces, then all I faces # within J faces, ordering is all of J- faces for J = 0 first, then increasing planes in J # similarly for I faces nk_plus_1 = ijk_grid.nk + 1 nj_plus_1 = ijk_grid.nj + 1 ni_plus_1 = ijk_grid.ni + 1 k_face_count = nk_plus_1 * ijk_grid.nj * ijk_grid.ni j_face_count = ijk_grid.nk * nj_plus_1 * ijk_grid.ni i_face_count = ijk_grid.nk * ijk_grid.nj * ni_plus_1 kj_face_count = k_face_count + j_face_count hexa_grid.face_count = k_face_count + j_face_count + i_face_count hexa_grid.faces_per_cell_cl = 6 * (1 + np.arange(hexa_grid.cell_count, dtype = int)) # 6 faces per cell hexa_grid.faces_per_cell = np.empty(6 * hexa_grid.cell_count, dtype = int) arange = np.arange(hexa_grid.cell_count, dtype = int) hexa_grid.faces_per_cell[0::6] = arange # K- faces hexa_grid.faces_per_cell[1::6] = ijk_grid.nj * ijk_grid.ni + arange # K+ faces nki = ijk_grid.nk * ijk_grid.ni nkj = ijk_grid.nk * ijk_grid.nj # todo: vectorise following for loop for cell in range(hexa_grid.cell_count): k, j, i = ijk_grid.denaturalized_cell_index(cell) j_minus_face = k_face_count + nki * j + ijk_grid.ni * k + i hexa_grid.faces_per_cell[6 * cell + 2] = j_minus_face # J- face hexa_grid.faces_per_cell[6 * cell + 3] = j_minus_face + nki # J+ face i_minus_face = kj_face_count + nkj * i + ijk_grid.nj * k + j hexa_grid.faces_per_cell[6 * cell + 4] = i_minus_face # I- face hexa_grid.faces_per_cell[6 * cell + 5] = i_minus_face + nkj # I+ face # setup nodes per face, clockwise when viewed from negative side of face if ijk handedness matches xyz handedness # ordering of nodes in points array is as for the IJK grid hexa_grid.node_count = hexa_grid.points_cached.shape[0] assert hexa_grid.node_count == (ijk_grid.nk + 1) * (ijk_grid.nj + 1) * (ijk_grid.ni + 1) hexa_grid.nodes_per_face_cl = 4 * (1 + np.arange(hexa_grid.face_count, dtype = int)) # 4 nodes per face hexa_grid.nodes_per_face = np.empty(4 * hexa_grid.face_count, dtype = int) # todo: vectorise for loops # K faces face_base = 0 for k in range(nk_plus_1): for j in range(ijk_grid.nj): for i in range(ijk_grid.ni): hexa_grid.nodes_per_face[face_base] = (k * nj_plus_1 + j) * ni_plus_1 + i # ip 0, jp 0 hexa_grid.nodes_per_face[face_base + 1] = (k * nj_plus_1 + j + 1) * ni_plus_1 + i # ip 0, jp 1 hexa_grid.nodes_per_face[face_base + 2] = (k * nj_plus_1 + j + 1) * ni_plus_1 + i + 1 # ip 1, jp 1 hexa_grid.nodes_per_face[face_base + 3] = (k * nj_plus_1 + j) * ni_plus_1 + i + 1 # ip 1, jp 0 face_base += 4 # J faces assert face_base == 4 * k_face_count for j in range(nj_plus_1): for k in range(ijk_grid.nk): for i in range(ijk_grid.ni): hexa_grid.nodes_per_face[face_base] = (k * nj_plus_1 + j) * ni_plus_1 + i # ip 0, kp 0 hexa_grid.nodes_per_face[face_base + 1] = (k * nj_plus_1 + j) * ni_plus_1 + i + 1 # ip 1, kp 0 hexa_grid.nodes_per_face[face_base + 2] = ((k + 1) * nj_plus_1 + j) * ni_plus_1 + i + 1 # ip 1, kp 1 hexa_grid.nodes_per_face[face_base + 3] = ((k + 1) * nj_plus_1 + j) * ni_plus_1 + i # ip 0, kp 1 face_base += 4 # I faces assert face_base == 4 * kj_face_count for i in range(ni_plus_1): for k in range(ijk_grid.nk): for j in range(ijk_grid.nj): hexa_grid.nodes_per_face[face_base] = (k * nj_plus_1 + j) * ni_plus_1 + i # jp 0, kp 0 hexa_grid.nodes_per_face[face_base + 1] = ((k + 1) * nj_plus_1 + j) * ni_plus_1 + i # jp 0, kp 1 hexa_grid.nodes_per_face[face_base + 2] = ((k + 1) * nj_plus_1 + j + 1) * ni_plus_1 + i # jp 1, kp 1 hexa_grid.nodes_per_face[face_base + 3] = (k * nj_plus_1 + j + 1) * ni_plus_1 + i # jp 1, kp 0 face_base += 4 assert face_base == 4 * hexa_grid.face_count # set cell face is right handed # todo: check Energistics documents for meaning of cell face is right handed # here the assumption is clockwise ordering of nodes viewed from within cell means 'right handed' hexa_grid.cell_face_is_right_handed = np.zeros(6 * hexa_grid.cell_count, dtype = bool) # initially set to left handed # if IJK grid's ijk handedness matches the xyz handedness, then set +ve faces to right handed; else -ve faces if ijk_grid.off_handed(): hexa_grid.cell_face_is_right_handed[0::2] = True # negative faces are right handed else: hexa_grid.cell_face_is_right_handed[1::2] = True # positive faces are right handed hexa_grid.write_hdf5(write_active = write_active) hexa_grid.create_xml(write_active = write_active) if inherit_properties: ijk_pc = ijk_grid.extract_property_collection() hexa_pc = rqp.PropertyCollection(support = hexa_grid) for part in ijk_pc.parts(): count = ijk_pc.count_for_part(part) hexa_part_shape = (hexa_grid.cell_count,) if count == 1 else (hexa_grid.cell_count, count) hexa_pc.add_cached_array_to_imported_list( ijk_pc.cached_part_array_ref(part).reshape(hexa_part_shape), 'inherited from grid ' + str(ijk_grid.title), ijk_pc.citation_title_for_part(part), discrete = not ijk_pc.continuous_for_part(part), uom = ijk_pc.uom_for_part(part), time_index = ijk_pc.time_index_for_part(part), null_value = ijk_pc.null_value_for_part(part), property_kind = ijk_pc.property_kind_for_part(part), local_property_kind_uuid = ijk_pc.local_property_kind_uuid(part), facet_type = ijk_pc.facet_type_for_part(part), facet = ijk_pc.facet_for_part(part), realization = ijk_pc.realization_for_part(part), indexable_element = ijk_pc.indexable_for_part(part), count = count, const_value = ijk_pc.constant_value_for_part(part)) # todo: patch min & max values if present in ijk part hexa_pc.write_hdf5_for_imported_list() hexa_pc.create_xml_for_imported_list_and_add_parts_to_model( support_uuid = hexa_grid.uuid, time_series_uuid = ijk_pc.time_series_uuid_for_part(part), string_lookup_uuid = ijk_pc.string_lookup_uuid_for_part(part), extra_metadata = ijk_pc.extra_metadata_for_part(part)) return hexa_grid
def __init__( self, model, support_root=None, # deprecated uuid=None, df=None, uom_list=None, realization=None, title='dataframe', column_lookup_uuid=None, uom_lookup_uuid=None, extra_metadata=None): """Create a new Dataframe object from either a previously stored property or a pandas dataframe. arguments: model (model.Model): the model to which the new Dataframe will be attached support_root (lxml.Element, DEPRECATED): use uuid instead uuid (uuid.UUID, optional): the uuid of an existing Grid2dRepresentation object acting as support for a dataframe property (or holding the dataframe as z values) df (pandas.DataFrame, optional): a dataframe from which the new Dataframe is to be created; if both uuid (or support_root) and df are supplied, realization must not be None and a new realization property will be created uom_list (list of str, optional): a list holding the units of measure for each column; if present, length of list must match number of columns in df; ignored if uuid or support_root is not None realization (int, optional): if present, the realization number of the RESQML property holding the dataframe title (str, default 'dataframe'): used as the citation title for the Mesh (and property); ignored if uuid or support_root is not None column_lookup_uuid (uuid, optional): if present, the uuid of a string lookup table holding the column names; if present, the contents and order of the table must match the columns in the dataframe; if absent, a new lookup table will be created; ignored if support_root is not None uom_lookup_uuid (uuid, optional): if present, the uuid of a string lookup table holding the units of measure for each column; if None and uom_list is present, a new table will be created; if both uom_list and uom_lookup_uuid are present, their contents must match; ignored if support_root is not None extra_metadata (dict, optional): if present, a dictionary of extra metadata items, str: str; ignored if uuid (or support_root) is not None returns: a newly created Dataframe object notes: when initialising from an existing RESQML object, the supporting mesh and its property should have been originally created using this class; when working with ensembles, each object of this class will only handle the data for one realization, though they may share a common support_root """ assert uuid is not None or support_root is not None or df is not None assert (uuid is None and support_root is None) or df is None or realization is not None if uuid is None: if support_root is not None: warnings.warn( "support_root parameter is deprecated, use uuid instead", DeprecationWarning) uuid = rqet.uuid_for_part_root(support_root) else: support_root = model.root_for_uuid(uuid) self.model = model self.df = None self.n_rows = self.n_cols = 0 self.uom_list = None self.realization = realization self.title = title self.mesh = None # only generated when needed for write_hdf5(), create_xml() self.pc = None # property collection; only generated when needed for write_hdf5(), create_xml() self.column_lookup_uuid = column_lookup_uuid self.column_lookup = None # string lookup table mapping column index (0 based) to column name self.uom_lookup_uuid = uom_lookup_uuid self.uom_lookup = None # string lookup table mapping column index (0 based) to uom self.extra_metadata = extra_metadata if uuid is not None: assert rqet.node_type(support_root) == 'obj_Grid2dRepresentation' self.mesh = rqs.Mesh(self.model, uuid=uuid) self.extra_metadata = self.mesh.extra_metadata assert 'dataframe' in self.extra_metadata and self.extra_metadata[ 'dataframe'] == 'true' self.title = self.mesh.title self.n_rows, self.n_cols = self.mesh.nj, self.mesh.ni cl_uuid = self.model.uuid(obj_type='StringTableLookup', related_uuid=uuid, title='dataframe columns') assert cl_uuid is not None, 'column name lookup table not found for dataframe' self.column_lookup = rqp.StringLookup(self.model, uuid=cl_uuid) self.column_lookup_uuid = self.column_lookup.uuid assert self.column_lookup.length() == self.n_cols ul_uuid = self.model.uuid(obj_type='StringTableLookup', related_uuid=uuid, title='dataframe units') if ul_uuid is not None: self.uom_lookup = rqp.StringLookup(self.model, uuid=ul_uuid) self.uom_lookup_uuid = self.uom_lookup.uuid self.uom_list = self.uom_lookup.get_list() da = self.mesh.full_array_ref( )[..., 2] # dataframe data as 2D numpy array, defaulting to z values in mesh existing_pc = rqp.PropertyCollection(support=self.mesh) existing_count = 0 if existing_pc is None else existing_pc.number_of_parts( ) if df is None: # existing dara, either in mesh or property if existing_count > 0: # use property data instead of z values if existing_count == 1: if self.realization is not None: assert existing_pc.realization_for_part( existing_pc.singleton()) == self.realization else: assert self.realization is not None, 'no realization specified when accessing ensemble dataframe' da = existing_pc.single_array_ref( realization=self.realization) assert da is not None and da.ndim == 2 and da.shape == ( self.n_rows, self.n_cols) else: assert realization is None self.df = pd.DataFrame(da, columns=self.column_lookup.get_list()) else: # both support_root and df supplied: add a new realisation if existing_count > 0: assert existing_pc.singleton( realization=self.realization ) is None, 'dataframe realization already exists' self.df = df.copy() assert len(self.df) == self.n_rows assert len(self.df.columns) == self.n_rows else: assert df is not None, 'no dataframe (or support root) provided when instantiating DataFrame object' self.df = df.copy() # todo: check data type of columns – restrict to numerical data self.n_rows = len(self.df) self.n_cols = len(self.df.columns) if column_lookup_uuid is not None: self.column_lookup = rqp.StringLookup(self.model, uuid=column_lookup_uuid) assert self.column_lookup is not None assert self.column_lookup.length() == self.n_cols assert all(self.df.columns == self.column_lookup.get_list() ) # exact match of column names required! if uom_lookup_uuid is not None: self.uom_lookup = rqp.StringLookup(self.model, uuid=uom_lookup_uuid) assert self.uom_lookup is not None if uom_list is not None: assert len(uom_list) == self.n_cols self.uom_list = uom_list.copy() if self.uom_lookup is not None: assert self.uom_list == self.uom_lookup.get_list() elif self.uom_lookup is not None: self.uom_list = self.uom_lookup.get_list()
def add_one_blocked_well_property(epc_file, a, property_kind, blocked_well_uuid, source_info='imported', title=None, discrete=False, uom=None, time_index=None, time_series_uuid=None, string_lookup_uuid=None, null_value=None, indexable_element='cells', facet_type=None, facet=None, realization=None, local_property_kind_uuid=None, count_per_element=1, points=False, extra_metadata={}, new_epc_file=None): """Adds a blocked well property from a numpy array to an existing resqml dataset. arguments: epc_file (string): file name to load model resqml model from (and rewrite to if new_epc_file is None) a (1D numpy array): the blocked well property array to be added to the model property_kind (string): the resqml property kind blocked_well_uuid (uuid object or string): the uuid of the blocked well to which the property relates source_info (string): typically the name of a file from which the array has been read but can be any information regarding the source of the data title (string): this will be used as the citation title when a part is generated for the array discrete (boolean, default False): if True, the array should contain integer (or boolean) data; if False, float uom (string, default None): the resqml units of measure for the data; not relevant to discrete data time_index (integer, default None): if not None, the time index to be used when creating a part for the array time_series_uuid (uuid object or string, default None): required if time_index is not None string_lookup_uuid (uuid object or string, optional): required if the array is to be stored as a categorical property; set to None for non-categorical discrete data; only relevant if discrete is True null_value (int, default None): if present, this is used in the metadata to indicate that this value is to be interpreted as a null value wherever it appears in the data (use for discrete data only) indexable_element (string, default 'cells'): the indexable element in the supporting representation (the blocked well); valid values are 'cells', 'intervals' (which includes unblocked intervals), or 'nodes' facet_type (string): resqml facet type, or None facet (string): resqml facet, or None realization (int): realization number, or None local_property_kind_uuid (uuid.UUID or string): uuid of local property kind, or None count_per_element (int, default 1): the number of values per indexable element; if greater than one then this must be the fastest cycling axis in the cached array, ie last index; if greater than 1 then a must be a 2D array points (bool, default False): if True, this is a points property with an extra dimension of extent 3 extra_metadata (dict, optional): any items in this dictionary are added as extra metadata to the new property new_epc_file (string, optional): if None, the source epc_file is extended with the new property object; if present, a new epc file (& associated h5 file) is created to contain a copy of the blocked well (and dependencies) and the new property returns: uuid.UUID of newly created property object """ if new_epc_file and epc_file and ( (new_epc_file == epc_file) or (os.path.exists(new_epc_file) and os.path.exists(epc_file) and os.path.samefile(new_epc_file, epc_file))): new_epc_file = None # open up model and establish grid object model = rq.Model(epc_file) blocked_well = rqw.BlockedWell(model, uuid=blocked_well_uuid) assert blocked_well is not None, f'no blocked well object found with uuid {blocked_well_uuid}' if not discrete: string_lookup_uuid = None # create an empty property collection and add the new array to its 'imported' list bwpc = rqp.PropertyCollection() bwpc.set_support(support=blocked_well, model=model) bwpc.add_cached_array_to_imported_list( a, source_info, title, discrete=discrete, uom=uom, time_index=time_index, null_value=null_value, property_kind=property_kind, local_property_kind_uuid=local_property_kind_uuid, facet_type=facet_type, facet=facet, realization=realization, indexable_element=indexable_element, count=count_per_element, points=points) bwpc.write_hdf5_for_imported_list() uuid_list = bwpc.create_xml_for_imported_list_and_add_parts_to_model( time_series_uuid=time_series_uuid, string_lookup_uuid=string_lookup_uuid, property_kind_uuid=local_property_kind_uuid, extra_metadata=extra_metadata) assert len(uuid_list) == 1 model.store_epc() return uuid_list[0]