Ejemplo n.º 1
0
    def extract_crs_root_and_uuid(self):
        """Caches uuid for coordinate reference system, as stored in geometry xml sub-tree."""

        if self.crs_uuid is None:
            crs_root = rqet.find_nested_tags(self.node,
                                             ['Geometry', 'LocalCrs'])
            assert crs_root is not None, 'failed to find crs reference in triangulated patch xml'
            self.crs_uuid = bu.uuid_from_string(
                rqet.find_tag_text(crs_root, 'UUID'))
        else:
            crs_root = self.model.root_for_uuid(self.crs_uuid)
        return crs_root, self.crs_uuid
Ejemplo n.º 2
0
def _add_part_to_dict_get_null_constvalue_points(xml_node, continuous, points):
    null_value = None
    if not continuous:
        null_value = rqet.find_nested_tags_int(
            xml_node, ['PatchOfValues', 'Values', 'NullValue'])
    const_value = None
    if points:
        values_node = rqet.find_nested_tags(xml_node,
                                            ['PatchOfPoints', 'Points'])
    else:
        values_node = rqet.find_nested_tags(xml_node,
                                            ['PatchOfValues', 'Values'])
    values_type = rqet.node_type(values_node)
    assert values_type is not None
    if values_type.endswith('ConstantArray'):
        if continuous:
            const_value = rqet.find_tag_float(values_node, 'Value')
        elif values_type.startswith('Bool'):
            const_value = rqet.find_tag_bool(values_node, 'Value')
        else:
            const_value = rqet.find_tag_int(values_node, 'Value')

    return null_value, const_value
Ejemplo n.º 3
0
def test_h5_array_element(example_model_with_properties):
    model = example_model_with_properties
    zone_root = model.root(obj_type='DiscreteProperty', title='Zone')
    assert zone_root is not None
    key_pair = model.h5_uuid_and_path_for_node(
        rqet.find_nested_tags(zone_root, ['PatchOfValues', 'Values']))
    assert key_pair is not None and all([x is not None for x in key_pair])
    # check full array is expected size and shape
    shape, dtype = model.h5_array_shape_and_type(key_pair)
    assert shape == (3, 5, 5)
    assert str(dtype)[0] == 'i'
    # test single element access
    zone = model.h5_array_element(key_pair, index=(1, 2, 2), dtype=int)
    assert zone == 2
    # test single element access with required shape
    zone = model.h5_array_element(key_pair,
                                  index=(1, 7),
                                  dtype=int,
                                  required_shape=(3, 25))
    assert zone == 2
Ejemplo n.º 4
0
def extract_k_gaps(grid):
    """Returns information about gaps (voids) between layers in the grid.

    returns:
       (int, numpy bool array, numpy int array) being the number of gaps between layers;
       a 1D bool array of extent nk-1 set True where there is a gap below the layer; and
       a 1D int array being the k index to actually use in the points data for each layer k0

    notes:
       all returned elements are stored as attributes in the grid object; int and bool array elements
       will be None if there are no k gaps; each k gap implies an extra element in the points data for
       each pillar; when wanting to index k interfaces (horizons) rather than layers, the last of the
       returned values can be used to index the k axis of the points data to yield the top face of the
       layer and the successor in k will always index the basal face of the same layer
    """

    if grid.k_gaps is not None:
        return grid.k_gaps, grid.k_gap_after_array, grid.k_raw_index_array
    grid.k_gaps = rqet.find_nested_tags_int(grid.root, ['KGaps', 'Count'])
    if grid.k_gaps:
        k_gap_after_root = rqet.find_nested_tags(grid.root, ['KGaps', 'GapAfterLayer'])
        assert k_gap_after_root is not None
        bool_array_type = rqet.node_type(k_gap_after_root)
        assert bool_array_type == 'BooleanHdf5Array'  # could be a constant array but not handled by this code
        h5_key_pair = grid.model.h5_uuid_and_path_for_node(k_gap_after_root)
        assert h5_key_pair is not None
        grid.model.h5_array_element(h5_key_pair,
                                    index = None,
                                    cache_array = True,
                                    object = grid,
                                    array_attribute = 'k_gap_after_array',
                                    dtype = 'bool')
        assert hasattr(grid, 'k_gap_after_array')
        assert grid.k_gap_after_array.ndim == 1 and grid.k_gap_after_array.size == grid.nk - 1
        grid._set_k_raw_index_array()
    else:
        grid.k_gap_after_array = None
        grid.k_raw_index_array = np.arange(grid.nk, dtype = int)
    return grid.k_gaps, grid.k_gap_after_array, grid.k_raw_index_array
Ejemplo n.º 5
0
    def _load_from_xml(self, marker_node):
        """Load attributes from xml.

        This is invoked as part of the init method when an existing uuid is given.

        Returns:
           [bool]: True if successful
        """

        assert marker_node is not None

        # Load XML data
        uuid_str = marker_node.attrib.get('uuid')
        if uuid_str:
            self.uuid = bu.uuid_from_string(uuid_str)
        citation_tag = rqet.find_nested_tags(root=marker_node,
                                             tag_list=['Citation'])
        assert citation_tag is not None
        self.title = rqet.find_tag_text(root=citation_tag, tag_name='Title')
        self.originator = rqet.find_tag_text(root=citation_tag,
                                             tag_name='Originator')

        self.marker_type = None
        for boundary_feature_type in [
                'GeologicBoundaryKind', 'FluidMarker', 'FluidContact'
        ]:
            found_tag_text = rqet.find_tag_text(root=marker_node,
                                                tag_name=boundary_feature_type)
            if found_tag_text is not None:
                self.marker_type = found_tag_text
                break
        assert self.marker_type is not None

        self.interpretation_uuid = bu.uuid_from_string(
            rqet.find_nested_tags_text(root=marker_node,
                                       tag_list=['Interpretation', 'UUID']))
        self.extra_metadata = rqet.load_metadata_from_xml(node=marker_node)

        return True
Ejemplo n.º 6
0
def drape_to_surface(epc_file,
                     source_grid=None,
                     surface=None,
                     scaling_factor=None,
                     ref_k0=0,
                     ref_k_faces='top',
                     quad_triangles=True,
                     border=None,
                     store_displacement=False,
                     inherit_properties=False,
                     inherit_realization=None,
                     inherit_all_realizations=False,
                     new_grid_title=None,
                     new_epc_file=None):
    """Return a new grid with geometry draped to a surface.
    
    Extend a resqml model with a new grid where the reference layer boundary of the source grid has been re-draped
    to a surface.

    arguments:
       epc_file (string): file name to rewrite the model's xml to; if source grid is None, model is loaded from this file
       source_grid (grid.Grid object, optional): if None, the epc_file is loaded and it should contain one ijk grid object
          (or one 'ROOT' grid) which is used as the source grid
       surface (surface.Surface object, optional): the surface to drape the grid to; if None, a surface is generated from
          the reference layer boundary (which can then be scaled with the scaling_factor)
       scaling_factor (float, optional): if not None, prior to draping, the surface is stretched vertically by this factor,
          away from a horizontal plane located at the surface's shallowest depth
       ref_k0 (integer, default 0): the reference layer (zero based) to drape to the surface
       ref_k_faces (string, default 'top'): 'top' or 'base' identifying which bounding interface to use as the reference
       quad_triangles (boolean, default True): if True and surface is None, each cell face in the reference boundary layer
          is represented by 4 triangles (with a common vertex at the face centre) in the generated surface; if False,
          only 2 trianges are used for each cell face (which gives a non-unique solution)
       cell_range (integer, default 0): the number of cells away from faults which will have depths adjusted to spatially
          smooth the effect of the throw scaling (ie. reduce sudden changes in gradient due to the scaling)
       offset_decay (float, default 0.5): the factor to reduce depth shifts by with each cell step away from faults (used
          in conjunction with cell_range)
       store_displacement (boolean, default False): if True, 3 grid property parts are created, one each for x, y, & z
          displacement of cells' centres brought about by the local depth shift
       inherit_properties (boolean, default False): if True, the new grid will have a copy of any properties associated
          with the source grid
       inherit_realization (int, optional): realization number for which properties will be inherited; ignored if
          inherit_properties is False
       inherit_all_realizations (boolean, default False): if True (and inherit_realization is None), properties for all
          realizations will be inherited; if False, only properties with a realization of None are inherited; ignored if
          inherit_properties is False or inherit_realization is not None
       new_grid_title (string): used as the citation title text for the new grid object
       new_epc_file (string, optional): if None, the source epc_file is extended with the new grid object; if present,
          a new epc file (& associated h5 file) is created to contain the draped grid (& crs)

    returns:
       new grid (grid.Grid object), with geometry draped to surface

    notes:
       at least one of a surface or a scaling factor must be given;
       if no surface is given, one is created from the fault-healed grid points for the reference layer interface;
       if a scaling factor other than 1.0 is given, the surface is flexed vertically, relative to its shallowest point;
       layer thicknesses measured along pillars are maintained; cell volumes may change;
       the coordinate reference systems for the surface and the grid are assumed to be the same;
       this function currently uses an exhaustive, computationally and memory intensive algorithm;
       setting quad_triangles argument to False should give a factor of 2 speed up and reduction in memory requirement;
       the epc file and associated hdf5 file are appended to (extended) with the new grid, as a side effect of this function
    """

    log.info('draping grid to surface')

    assert epc_file or new_epc_file, 'epc file name not specified'
    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
    model, source_grid = _establish_model_and_source_grid(
        epc_file, source_grid)
    assert source_grid.grid_representation == 'IjkGrid'
    assert model is not None

    assert ref_k0 >= 0 and ref_k0 < source_grid.nk
    assert ref_k_faces in ['top', 'base']
    assert surface is not None or (scaling_factor is not None
                                   and scaling_factor != 1.0)

    if surface is None:
        surface = rgs.generate_untorn_surface_for_layer_interface(
            source_grid,
            k0=ref_k0,
            ref_k_faces=ref_k_faces,
            quad_triangles=quad_triangles,
            border=border)

    if scaling_factor is not None and scaling_factor != 1.0:
        scaled_surf = copy.deepcopy(surface)
        scaled_surf.vertical_rescale_points(scaling_factor=scaling_factor)
        surface = scaled_surf

    # check that surface and grid use same crs; if not, convert to same
    surface_crs = rqc.Crs(surface.model, surface.crs_uuid)
    if source_grid.crs is None:
        source_grid.crs = rqc.Crs(source_grid.model, uuid=source_grid.crs_uuid)
    if surface_crs != source_grid.crs:
        surface.triangles_and_points()
        surface_crs.convert_array_to(source_grid.crs, surface.points)

    # take a copy of the grid
    log.debug('copying grid')
    grid = copy_grid(source_grid, model)
    grid.cache_all_geometry_arrays()  # probably already cached anyway

    # todo: handle pillars with no geometry defined, and cells without geometry defined
    assert grid.geometry_defined_for_all_pillars(
    ), 'not all pillars have defined geometry'
    assert grid.geometry_defined_for_all_cells(
    ), 'not all cells have defined geometry'

    # fetch unsplit equivalent of grid points
    log.debug('fetching unsplit equivalent grid points')
    unsplit_points = grid.unsplit_points_ref(cache_array=True)

    # assume pillars to be straight lines based on top and base points
    log.debug('setting up pillar sample points and directional vectors')
    line_p = unsplit_points[0, :, :, :].reshape((-1, 3))
    line_v = unsplit_points[-1, :, :, :].reshape((-1, 3)) - line_p
    if ref_k_faces == 'base':
        ref_k0 += 1

    # access triangulated surface as triangle node indices into array of points
    log.debug('fetching surface points and triangle corner indices')
    t, p = surface.triangles_and_points(
    )  # will pick up cached crs converted copy if appropriate

    # compute intersections of all pillars with all triangles (sparse array returned with NaN for no intersection)
    log.debug('computing intersections of all pillars with all triangles')
    intersects = meet.line_set_triangles_intersects(line_p, line_v, p[t])

    # reduce to a single intersection point per pillar; todo: flag multiple intersections with a warning
    log.debug(
        'selecting last intersection for each pillar (there should be only one intersection anyway)'
    )
    picks = meet.last_intersects(intersects)

    # count the number of pillars with no intersection at surface (indicated by triple nan)
    log.debug(
        'counting number of pillars which fail to intersect with surface')
    failures = np.count_nonzero(np.isnan(picks)) // 3
    log.info('number of pillars which do not intersect with surface: ' +
             str(failures))
    assert failures == 0, 'cannot proceed as some pillars do not intersect with surface'

    # compute a translation vector per pillar
    log.debug('computing translation vectors for pillars')
    translate = picks - unsplit_points[ref_k0, :, :, :].reshape((-1, 3))

    # shift all points by translation vectors
    _shift_by_translation_vectors(grid, translate)

    # check cell edge relative directions (in x,y) to ensure geometry is still coherent
    log.debug('checking grid geometry coherence')
    grid.check_top_and_base_cell_edge_directions()

    # build cell displacement property array(s)
    if store_displacement:
        log.debug('generating cell displacement property arrays')
        displacement_collection = _displacement_properties(grid, source_grid)
    else:
        displacement_collection = None

    collection = _prepare_simple_inheritance(grid, source_grid,
                                             inherit_properties,
                                             inherit_realization,
                                             inherit_all_realizations)
    if collection is None:
        collection = displacement_collection
    elif displacement_collection is not None:
        collection.inherit_imported_list_from_other_collection(
            displacement_collection, copy_cached_arrays=False)

    if new_grid_title is None or len(new_grid_title) == 0:
        new_grid_title = 'grid flexed from ' + str(
            rqet.citation_title_for_node(source_grid.root))

    # write model
    model.h5_release()
    if new_epc_file:
        _write_grid(new_epc_file,
                    grid,
                    property_collection=collection,
                    grid_title=new_grid_title,
                    mode='w')
    else:
        ext_uuid, _ = model.h5_uuid_and_path_for_node(
            rqet.find_nested_tags(source_grid.root, ['Geometry', 'Points']),
            'Coordinates')
        _write_grid(epc_file,
                    grid,
                    ext_uuid=ext_uuid,
                    property_collection=collection,
                    grid_title=new_grid_title,
                    mode='a')

    return grid
Ejemplo n.º 7
0
def tilted_grid(epc_file,
                source_grid = None,
                pivot_xyz = None,
                azimuth = None,
                dip = None,
                store_displacement = False,
                inherit_properties = False,
                inherit_realization = None,
                inherit_all_realizations = False,
                new_grid_title = None,
                new_epc_file = None):
    """Extends epc file with a new grid which is a version of the source grid tilted.

    arguments:
       epc_file (string): file name to rewrite the model's xml to; if source grid is None, model is loaded from this file
       source_grid (grid.Grid object, optional): if None, the epc_file is loaded and it should contain one ijk grid object
          (or one 'ROOT' grid) which is used as the source grid
       pivot_xyz (triple float): a point in 3D space on the pivot axis, which is horizontal and orthogonal to azimuth
       azimuth: the direction of tilt (orthogonal to tilt axis), as a compass bearing in degrees
       dip: the angle to tilt the grid by, in degrees; a positive value tilts points in direction azimuth downwards (needs checking!)
       store_displacement (boolean, default False): if True, 3 grid property parts are created, one each for x, y, & z
          displacement of cells' centres brought about by the tilting
       inherit_properties (boolean, default False): if True, the new grid will have a copy of any properties associated
          with the source grid
       inherit_realization (int, optional): realization number for which properties will be inherited; ignored if
          inherit_properties is False
       inherit_all_realizations (boolean, default False): if True (and inherit_realization is None), properties for all
          realizations will be inherited; if False, only properties with a realization of None are inherited; ignored if
          inherit_properties is False or inherit_realization is not None
       new_grid_title (string): used as the citation title text for the new grid object
       new_epc_file (string, optional): if None, the source epc_file is extended with the new grid object; if present,
          a new epc file (& associated h5 file) is created to contain the tilted grid (& crs)

    returns:
       a new grid (grid.Grid object) which is a copy of the source grid tilted in 3D space
    """

    assert epc_file or new_epc_file, 'epc file name not specified'
    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
    model, source_grid = _establish_model_and_source_grid(epc_file, source_grid)
    assert source_grid.grid_representation == 'IjkGrid'
    assert model is not None

    # take a copy of the grid
    grid = copy_grid(source_grid, model)

    if grid.inactive is not None:
        log.debug('copied grid inactive shape: ' + str(grid.inactive.shape))

    # tilt the grid
    grid.cache_all_geometry_arrays()  # probably already cached anyway
    vec.tilt_points(pivot_xyz, azimuth, dip, grid.points_cached)

    # build cell displacement property array(s)
    if store_displacement:
        displacement_collection = _displacement_properties(grid, source_grid)
    else:
        displacement_collection = None

    collection = _prepare_simple_inheritance(grid, source_grid, inherit_properties, inherit_realization,
                                             inherit_all_realizations)
    if collection is None:
        collection = displacement_collection
    elif displacement_collection is not None:
        collection.inherit_imported_list_from_other_collection(displacement_collection, copy_cached_arrays = False)

    if new_grid_title is None or len(new_grid_title) == 0:
        new_grid_title = 'tilted version ({0:4.2f} degree dip) of '.format(abs(dip)) + str(
            rqet.citation_title_for_node(source_grid.root))

    # write model
    model.h5_release()
    if new_epc_file:
        _write_grid(new_epc_file, grid, property_collection = collection, grid_title = new_grid_title, mode = 'w')
    else:
        ext_uuid, _ = model.h5_uuid_and_path_for_node(rqet.find_nested_tags(source_grid.root, ['Geometry', 'Points']),
                                                      'Coordinates')
        _write_grid(epc_file,
                    grid,
                    ext_uuid = ext_uuid,
                    property_collection = collection,
                    grid_title = new_grid_title,
                    mode = 'a')

    return grid
Ejemplo n.º 8
0
def add_ab_properties(
    epc_file,  # existing resqml model
    grid_uuid=None,  # optional grid uuid, required if more than one grid in model; todo: handle list of grids?
    ext_uuid=None,  # if None, hdf5 file holding grid geometry will be used
    ab_property_list=None
):  # list of (file_name, keyword, property_kind, facet_type, facet, uom, time_index, null_value,
    #          discrete, realization)
    """Process a list of pure binary property array files.

    Adds as parts of model, related to grid (hdf5 file is appended to).
    """

    assert ab_property_list, 'property list is empty or missing'

    model = rq.Model(epc_file=epc_file)
    if grid_uuid is None:
        grid_node = model.root_for_ijk_grid(
        )  # will raise an exception if Model has more than 1 grid
        assert grid_node is not None, 'grid not found in model'
        grid_uuid = rqet.uuid_for_part_root(grid_node)
    grid = grr.any_grid(parent_model=model,
                        uuid=grid_uuid,
                        find_properties=False)

    if ext_uuid is None:
        ext_node = rqet.find_nested_tags(
            grid.geometry_root, ['Points', 'Coordinates', 'HdfProxy', 'UUID'])
        if ext_node is not None:
            ext_uuid = bu.uuid_from_string(ext_node.text.strip())

    #  ab_property_list: list of (filename, keyword, property_kind, facet_type, facet, uom, time_index, null_value, discrete, realization)
    prop_import_collection = rp.GridPropertyCollection()
    prop_import_collection.set_grid(grid)
    for (p_filename, p_keyword, p_property_kind, p_facet_type, p_facet, p_uom,
         p_time_index, p_null_value, p_discrete,
         p_realization) in ab_property_list:
        prop_import_collection.import_ab_property_to_cache(
            p_filename,
            p_keyword,
            grid.extent_kji,
            discrete=p_discrete,
            uom=p_uom,
            time_index=p_time_index,
            null_value=p_null_value,
            property_kind=p_property_kind,
            facet_type=p_facet_type,
            facet=p_facet,
            realization=p_realization)
        # todo: property_kind, facet_type & facet are not currently getting passed through the imported_list tuple in resqml_property

    if prop_import_collection is None:
        log.warning('no pure binary grid properties to import')
    else:
        log.info('number of pure binary grid property arrays: ' +
                 str(prop_import_collection.number_of_imports()))

    # append to hdf5 file using arrays cached in grid property collection above
    hdf5_file = model.h5_file_name()
    log.debug('appending to hdf5 file: ' + hdf5_file)
    grid.write_hdf5_from_caches(hdf5_file,
                                mode='a',
                                geometry=False,
                                imported_properties=prop_import_collection,
                                write_active=False)
    # remove cached static property arrays from memory
    if prop_import_collection is not None:
        prop_import_collection.remove_all_cached_arrays()

    # add imported properties parts to model, building property parts list
    if prop_import_collection is not None and prop_import_collection.imported_list is not None:
        prop_import_collection.create_xml_for_imported_list_and_add_parts_to_model(
            ext_uuid)

    # mark model as modified
    model.set_modified()

    # store new version of model
    log.info('storing model with additional properties in epc file: ' +
             epc_file)
    model.store_epc(epc_file)

    return model
Ejemplo n.º 9
0
def local_depth_adjustment(epc_file,
                           source_grid,
                           centre_x,
                           centre_y,
                           radius,
                           centre_shift,
                           use_local_coords,
                           decay_shape = 'quadratic',
                           ref_k0 = 0,
                           store_displacement = False,
                           inherit_properties = False,
                           inherit_realization = None,
                           inherit_all_realizations = False,
                           new_grid_title = None,
                           new_epc_file = None):
    """Applies a local depth adjustment to the grid, adding as a new grid part in the model.

    arguments:
       epc_file (string): file name to rewrite the model's xml to; if source grid is None, model is loaded from this file
       source_grid (grid.Grid object, optional): a multi-layer RESQML grid object; if None, the epc_file is loaded
          and it should contain one ijk grid object (or one 'ROOT' grid) which is used as the source grid
       centre_x, centre_y (floats): the centre of the depth adjustment, corresponding to the location of maximum change
          in depth; crs is implicitly that of the grid but see also use_local_coords argument
       radius (float): the radius of adjustment of depths; units are implicitly xy (projected) units of grid crs
       centre_shift (float): the maximum vertical depth adjustment; units are implicily z (vertical) units of grid crs;
          use positive value to increase depth, negative to make shallower
       use_local_coords (boolean): if True, centre_x & centre_y are taken to be in the local coordinates of the grid's
          crs; otherwise the global coordinates
       decay_shape (string): 'linear' yields a cone shaped change in depth values; 'quadratic' (the default) yields a
          bell shaped change
       ref_k0 (integer, default 0): the layer in the grid to use as reference for determining the distance of a pillar
          from the centre of the depth adjustment; the corners of the top face of the reference layer are used
       store_displacement (boolean, default False): if True, 3 grid property parts are created, one each for x, y, & z
          displacement of cells' centres brought about by the local depth shift
       inherit_properties (boolean, default False): if True, the new grid will have a copy of any properties associated
          with the source grid
       inherit_realization (int, optional): realization number for which properties will be inherited; ignored if
          inherit_properties is False
       inherit_all_realizations (boolean, default False): if True (and inherit_realization is None), properties for all
          realizations will be inherited; if False, only properties with a realization of None are inherited; ignored if
          inherit_properties is False or inherit_realization is not None
       new_grid_title (string): used as the citation title text for the new grid object
       new_epc_file (string, optional): if None, the source epc_file is extended with the new grid object; if present,
          a new epc file (& associated h5 file) is created to contain the adjusted grid (& crs)

    returns:
       new grid object which is a copy of the source grid with the local depth adjustment applied
    """

    log.info('adjusting depth')
    log.debug('centre x: {0:3.1f}; y: {1:3.1f}'.format(centre_x, centre_y))
    if use_local_coords:
        log.debug('centre x & y interpreted in local crs')
    log.debug('radius of influence: {0:3.1f}'.format(radius))
    log.debug('depth shift at centre: {0:5.3f}'.format(centre_shift))
    log.debug('decay shape: ' + decay_shape)
    log.debug('reference layer (k0 protocol): ' + str(ref_k0))

    assert epc_file or new_epc_file, 'epc file name not specified'
    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
    model, source_grid = _establish_model_and_source_grid(epc_file, source_grid)
    assert source_grid.grid_representation == 'IjkGrid'
    assert model is not None

    # take a copy of the grid
    grid = copy_grid(source_grid, model, copy_crs = True)

    # if not use_local_coords, convert centre_x & y into local_coords
    if grid.crs is None:
        grid.crs = rqc.Crs(model, uuid = grid.crs_uuid)
    if not use_local_coords:
        rotation = grid.crs.rotation
        if rotation > 0.001:
            log.error('unable to account for rotation in crs: use local coordinates')
            return
        centre_x -= grid.crs.x_offset
        centre_y -= grid.crs.y_offset
    z_inc_down = grid.crs.z_inc_down

    if not z_inc_down:
        centre_shift = -centre_shift

    # cache geometry in memory; needed prior to writing new coherent set of arrays to hdf5
    grid.cache_all_geometry_arrays()
    if grid.has_split_coordinate_lines:
        reshaped_points = grid.points_cached.copy()
    else:
        nkp1, njp1, nip1, xyz = grid.points_cached.shape
        reshaped_points = grid.points_cached.copy().reshape((nkp1, njp1 * nip1, xyz))
    assert reshaped_points.ndim == 3 and reshaped_points.shape[2] == 3
    assert ref_k0 >= 0 and ref_k0 < reshaped_points.shape[0]

    log.debug('reshaped_points.shape: ' + str(reshaped_points.shape))

    log.debug('min z before depth adjustment: ' + str(np.nanmin(reshaped_points[:, :, 2])))

    # for each pillar, find x, y for k = reference_layer_k0
    pillars_adjusted = 0

    # todo: replace with numpy array operations
    radius_sqr = radius * radius
    for pillar in range(reshaped_points.shape[1]):
        x, y, z = tuple(reshaped_points[ref_k0, pillar, :])
        # find distance of this pillar from the centre
        dx = centre_x - x
        dy = centre_y - y
        distance_sqr = (dx * dx) + (dy * dy)
        # if this pillar is beyond radius of influence, no action needed
        if distance_sqr > radius_sqr:
            continue
        distance = maths.sqrt(distance_sqr)
        # compute decayed shift as function of distance
        shift = _decayed_shift(centre_shift, distance, radius, decay_shape)
        # adjust depth values for pillar in cached array
        log.debug('adjusting pillar number {0} at x: {1:3.1f}, y: {2:3.1f}, distance: {3:3.1f} by {4:5.3f}'.format(
            pillar, x, y, distance, shift))
        reshaped_points[:, pillar, 2] += shift
        pillars_adjusted += 1

    # if no pillars adjusted: warn and return
    if pillars_adjusted == 0:
        log.warning('no pillars adjusted')
        return

    log.debug('min z after depth adjustment: ' + str(np.nanmin(reshaped_points[:, :, 2])))
    if grid.has_split_coordinate_lines:
        grid.points_cached[:] = reshaped_points
    else:
        grid.points_cached[:] = reshaped_points.reshape((nkp1, njp1, nip1, xyz))


#   model.copy_part(old_uuid, grid.uuid, change_hdf5_refs = True)   # copies the xml, substituting the new uuid in the root node (and in hdf5 refs)
    log.info(str(pillars_adjusted) + ' pillars adjusted')

    # build cell displacement property array(s)
    if store_displacement:
        log.debug('generating cell displacement property arrays')
        displacement_collection = _displacement_properties(grid, source_grid)
    else:
        displacement_collection = None

    collection = _prepare_simple_inheritance(grid, source_grid, inherit_properties, inherit_realization,
                                             inherit_all_realizations)
    if collection is None:
        collection = displacement_collection
    elif displacement_collection is not None:
        collection.inherit_imported_list_from_other_collection(displacement_collection, copy_cached_arrays = False)

    if new_grid_title is None or len(new_grid_title) == 0:
        new_grid_title = 'grid derived from {0} with local depth shift of {1:3.1f} applied'.format(
            str(rqet.citation_title_for_node(source_grid.root)), centre_shift)

    # write model
    model.h5_release()
    if new_epc_file:
        _write_grid(new_epc_file, grid, property_collection = collection, grid_title = new_grid_title, mode = 'w')
    else:
        ext_uuid, _ = model.h5_uuid_and_path_for_node(rqet.find_nested_tags(source_grid.root, ['Geometry', 'Points']),
                                                      'Coordinates')
        _write_grid(epc_file,
                    grid,
                    ext_uuid = ext_uuid,
                    property_collection = collection,
                    grid_title = new_grid_title,
                    mode = 'a')

    return grid
Ejemplo n.º 10
0
def unsplit_grid(epc_file,
                 source_grid=None,
                 inherit_properties=False,
                 inherit_realization=None,
                 inherit_all_realizations=False,
                 new_grid_title=None,
                 new_epc_file=None):
    """Extends epc file with a new grid which is a version of the source grid with all faults healed.

    arguments:
       epc_file (string): file name to rewrite the model's xml to; if source grid is None, model is loaded from this file
       source_grid (grid.Grid object, optional): if None, the epc_file is loaded and it should contain one ijk grid object
          (or one 'ROOT' grid) which is used as the source grid
       inherit_properties (boolean, default False): if True, the new grid will have a copy of any properties associated
          with the source grid
       inherit_realization (int, optional): realization number for which properties will be inherited; ignored if
          inherit_properties is False
       inherit_all_realizations (boolean, default False): if True (and inherit_realization is None), properties for all
          realizations will be inherited; if False, only properties with a realization of None are inherited; ignored if
          inherit_properties is False or inherit_realization is not None
       new_grid_title (string): used as the citation title text for the new grid object
       new_epc_file (string, optional): if None, the source epc_file is extended with the new grid object; if present,
          a new epc file (& associated h5 file) is created to contain the unsplit grid (& crs)

    returns:
       a new grid (grid.Grid object) which is an unfaulted copy of the source grid

    notes:
       the faults are healed by shifting the thrown sides up and down to the midpoint, only along the line of the fault;
       to smooth the adjustments away from the line of the fault, use the global_fault_throw_scaling() function first
    """

    assert epc_file or new_epc_file, 'epc file name not specified'
    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
    model, source_grid = _establish_model_and_source_grid(
        epc_file, source_grid)
    assert source_grid.grid_representation == 'IjkGrid'
    assert model is not None

    assert source_grid.has_split_coordinate_lines, 'source grid is unfaulted'

    # take a copy of the grid
    grid = copy_grid(source_grid, model)

    if grid.inactive is not None:
        log.debug('copied grid inactive shape: ' + str(grid.inactive.shape))

    # heal faults in the grid
    grid.cache_all_geometry_arrays()  # probably already cached anyway
    unsplit = source_grid.unsplit_points_ref()
    grid.points_cached = unsplit.copy()
    assert grid.points_cached.shape == (
        grid.nk + 1, grid.nj + 1, grid.ni + 1,
        3), 'unsplit points have incorrect shape'

    grid.has_split_coordinate_lines = False
    delattr(grid, 'split_pillar_indices_cached')
    delattr(grid, 'cols_for_split_pillars')
    delattr(grid, 'cols_for_split_pillars_cl')
    if hasattr(grid, 'pillars_for_column'):
        delattr(grid, 'pillars_for_column')

    collection = _prepare_simple_inheritance(grid, source_grid,
                                             inherit_properties,
                                             inherit_realization,
                                             inherit_all_realizations)
    # todo: recompute depth properties (and volumes, cell lengths etc. if being strict)

    if new_grid_title is None or len(new_grid_title) == 0:
        new_grid_title = 'unfaulted version of ' + str(
            rqet.citation_title_for_node(source_grid.root))

    # write model
    if new_epc_file:
        _write_grid(new_epc_file,
                    grid,
                    property_collection=collection,
                    grid_title=new_grid_title,
                    mode='w')
    else:
        ext_uuid, _ = model.h5_uuid_and_path_for_node(
            rqet.find_nested_tags(source_grid.root, ['Geometry', 'Points']),
            'Coordinates')
        _write_grid(epc_file,
                    grid,
                    ext_uuid=ext_uuid,
                    property_collection=collection,
                    grid_title=new_grid_title,
                    mode='a')

    return grid
Ejemplo n.º 11
0
def extract_box(epc_file=None,
                source_grid=None,
                box=None,
                box_inactive=None,
                inherit_properties=False,
                inherit_realization=None,
                inherit_all_realizations=False,
                set_parent_window=None,
                new_grid_title=None,
                new_epc_file=None):
    """Extends an existing model with a new grid extracted as a logical IJK box from the source grid.

    arguments:
       epc_file (string): file name to rewrite the model's xml to; if source grid is None, model is loaded from this file
       source_grid (grid.Grid object, optional): if None, the epc_file is loaded and it should contain one ijk grid object
          (or one 'ROOT' grid) which is used as the source grid
       box (numpy int array of shape (2, 3)): the minimum and maximum kji0 indices in the source grid (zero based) to include
          in the extracted grid; note that cells with index equal to maximum value are included (unlike with python ranges)
       box_inactive (numpy bool array, optional): if present, shape must match box and values will be or'ed in with the
          inactive mask inherited from the source grid; if None, inactive mask will be as inherited from source grid
       inherit_properties (boolean, default False): if True, the new grid will have a copy of any properties associated
          with the source grid, with values taken from the specified box
       inherit_realization (int, optional): realization number for which properties will be inherited; ignored if
          inherit_properties is False
       inherit_all_realizations (boolean, default False): if True (and inherit_realization is None), properties for all
          realizations will be inherited; if False, only properties with a realization of None are inherited; ignored if
          inherit_properties is False or inherit_realization is not None
       set_parent_window (boolean, optional): if True, the extracted grid has its parent window attribute set; if False,
          the parent window is not set; if None, the default will be True if new_epc_file is None or False otherwise
       new_grid_title (string): used as the citation title text for the new grid object
       new_epc_file (string, optional): if None, the source epc_file is extended with the new grid object; if present,
          a new epc file (& associated h5 file) is created to contain the extracted grid (& crs)

    returns:
       new grid object with extent as implied by the box argument

    note:
       the epc file and associated hdf5 file are appended to (extended) with the new grid, unless a new_epc_file is specified,
       in which case the grid and inherited properties are written there instead
    """

    log.debug('extracting grid for box')

    assert epc_file or new_epc_file, 'epc file name not specified'
    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
    if set_parent_window is None:
        set_parent_window = (new_epc_file is None)
    model, source_grid = _establish_model_and_source_grid(
        epc_file, source_grid)
    assert source_grid.grid_representation in ['IjkGrid', 'IjkBlockGrid']
    assert model is not None
    assert box is not None and box.shape == (2, 3)
    assert np.all(box[1, :] >= box[0, :]) and np.all(
        box[0, :] >= 0) and np.all(box[1, :] < source_grid.extent_kji)

    if source_grid.grid_representation == 'IjkBlockGrid':
        source_grid.make_regular_points_cached()

    box_str = bx.string_iijjkk1_for_box_kji0(box)

    # create a new, empty grid object
    grid = grr.Grid(model)

    # inherit attributes from source grid
    grid.grid_representation = 'IjkGrid'
    grid.extent_kji = box[1, :] - box[0, :] + 1
    if box_inactive is not None:
        assert box_inactive.shape == tuple(grid.extent_kji)
    grid.nk, grid.nj, grid.ni = grid.extent_kji[0], grid.extent_kji[
        1], grid.extent_kji[2]
    grid.k_direction_is_down = source_grid.k_direction_is_down
    grid.grid_is_right_handed = source_grid.grid_is_right_handed
    grid.pillar_shape = source_grid.pillar_shape
    grid.has_split_coordinate_lines = source_grid.has_split_coordinate_lines
    # inherit the coordinate reference system used by the grid geometry
    grid.crs_uuid = source_grid.crs_uuid
    if source_grid.model is not model:
        model.duplicate_node(source_grid.model.root_for_uuid(grid.crs_uuid),
                             add_as_part=True)
    grid.crs = rqc.Crs(model, uuid=grid.crs_uuid)

    # inherit k_gaps for selected layer range
    _inherit_k_gaps(source_grid, grid, box)

    # extract inactive cell mask
    _extract_inactive_cell_mask(source_grid, grid, box_inactive, box)

    # extract the grid geometry
    source_grid.cache_all_geometry_arrays()

    # determine cell geometry is defined
    if hasattr(source_grid, 'array_cell_geometry_is_defined'):
        grid.array_cell_geometry_is_defined = _array_box(
            source_grid.array_cell_geometry_is_defined, box)
        grid.geometry_defined_for_all_cells_cached = np.all(
            grid.array_cell_geometry_is_defined)
    else:
        grid.geometry_defined_for_all_cells_cached = source_grid.geometry_defined_for_all_cells_cached

    # copy info for pillar geometry is defined
    grid.geometry_defined_for_all_pillars_cached = source_grid.geometry_defined_for_all_pillars_cached
    if hasattr(source_grid, 'array_pillar_geometry_is_defined'):
        grid.array_pillar_geometry_is_defined = _array_box(
            source_grid.array_pillar_geometry_is_defined, box)
        grid.geometry_defined_for_all_pillars_cached = np.all(
            grid.array_pillar_geometry_is_defined)

    # get reference to points for source grid geometry
    source_points = source_grid.points_ref()

    pillar_box = box.copy()
    if source_grid.k_gaps:
        pillar_box[:, 0] = source_grid.k_raw_index_array[pillar_box[:, 0]]
    pillar_box[
        1, :] += 1  # pillar points have extent one greater than cells, in each axis

    if not source_grid.has_split_coordinate_lines:
        log.debug('no split pillars in source grid')
        grid.points_cached = _array_box(
            source_points, pillar_box)  # should work, ie. preserve xyz axis
    else:
        _process_split_pillars(source_grid, grid, box, pillar_box)

    if set_parent_window:
        fine_coarse = fc.FineCoarse(grid.extent_kji,
                                    grid.extent_kji,
                                    within_coarse_box=box)
        fine_coarse.set_all_ratios_constant()
        grid.set_parent(source_grid.uuid, True, fine_coarse)

    collection = _inherit_collection(source_grid, grid, inherit_properties,
                                     box, inherit_realization,
                                     inherit_all_realizations)

    if new_grid_title is None or len(new_grid_title) == 0:
        new_grid_title = 'local grid ' + box_str + ' extracted from ' + str(
            rqet.citation_title_for_node(source_grid.root))

    model.h5_release()
    if new_epc_file:
        _write_grid(new_epc_file,
                    grid,
                    property_collection=collection,
                    grid_title=new_grid_title,
                    mode='w')
    else:
        ext_uuid, _ = model.h5_uuid_and_path_for_node(
            rqet.find_nested_tags(source_grid.root, ['Geometry', 'Points']),
            'Coordinates')
        _write_grid(epc_file,
                    grid,
                    ext_uuid=ext_uuid,
                    property_collection=collection,
                    grid_title=new_grid_title,
                    mode='a')

    return grid
Ejemplo n.º 12
0
def interpolated_grid(epc_file,
                      grid_a,
                      grid_b,
                      a_to_b_0_to_1=0.5,
                      split_tolerance=0.01,
                      inherit_properties=False,
                      inherit_realization=None,
                      inherit_all_realizations=False,
                      new_grid_title=None,
                      new_epc_file=None):
    """Extends an existing model with a new grid geometry linearly interpolated between the two source_grids.

    arguments:
       epc_file (string): file name to rewrite the model's xml to
       grid_a, grid_b (grid.Grid objects): a pair of RESQML grid objects representing the end cases, between
          which the new grid will be interpolated
       a_to_b_0_to_1 (float, default 0.5): the interpolation factor in the range zero to one; a value of 0.0 will yield
          a copy of grid a, a value of 1.0 will yield a copy of grid b, intermediate values will yield a grid with all
          points interpolated
       split_tolerance (float, default 0.01): maximum offset of corner points for shared point to be generated; units
          are same as those in grid crs; only relevant if working from corner points, ignored otherwise
       inherit_properties (boolean, default False): if True, the new grid will have a copy of any properties associated
          with grid_a
       inherit_realization (int, optional): realization number for which properties will be inherited; ignored if
          inherit_properties is False
       inherit_all_realizations (boolean, default False): if True (and inherit_realization is None), properties for all
          realizations will be inherited; if False, only properties with a realization of None are inherited; ignored if
          inherit_properties is False or inherit_realization is not None
       new_grid_title (string): used as the citation title text for the new grid object
       new_epc_file (string, optional): if None, the source epc_file is extended with the new grid object; if present,
          a new epc file (& associated h5 file) is created to contain the interpolated grid (& crs)

    returns:
       new grid object (grid.Grid) with geometry interpolated between grid a and grid b

    notes:
       the hdf5 file used by the grid_a model is appended to, so it is recommended that the grid_a model's epc is specified
       as the first argument (unless a new epc file is required, sharing the hdf5 file)
    """

    assert epc_file or new_epc_file, 'epc file name not specified'
    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
    assert grid_a is not None and grid_b is not None, 'at least one source grid is missing'
    assert grid_a.grid_representation == 'IjkGrid' and grid_b.grid_representation == 'IjkGrid'
    assert 0.0 <= a_to_b_0_to_1 <= 1.0, 'interpolation factor outside range 0.0 to 1.0'
    assert tuple(grid_a.extent_kji) == tuple(
        grid_b.extent_kji), 'source grids have different extents'
    assert grid_a.k_direction_is_down == grid_b.k_direction_is_down, 'source grids have different k directions'
    assert grid_a.grid_is_right_handed == grid_b.grid_is_right_handed, 'source grids have different ijk handedness'
    assert grid_a.pillar_shape == grid_b.pillar_shape, 'source grids have different resqml pillar shapes'

    b_weight = a_to_b_0_to_1
    a_weight = 1.0 - b_weight

    model = grid_a.model

    if not bu.matching_uuids(grid_a.crs_uuid, grid_b.crs_uuid):
        crs_a = rqc.Crs(grid_a.model, uuid=grid_a.crs_uuid)
        crs_b = rqc.Crs(grid_b.model, uuid=grid_b.crs_uuid)
        assert crs_a.is_equivalent(crs_b),  \
            'end point grids for interpolation have different coordinate reference systems'

    log.info('loading geometry for two source grids')
    grid_a.cache_all_geometry_arrays()
    grid_b.cache_all_geometry_arrays()

    assert (grid_a.geometry_defined_for_all_cells()
            and grid_b.geometry_defined_for_all_cells()
            ), 'geometry not defined for all cells'
    # assert grid_a.geometry_defined_for_all_pillars() and grid_b.geometry_defined_for_all_pillars(),  \
    #     'geometry not defined for all pillars'

    work_from_pillars = _determine_work_from_pillars(grid_a, grid_b)

    # create a new, empty grid object
    grid = grr.Grid(model)

    # inherit attributes from source grid
    _inherit_basics(grid, grid_a, grid_b)

    if work_from_pillars:
        _interpolate_points_cached_from_pillars(grid, grid_a, grid_b, a_weight,
                                                b_weight)
    else:
        _interpolate_points_cached_from_cp(grid, grid_a, grid_b, a_weight,
                                           b_weight, split_tolerance)

    collection = _prepare_simple_inheritance(grid, grid_a, inherit_properties,
                                             inherit_realization,
                                             inherit_all_realizations)

    if new_grid_title is None or len(new_grid_title) == 0:
        new_grid_title = 'interpolated between two grids with factor: ' + str(
            a_to_b_0_to_1)

    model.h5_release()
    if new_epc_file:
        _write_grid(new_epc_file,
                    grid,
                    property_collection=collection,
                    grid_title=new_grid_title,
                    mode='w')
    else:
        ext_uuid, _ = model.h5_uuid_and_path_for_node(
            rqet.find_nested_tags(grid_a.root, ['Geometry', 'Points']),
            'Coordinates')
        _write_grid(epc_file,
                    grid,
                    ext_uuid=ext_uuid,
                    property_collection=collection,
                    grid_title=new_grid_title,
                    mode='a')

    return grid
Ejemplo n.º 13
0
def coarsened_grid(epc_file,
                   source_grid,
                   fine_coarse,
                   inherit_properties=False,
                   inherit_realization=None,
                   inherit_all_realizations=False,
                   set_parent_window=None,
                   infill_missing_geometry=True,
                   new_grid_title=None,
                   new_epc_file=None):
    """Generates a coarsened version of an unsplit source grid, todo: optionally inheriting properties.

    arguments:
       epc_file (string): file name to rewrite the model's xml to; if source grid is None, model is loaded from this file
       source_grid (grid.Grid object, optional): if None, the epc_file is loaded and it should contain one ijk grid object
          (or one 'ROOT' grid) which is used as the source grid
       fine_coarse (resqpy.olio.fine_coarse.FineCoarse object): the mapping between cells in the fine (source) and
          coarse (output) grids
       inherit_properties (boolean, default False): if True, the new grid will have a copy of any properties associated
          with the source grid, with values upscaled or sampled
       inherit_realization (int, optional): realization number for which properties will be inherited; ignored if
          inherit_properties is False
       inherit_all_realizations (boolean, default False): if True (and inherit_realization is None), properties for all
          realizations will be inherited; if False, only properties with a realization of None are inherited; ignored if
          inherit_properties is False or inherit_realization is not None
       set_parent_window (boolean or str, optional): if True or 'parent', the coarsened grid has its parent window attribute
          set; if False, the parent window is not set; if None, the default will be True if new_epc_file is None or False
          otherwise; if 'grandparent' then an intervening parent window with no refinement or coarsening will be skipped
          and its box used in the parent window for the new grid, relating directly to the original grid
       infill_missing_geometry (boolean, default True): if True, an attempt is made to generate grid geometry in the
          source grid wherever it is undefined; if False, any undefined geometry will result in an assertion failure
       new_grid_title (string): used as the citation title text for the new grid object
       new_epc_file (string, optional): if None, the source epc_file is extended with the new grid object; if present,
          a new epc file (& associated h5 file) is created to contain the refined grid (& crs)

    returns:
       new grid object being the coarsened grid; the epc and hdf5 files are written to as an intentional side effect

    note:
       this function coarsens an entire grid; to coarsen a local area of a grid, first use the extract_box function
       and then use this function on the extracted grid; in such a case, using a value of 'grandparent' for the
       set_parent_window argument will relate the coarsened grid back to the original
    """

    new_epc_file, model, source_grid = _establish_files_and_model(
        epc_file, new_epc_file, source_grid)

    if set_parent_window is None:
        set_parent_window = (new_epc_file is None)
    assert fine_coarse is not None and isinstance(fine_coarse, fc.FineCoarse)

    assert not source_grid.has_split_coordinate_lines, 'coarsening only available for unsplit grids: use other functions to heal faults first'

    if infill_missing_geometry and (
            not source_grid.geometry_defined_for_all_cells()
            or not source_grid.geometry_defined_for_all_pillars()):
        log.debug('attempting infill of geometry missing in source grid')
        source_grid.set_geometry_is_defined(treat_as_nan=None,
                                            treat_dots_as_nan=True,
                                            complete_partial_pillars=True,
                                            nullify_partial_pillars=False,
                                            complete_all=True)

    assert source_grid.geometry_defined_for_all_pillars(
    ), 'coarsening requires geometry to be defined for all pillars'
    assert source_grid.geometry_defined_for_all_cells(
    ), 'coarsening requires geometry to be defined for all cells'
    assert not source_grid.k_gaps, 'coarsening of grids with k gaps not currently supported'

    assert tuple(fine_coarse.fine_extent_kji) == tuple(source_grid.extent_kji),  \
           'fine_coarse mapping fine extent does not match that of source grid'
    fine_coarse.assert_valid()

    source_grid.cache_all_geometry_arrays()
    source_points = source_grid.points_ref().reshape(
        (source_grid.nk + 1), (source_grid.nj + 1) * (source_grid.ni + 1), 3)

    # create a new, empty grid object
    grid = grr.Grid(model)

    # inherit attributes from source grid
    grid.grid_representation = 'IjkGrid'
    grid.extent_kji = fine_coarse.coarse_extent_kji
    grid.nk, grid.nj, grid.ni = grid.extent_kji[0], grid.extent_kji[
        1], grid.extent_kji[2]
    grid.k_direction_is_down = source_grid.k_direction_is_down
    grid.grid_is_right_handed = source_grid.grid_is_right_handed
    grid.pillar_shape = source_grid.pillar_shape
    grid.has_split_coordinate_lines = False
    grid.split_pillars_count = None
    # inherit the coordinate reference system used by the grid geometry
    grid.crs_uuid = source_grid.crs_uuid
    if source_grid.model is not model:
        model.duplicate_node(source_grid.model.root_for_uuid(grid.crs_uuid),
                             add_as_part=True)
    grid.crs = rqc.Crs(model, grid.crs_uuid)

    coarsened_points = np.empty(
        (grid.nk + 1, (grid.nj + 1) * (grid.ni + 1),
         3))  # note: gets reshaped after being populated

    k_ratio_constant = fine_coarse.constant_ratios[0]
    if k_ratio_constant:
        k_indices = None
    else:
        k_indices = np.empty(grid.nk + 1, dtype=int)
        k_indices[0] = 0
        for k in range(grid.nk):
            k_indices[k + 1] = k_indices[k] + fine_coarse.vector_ratios[0][k]
        assert k_indices[-1] == source_grid.nk

    for cjp in range(grid.nj + 1):
        for cji in range(grid.ni + 1):
            natural_coarse_pillar = cjp * (grid.ni + 1) + cji
            natural_fine_pillar = fine_coarse.fine_for_coarse_natural_pillar_index(
                natural_coarse_pillar)
            if k_ratio_constant:
                coarsened_points[:, natural_coarse_pillar, :] = source_points[
                    0:source_grid.nk + 1:k_ratio_constant,
                    natural_fine_pillar, :]
            else:
                coarsened_points[:, natural_coarse_pillar, :] = source_points[
                    k_indices, natural_fine_pillar, :]

    grid.points_cached = coarsened_points.reshape(
        ((grid.nk + 1), (grid.nj + 1), (grid.ni + 1), 3))

    grid.geometry_defined_for_all_pillars_cached = True
    grid.geometry_defined_for_all_cells_cached = True
    grid.array_cell_geometry_is_defined = np.full(tuple(grid.extent_kji),
                                                  True,
                                                  dtype=bool)

    collection = None
    if inherit_properties:
        source_collection = source_grid.extract_property_collection()
        if source_collection is not None:
            collection = rqp.GridPropertyCollection()
            collection.set_grid(grid)
            collection.extend_imported_list_copying_properties_from_other_grid_collection(
                source_collection,
                coarsening=fine_coarse,
                realization=inherit_realization,
                copy_all_realizations=inherit_all_realizations)

    _set_parent_window_in_grid(set_parent_window, source_grid, grid,
                               fine_coarse)

    # write grid
    if new_grid_title is None or len(new_grid_title) == 0:
        new_grid_title = 'grid coarsened from ' + str(
            rqet.citation_title_for_node(source_grid.root))

    model.h5_release()
    if new_epc_file:
        _write_grid(new_epc_file,
                    grid,
                    property_collection=collection,
                    grid_title=new_grid_title,
                    mode='w')
    else:
        ext_uuid, _ = model.h5_uuid_and_path_for_node(
            rqet.find_nested_tags(source_grid.root, ['Geometry', 'Points']),
            'Coordinates')
        _write_grid(epc_file,
                    grid,
                    ext_uuid=ext_uuid,
                    property_collection=collection,
                    grid_title=new_grid_title,
                    mode='a')

    return grid
Ejemplo n.º 14
0
def refined_grid(epc_file,
                 source_grid,
                 fine_coarse,
                 inherit_properties=False,
                 inherit_realization=None,
                 inherit_all_realizations=False,
                 source_grid_uuid=None,
                 set_parent_window=None,
                 infill_missing_geometry=True,
                 new_grid_title=None,
                 new_epc_file=None):
    """Generates a refined version of the source grid, optionally inheriting properties.

    arguments:
       epc_file (string): file name to rewrite the model's xml to; if source grid is None, model is loaded from this file
       source_grid (grid.Grid object, optional): if None, the epc_file is loaded and it should contain one ijk grid object
          (or one 'ROOT' grid) which is used as the source grid unless source_grid_uuid is specified to identify the grid
       fine_coarse (resqpy.olio.fine_coarse.FineCoarse object): the mapping between cells in the fine (output) and
          coarse (source) grids
       inherit_properties (boolean, default False): if True, the new grid will have a copy of any properties associated
          with the source grid, with values resampled in the simplest way onto the finer grid
       inherit_realization (int, optional): realization number for which properties will be inherited; ignored if
          inherit_properties is False
       inherit_all_realizations (boolean, default False): if True (and inherit_realization is None), properties for all
          realizations will be inherited; if False, only properties with a realization of None are inherited; ignored if
          inherit_properties is False or inherit_realization is not None
       source_grid_uuid (uuid.UUID, optional): the uuid of the source grid – an alternative to the source_grid argument
          as a way of identifying the grid
       set_parent_window (boolean or str, optional): if True or 'parent', the refined grid has its parent window attribute
          set; if False, the parent window is not set; if None, the default will be True if new_epc_file is None or False
          otherwise; if 'grandparent' then an intervening parent window with no refinement or coarsening will be skipped
          and its box used in the parent window for the new grid, relating directly to the original grid
       infill_missing_geometry (boolean, default True): if True, an attempt is made to generate grid geometry in the
          source grid wherever it is undefined; if False, any undefined geometry will result in an assertion failure
       new_grid_title (string): used as the citation title text for the new grid object
       new_epc_file (string, optional): if None, the source epc_file is extended with the new grid object; if present,
          a new epc file (& associated h5 file) is created to contain the refined grid (& crs)

    returns:
       new grid object being the refined grid; the epc and hdf5 files are written to as an intentional side effect

    notes:
       this function refines an entire grid; to refine a local area of a grid, first use the extract_box function
       and then use this function on the extracted grid; in such a case, using a value of 'grandparent' for the
       set_parent_window argument will relate the refined grid back to the original;
       if geometry infilling takes place, cached geometry and mask arrays within the source grid object will be
       modified as a side-effect of the function (but not written to hdf5 or changed in xml)
    """

    epc_file, model, model_in, source_grid =  \
        _establish_models_and_source_grid(epc_file, new_epc_file, source_grid, source_grid_uuid)

    assert fine_coarse is not None and isinstance(fine_coarse, fc.FineCoarse)
    if set_parent_window is None:
        set_parent_window = (new_epc_file is None)

    if infill_missing_geometry and (
            not source_grid.geometry_defined_for_all_cells()
            or not source_grid.geometry_defined_for_all_pillars()):
        log.debug('attempting infill of geometry missing in source grid')
        source_grid.set_geometry_is_defined(treat_as_nan=None,
                                            treat_dots_as_nan=True,
                                            complete_partial_pillars=True,
                                            nullify_partial_pillars=False,
                                            complete_all=True)
    assert source_grid.geometry_defined_for_all_pillars(
    ), 'refinement requires geometry to be defined for all pillars'
    assert source_grid.geometry_defined_for_all_cells(
    ), 'refinement requires geometry to be defined for all cells'

    assert tuple(fine_coarse.coarse_extent_kji) == tuple(source_grid.extent_kji),  \
           'fine_coarse mapping coarse extent does not match that of source grid'
    fine_coarse.assert_valid()

    source_grid.cache_all_geometry_arrays()
    if source_grid.has_split_coordinate_lines:
        source_grid.create_column_pillar_mapping()

    if model is not model_in:
        crs_part = model_in.part_for_uuid(source_grid.crs_uuid)
        assert crs_part is not None
        model.copy_part_from_other_model(model_in, crs_part)

    # todo: set nan-abled numpy operations?

    if source_grid.has_split_coordinate_lines:

        grid = _refined_faulted_grid(model, source_grid, fine_coarse)

    else:

        grid = _refined_unfaulted_grid(model, source_grid, fine_coarse)

    # todo: option of re-draping interpolated pillars to surface

    collection = None
    if inherit_properties:
        collection = _inherit_properties(source_grid, grid, fine_coarse,
                                         inherit_realization,
                                         inherit_all_realizations)

    if set_parent_window:
        _set_parent_window(set_parent_window, source_grid, grid, fine_coarse)

    # write grid
    if new_grid_title is None or len(new_grid_title) == 0:
        new_grid_title = 'grid refined from ' + str(
            rqet.citation_title_for_node(source_grid.root))

    model.h5_release()
    if model is not model_in:
        model_in.h5_release()

    if new_epc_file:
        _write_grid(new_epc_file,
                    grid,
                    property_collection=collection,
                    grid_title=new_grid_title,
                    mode='w')
    else:
        ext_uuid, _ = model.h5_uuid_and_path_for_node(
            rqet.find_nested_tags(source_grid.root, ['Geometry', 'Points']),
            'Coordinates')
        _write_grid(epc_file,
                    grid,
                    ext_uuid=ext_uuid,
                    property_collection=collection,
                    grid_title=new_grid_title,
                    mode='a')

    return grid
Ejemplo n.º 15
0
    def convert_to_polylines(self,
                             closed_array = None,
                             count_perpol = None,
                             coordinates = None,
                             crs_uuid = None,
                             rep_int_root = None):
        """Returns a list of Polylines objects from a PolylineSet.

        note:
            all arguments are optional and by default the data will be taken from self

        args:
            closed_array: array containing a bool for each polygon in if it is open (False) or closed (True)
            count_perpol: array containing a list of polygon "lengths" for each polygon
            coordinates: array containing coordinates for all the polygons
            crs_uuid: crs_uuid for polylineset
            rep_int_root: represented interpretation root (optional)

        returns:
            list of polyline objects

        :meta common:
        """

        from resqpy.lines._polyline import Polyline

        if count_perpol is None:
            count_perpol = self.count_perpol
        if closed_array is None:
            closed_array = np.zeros(len(count_perpol), dtype = bool)
            closed_node = rqet.find_nested_tags(self.root, ['LinePatch', 'ClosedPolylines'])
            if closed_node is not None:
                closed_array[:] = self.get_bool_array(closed_node)
        if coordinates is None:
            coordinates = self.coordinates
        if crs_uuid is None:
            crs_uuid = self.crs_uuid
        if rep_int_root is None:
            rep_int_root = self.rep_int_root
        polys = []
        count = 0
        for i in range(len(count_perpol)):
            if i != len(count_perpol) - 1:
                subset = coordinates[count:int(count_perpol[i]) + count].copy()
            else:
                subset = coordinates[count:int(count_perpol[i]) + count + 1].copy()
            if vu.isclose(subset[0], subset[-1]):
                isclosed = True
            else:
                isclosed = closed_array[i]
            count += int(count_perpol[i])
            subtitle = f"{self.title} {i+1}"
            polys.append(
                Polyline(self.model,
                         set_bool = isclosed,
                         set_coord = subset,
                         set_crs = crs_uuid,
                         title = subtitle,
                         rep_int_root = rep_int_root))

        return polys
Ejemplo n.º 16
0
def fault_throw_scaling(epc_file,
                        source_grid=None,
                        scaling_factor=None,
                        connection_set=None,
                        scaling_dict=None,
                        ref_k0=0,
                        ref_k_faces='top',
                        cell_range=0,
                        offset_decay=0.5,
                        store_displacement=False,
                        inherit_properties=False,
                        inherit_realization=None,
                        inherit_all_realizations=False,
                        inherit_gcs=True,
                        new_grid_title=None,
                        new_epc_file=None):
    """Extends epc with a new grid with fault throws multiplied by scaling factors.

    arguments:
       epc_file (string): file name to rewrite the model's xml to; if source grid is None, model is loaded from this file
       source_grid (grid.Grid object, optional): if None, the epc_file is loaded and it should contain one ijk grid object
          (or one 'ROOT' grid) which is used as the source grid
       scaling_factor (float, optional): if present, the default scaling factor to apply to split pillars which do not
          appear in any of the faults in the scaling dictionary; if None, such pillars are left unchanged
       connection_set (fault.GridConnectionSet object): the connection set with associated fault feature list, used to
          identify which faces (and hence pillars) belong to which named fault
       scaling_dict (dictionary mapping string to float): the scaling factor to apply to each named fault; any faults not
          included in the dictionary will be left unadjusted (unless a default scaling factor is given as scaling_factor arg)
       ref_k0 (integer, default 0): the reference layer (zero based) to use when determining the pre-existing throws
       ref_k_faces (string, default 'top'): 'top' or 'base' identifying which bounding interface to use as the reference
       cell_range (integer, default 0): the number of cells away from faults which will have depths adjusted to spatially
          smooth the effect of the throw scaling (ie. reduce sudden changes in gradient due to the scaling)
       offset_decay (float, default 0.5): DEPRECATED; ignored
       store_displacement (boolean, default False): if True, 3 grid property parts are created, one each for x, y, & z
          displacement of cells' centres brought about by the fault throw scaling
       inherit_properties (boolean, default False): if True, the new grid will have a copy of any properties associated
          with the source grid
       inherit_realization (int, optional): realization number for which properties will be inherited; ignored if
          inherit_properties is False
       inherit_all_realizations (boolean, default False): if True (and inherit_realization is None), properties for all
          realizations will be inherited; if False, only properties with a realization of None are inherited; ignored if
          inherit_properties is False or inherit_realization is not None
       inherit_gcs (boolean, default True): if True, any grid connection set objects related to the source grid will be
          inherited by the modified grid
       new_grid_title (string): used as the citation title text for the new grid object
       new_epc_file (string, optional): if None, the source epc_file is extended with the new grid object; if present,
          a new epc file (& associated h5 file) is created to contain the derived grid (& crs)

    returns:
       new grid (grid.Grid object), with fault throws scaled according to values in the scaling dictionary

    notes:
       grid points are moved along pillar lines;
       stretch is towards or away from mid-point of throw;
       same shift is applied to all layers along pillar;
       pillar lines assumed to be straight;
       the offset decay argument might be changed in a future version to give improved smoothing;
       if a large fault is represented by a series of parallel minor faults 'stepping' down, each minor fault will have the
       scaling factor applied independently, leading to some unrealistic results
    """

    assert epc_file or new_epc_file, 'epc file name not specified'
    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
    model, source_grid = _establish_model_and_source_grid(
        epc_file, source_grid)
    assert source_grid.grid_representation == 'IjkGrid'
    assert model is not None

    assert source_grid.has_split_coordinate_lines, 'cannot scale fault throws in unfaulted grid'
    assert scaling_factor is not None or (connection_set is not None
                                          and scaling_dict is not None)

    if ref_k_faces == 'base':
        ref_k0 += 1
    assert ref_k0 >= 0 and ref_k0 <= source_grid.nk, 'reference layer out of range'

    # take a copy of the grid
    log.debug('copying grid')
    grid = copy_grid(source_grid, model)
    grid.cache_all_geometry_arrays()  # probably already cached anyway

    # todo: handle pillars with no geometry defined, and cells without geometry defined
    assert grid.geometry_defined_for_all_pillars(
    ), 'not all pillars have defined geometry'

    primaries = (grid.nj + 1) * (grid.ni + 1)
    offsets = np.zeros(grid.points_cached.shape[1:])

    if scaling_factor is not None:  # apply global scaling to throws
        _set_offsets_based_on_scaling_factor(grid, scaling_factor, offsets,
                                             ref_k0, primaries)

    if connection_set is not None and scaling_dict is not None:  # overwrite any global offsets with named fault throw adjustments
        _set_offsets_based_on_scaling_dict(grid, connection_set, scaling_dict,
                                           offsets, ref_k0)

    # initialise flag array for adjustments
    adjusted = np.zeros((primaries, ), dtype=bool)

    # insert adjusted throws to all layers of split pillars
    grid.points_cached[:, grid.split_pillar_indices_cached, :] += offsets[
        grid.split_pillar_indices_cached, :].reshape(1, -1, 3)
    adjusted[grid.split_pillar_indices_cached] = True
    grid.points_cached[:, primaries:, :] += offsets[primaries:, :].reshape(
        1, -1, 3)

    # iteratively look for pillars neighbouring adjusted pillars, adjusting by a decayed amount
    adjusted = adjusted.reshape((grid.nj + 1, grid.ni + 1))
    while cell_range > 0:
        newly_adjusted = _neighbourly_adjustment(grid, offsets, adjusted,
                                                 cell_range)
        adjusted = np.logical_or(adjusted, newly_adjusted)
        cell_range -= 1

    # check cell edge relative directions (in x,y) to ensure geometry is still coherent
    log.debug('checking grid geometry coherence')
    grid.check_top_and_base_cell_edge_directions()

    # build cell displacement property array(s)
    if store_displacement:
        log.debug('generating cell displacement property arrays')
        displacement_collection = _displacement_properties(grid, source_grid)
    else:
        displacement_collection = None

    collection = _prepare_simple_inheritance(grid, source_grid,
                                             inherit_properties,
                                             inherit_realization,
                                             inherit_all_realizations)
    if collection is None:
        collection = displacement_collection
    elif displacement_collection is not None:
        collection.inherit_imported_list_from_other_collection(
            displacement_collection, copy_cached_arrays=False)

    if new_grid_title is None or len(new_grid_title) == 0:
        new_grid_title = 'grid with fault throws scaled by ' + str(scaling_factor) + ' from ' +  \
                         str(rqet.citation_title_for_node(source_grid.root))

    gcs_list = []
    if inherit_gcs:
        gcs_uuids = model.uuids(obj_type='GridConnectionSetRepresentation',
                                related_uuid=source_grid.uuid)
        for gcs_uuid in gcs_uuids:
            gcs = rqf.GridConnectionSet(model, uuid=gcs_uuid)
            gcs.cache_arrays()
            gcs_list.append((gcs, gcs.title))
        log.debug(f'{len(gcs_list)} grid connection sets to be inherited')

    # write model
    model.h5_release()
    if new_epc_file:
        _write_grid(new_epc_file,
                    grid,
                    property_collection=collection,
                    grid_title=new_grid_title,
                    mode='w')
        epc_file = new_epc_file
    else:
        ext_uuid, _ = model.h5_uuid_and_path_for_node(
            rqet.find_nested_tags(source_grid.root, ['Geometry', 'Points']),
            'Coordinates')
        _write_grid(epc_file,
                    grid,
                    ext_uuid=ext_uuid,
                    property_collection=collection,
                    grid_title=new_grid_title,
                    mode='a')

    if len(gcs_list):
        log.debug(
            f'inheriting grid connection sets related to source grid: {source_grid.uuid}'
        )
        _inherit_gcs_list(epc_file, gcs_list, source_grid, grid)

    return grid
Ejemplo n.º 17
0
def test_model_copy_all_parts_non_resqpy_hdf5_paths(
        example_model_with_properties):
    # this test uses low level methods to override the hdf5 internal paths for some new objects

    epc = example_model_with_properties.epc_file
    dir = example_model_with_properties.epc_directory
    copied_epc = os.path.join(dir, 'copied.epc')
    extent_kji = (3, 5, 5)  # needs to match extent of grid in example model

    # add some properties with bespoke internal hdf5 paths
    original = rq.Model(epc)
    assert original is not None
    grid_uuid = original.uuid(obj_type='IjkGridRepresentation')
    assert grid_uuid is not None
    data = np.linspace(0.0, 1000.0, num=75).reshape(extent_kji)

    hdf5_paths = [
        '/RESQML/uuid_waffle/values', '/RESQML/waffle_uuid/unusual_name',
        'RESQML/class_uuid_waffle/values0',
        'RESQML/something_interesting/array', '/RESQML/abrupt',
        'no_resqml/uuid/values'
    ]

    prop_list = []
    path_list = []  # elements will have 'uuid' replaced with uuid string
    h5_reg = rwh5.H5Register(original)
    for i, hdf5_path in enumerate(hdf5_paths):
        prop = rqp.Property(original, support_uuid=grid_uuid)
        if 'uuid' in hdf5_path:
            path = hdf5_path.replace('uuid', str(prop.uuid))
        else:
            path = hdf5_path
        prop.prepare_import(cached_array=data,
                            source_info='test data',
                            keyword=f'TEST{i}',
                            property_kind='continuous',
                            uom='m')
        for entry in prop.collection.imported_list:  # only one entry
            # override internal hdf5 path
            h5_reg.register_dataset(entry[0],
                                    'values_patch0',
                                    prop.collection.__dict__[entry[3]],
                                    hdf5_path=path)
        prop_list.append(prop)
        path_list.append(path)
    h5_reg.write(mode='a')

    for prop, hdf5_path in zip(prop_list, path_list):
        root_node = prop.create_xml(find_local_property_kind=False)
        assert root_node is not None
        # override the hdf5 internal path in the xml tree
        path_node = rqet.find_nested_tags(
            root_node, ['PatchOfValues', 'Values', 'Values', 'PathInHdfFile'])
        assert path_node is not None
        path_node.text = hdf5_path

    # rewrite model
    original.store_epc()

    # re-open model and test copying
    original = rq.Model(epc)
    assert original is not None
    copied = rq.new_model(copied_epc)
    copied.copy_all_parts_from_other_model(original, consolidate=False)

    assert set(original.uuids()) == set(copied.uuids())
    assert set(original.parts()) == set(copied.parts())