Exemple #1
0
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)]
Exemple #3
0
    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
Exemple #5
0
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
Exemple #6
0
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)
Exemple #7
0
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
Exemple #8
0
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
Exemple #9
0
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)
Exemple #10
0
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))
Exemple #11
0
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]
Exemple #12
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
Exemple #13
0
    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()
Exemple #14
0
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]