Example #1
0
    def is_equivalent(self, other, check_extra_metadata=True):
        """Returns True if this interpretation is essentially the same as the other; otherwise False."""

        if other is None or not isinstance(other, HorizonInterpretation):
            return False
        if self is other or bu.matching_uuids(self.uuid, other.uuid):
            return True
        if self.genetic_boundary_feature is not None:
            if not self.genetic_boundary_feature.is_equivalent(
                    other.genetic_boundary_feature):
                return False
        elif other.genetic_boundary_feature is not None:
            return False
        if self.root is not None and other.root is not None:
            if rqet.citation_title_for_node(
                    self.root) != rqet.citation_title_for_node(other.root):
                return False
        elif self.root is not None or other.root is not None:
            return False
        if (self.domain != other.domain or not equivalent_chrono_pairs(
                self.has_occurred_during, other.has_occurred_during)
                or self.sequence_stratigraphy_surface !=
                other.sequence_stratigraphy_surface):
            return False
        if check_extra_metadata and not equivalent_extra_metadata(self, other):
            return False
        if not self.boundary_relation_list and not other.boundary_relation_list:
            return True
        if not self.boundary_relation_list or not other.boundary_relation_list:
            return False
        return set(self.boundary_relation_list) == set(
            other.boundary_relation_list)
Example #2
0
    def is_equivalent(self, other, check_extra_metadata=True):
        """Returns True if this interpretation is essentially the same as the other; otherwise False."""

        if other is None or not isinstance(other, GeobodyInterpretation):
            return False
        if self is other or bu.matching_uuids(self.uuid, other.uuid):
            return True
        if self.geobody_feature is not None:
            if not self.geobody_feature.is_equivalent(other.geobody_feature):
                return False
        elif other.geobody_feature is not None:
            return False
        if self.root is not None and other.root is not None:
            if rqet.citation_title_for_node(
                    self.root) != rqet.citation_title_for_node(other.root):
                return False
        elif self.root is not None or other.root is not None:
            return False
        if check_extra_metadata and not equivalent_extra_metadata(self, other):
            return False
        return (self.domain == other.domain and equivalent_chrono_pairs(
            self.has_occurred_during, other.has_occurred_during)
                and self.composition == other.composition
                and self.implacement == other.implacement
                and self.geobody_shape == other.geobody_shape)
Example #3
0
    def is_equivalent(self, other, check_extra_metadata = True):
        """Returns True if this interpretation is essentially the same as the other; otherwise False."""

        if other is None or not isinstance(other, FaultInterpretation):
            return False
        if self is other or bu.matching_uuids(self.uuid, other.uuid):
            return True

        attr_list = ['tectonic_boundary_feature', 'root', 'maximum_throw', 'mean_azimuth', 'mean_dip']
        one_none_attr_list = [(getattr(self, v) is None) != (getattr(other, v) is None) for v in attr_list]
        if any(one_none_attr_list):
            # If only one of self or other has a None attribute
            return False

        # List of attributes that are not None in either self or other
        non_none_attr_list = [a for a in attr_list if getattr(self, a) is not None]

        # Additional tests for attributes that are not None
        check_dict = {
            'tectonic_boundary_feature':
                lambda: self.tectonic_boundary_feature.is_equivalent(other.tectonic_boundary_feature),
            'root':
                lambda: rqet.citation_title_for_node(self.root) != rqet.citation_title_for_node(other.root),
            'maximum_throw':
                lambda: maths.isclose(self.maximum_throw, other.maximum_throw, rel_tol = 1e-3),
            'mean_azimuth':
                lambda: maths.isclose(self.mean_azimuth, other.mean_azimuth, abs_tol = 0.5),
            'mean_dip':
                lambda: maths.isclose(self.mean_dip, other.mean_dip, abs_tol = 0.5)
        }

        check_outcomes = [check_dict[v]() for v in non_none_attr_list]
        if not all(check_outcomes):  # If any of the Additional tests fail then self and other are not equivalent
            return False

        if (not equivalent_chrono_pairs(self.main_has_occurred_during, other.main_has_occurred_during) or
                self.is_normal != other.is_normal or self.domain != other.domain or
                self.is_listric != other.is_listric):
            return False

        if check_extra_metadata and not equivalent_extra_metadata(self, other):
            return False
        if not self.throw_interpretation_list and not other.throw_interpretation_list:
            return True
        if not self.throw_interpretation_list or not other.throw_interpretation_list:
            return False
        if len(self.throw_interpretation_list) != len(other.throw_interpretation_list):
            return False
        for this_ti, other_ti in zip(self.throw_interpretation_list, other.throw_interpretation_list):
            if this_ti[0] != other_ti[0]:
                return False  # throw kind
            if not equivalent_chrono_pairs(this_ti[1], other_ti[1]):
                return False
        return True
Example #4
0
    def _load_from_xml(self):

        assert self.root is not None  # polyline xml node is specified
        poly_root = self.root

        self.title = rqet.citation_title_for_node(poly_root)

        self.extra_metadata = rqet.load_metadata_from_xml(self.root)

        self.isclosed = rqet.bool_from_text(
            rqet.node_text(rqet.find_tag(poly_root, 'IsClosed')))
        assert self.isclosed is not None  # Required field

        patch_node = rqet.find_tag(poly_root, 'NodePatch')
        assert patch_node is not None  # Required field

        geometry_node = rqet.find_tag(patch_node, 'Geometry')
        assert geometry_node is not None  # Required field

        self.crs_uuid = bu.uuid_from_string(
            rqet.find_nested_tags_text(geometry_node, ['LocalCrs', 'UUID']))
        assert self.crs_uuid is not None  # Required field

        points_node = rqet.find_tag(geometry_node, 'Points')
        assert points_node is not None  # Required field
        load_hdf5_array(self, points_node, 'coordinates', tag='Coordinates')

        self.nodepatch = (rqet.find_tag_int(patch_node, 'PatchIndex'),
                          rqet.find_tag_int(patch_node, 'Count'))
        assert not any(
            map(lambda x: x is None,
                self.nodepatch))  # Required fields - assert neither are None

        self.rep_int_root = self.model.referenced_node(
            rqet.find_tag(poly_root, 'RepresentedInterpretation'))
Example #5
0
File: _xml.py Project: bp/resqpy
def _create_supporting_representation(
        model,
        support_root=None,
        support_uuid=None,
        root=None,
        title=None,
        content_type='obj_IjkGridRepresentation'):
    """Craate a supporting representation reference node refering to an IjkGrid and optionally add to root."""

    assert support_root is not None or support_uuid is not None

    # todo: check that support_root is for a RESQML class that can support properties, matching content_type

    if support_root is not None:
        uuid = rqet.uuid_for_part_root(support_root)
        if uuid is not None:
            support_uuid = uuid
        if title is None:
            title = rqet.citation_title_for_node(support_root)
    assert support_uuid is not None
    if not title:
        title = model.title(uuid=support_uuid)
        if not title:
            title = 'supporting representation'

    return _create_ref_node('SupportingRepresentation',
                            title,
                            support_uuid,
                            content_type=content_type,
                            root=root)
Example #6
0
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)]
Example #7
0
File: _xml.py Project: bp/resqpy
def _referenced_node(model, ref_node, consolidate=False):
    """For a given xml reference node, returns the node for the object referred to, if present."""

    # log.debug(f'ref node called for: {ref_node}')
    if ref_node is None:
        return None
    # content_type = rqet.find_tag_text(ref_node, 'ContentType')
    # log.debug(f'ref node title: {rqet.citation_title_for_node(rqet.find_tag(ref_node, "Title"))}')
    uuid = bu.uuid_from_string(rqet.find_tag_text(ref_node, 'UUID'))
    # log.debug(f'ref node uuid: {uuid}')
    if uuid is None:
        return None
    # return model.root_for_part(model.parts_list_of_type(type_of_interest = content_type, uuid = uuid))
    if consolidate and model.consolidation is not None and uuid in model.consolidation.map:
        resident_uuid = model.consolidation.map[uuid]
        if resident_uuid is None:
            return None
        node = model.root_for_part(model.part_for_uuid(resident_uuid))
        if node is not None:
            # patch resident uuid and title into ref node!
            uuid_node = rqet.find_tag(ref_node, 'UUID')
            uuid_node.text = str(resident_uuid)
            title_node = rqet.find_tag(ref_node, 'Title')
            if title_node is not None:
                title = rqet.citation_title_for_node(node)
                if title:
                    title_node.text = str(title)
    else:
        node = model.root_for_part(model.part_for_uuid(uuid))
    # log.debug(f'ref_node return node: {node}')
    return node
    def is_equivalent(self, other, check_extra_metadata=True):
        """Returns True if this interpretation is essentially the same as the other; otherwise False.

        arguments:
           other (GeologicUnitInterpretation or StratigraphicUnitInterpretation): the other interpretation to
              compare this one against
           check_extra_metadata (bool, default True): if True, then extra metadata items must match for the two
              interpretations to be deemed equivalent; if False, extra metadata is ignored in the comparison

        returns:
           bool: True if this interpretation is essentially the same as the other; False otherwise
        """

        # this method is coded to allow use by the derived StratigraphicUnitInterpretation class
        if other is None or not isinstance(other, type(self)):
            return False
        if self is other or bu.matching_uuids(self.uuid, other.uuid):
            return True
        if self.geologic_unit_feature is not None:
            if not self.geologic_unit_feature.is_equivalent(
                    other.geologic_unit_feature):
                return False
        elif other.geologic_unit_feature is not None:
            return False
        if self.root is not None and other.root is not None:
            if rqet.citation_title_for_node(
                    self.root) != rqet.citation_title_for_node(other.root):
                return False
        elif self.root is not None or other.root is not None:
            return False
        if check_extra_metadata and not rqo.equivalent_extra_metadata(
                self, other):
            return False
        return (self.composition == other.composition
                and self.material_implacement == other.material_implacement
                and self.domain == other.domain
                and rqo.equivalent_chrono_pairs(self.has_occurred_during,
                                                other.has_occurred_during))
Example #9
0
    def is_equivalent(self, other, check_extra_metadata=True):
        """Returns True if this interpretation is essentially the same as the other; otherwise False."""

        if other is None or not isinstance(other, WellboreInterpretation):
            return False
        if self is other or bu.matching_uuids(self.uuid, other.uuid):
            return True
        if self.wellbore_feature is not None:
            if not self.wellbore_feature.is_equivalent(other.wellbore_feature):
                return False
        elif other.wellbore_feature is not None:
            return False
        if self.root is not None and other.root is not None:
            if rqet.citation_title_for_node(
                    self.root) != rqet.citation_title_for_node(other.root):
                return False
            if self.domain != other.domain:
                return False
        elif self.root is not None or other.root is not None:
            return False
        if check_extra_metadata and not equivalent_extra_metadata(self, other):
            return False
        return (self.title == other.title
                and self.is_drilled == other.is_drilled)
Example #10
0
 def better_root(model, root_a, root_b):
     a = rqet.citation_title_for_node(root_a)
     b = rqet.citation_title_for_node(root_b)
     if a is None or len(a) == 0:
         return root_b
     if b is None or len(b) == 0:
         return root_a
     parts_like_a = model.parts(title=a)
     parts_like_b = model.parts(title=b)
     if len(parts_like_a) > 1 and len(parts_like_b) == 1:
         return root_b
     elif len(parts_like_b) > 1 and len(parts_like_a) == 1:
         return root_a
     a_digits = 0
     for c in a:
         if c.isdigit():
             a_digits += 1
     b_digits = 0
     for c in b:
         if c.isdigit():
             b_digits += 1
     if a_digits < b_digits:
         return root_b
     return root_a
Example #11
0
def _create_xml_time_series_node(collection, time_series_uuid, time_index,
                                 p_node, support_uuid, support_type,
                                 support_root):
    if time_series_uuid is None or time_index is None:
        related_time_series_node = None
    else:
        related_time_series_node = collection.model.root(uuid=time_series_uuid)
        time_series = rts.any_time_series(collection.model,
                                          uuid=time_series_uuid)
        time_series.create_time_index(time_index, root=p_node)

    support_title = '' if support_root is None else rqet.citation_title_for_node(
        support_root)
    collection.model.create_supporting_representation(
        support_uuid=support_uuid,
        root=p_node,
        title=support_title,
        content_type=support_type)
    return related_time_series_node
Example #12
0
def _derive_from_wellspec_check_grid_name(check_grid_name, grid, col_list):
    """ Verify the grid object to which the cell indices in the WELLSPEC table belong.

    arguments:
       check_grid_name (boolean): if True, the citation title of the grid will be extracted and returned
       grid (grid object): the grid object whose citation titles will be returned
       col_list (list): list of strings of column names to be added to the WELLSPEC file. If a citation title is
                        extracted from the grid object, 'GRID' will be added to the col_list

    returns:
       string of grid citation title extracted from the grid object
       list of columns to be added to the WELLSPEC file
    """

    if check_grid_name:
        grid_name = rqet.citation_title_for_node(grid.root).upper()
        if not grid_name:
            name_for_check = None
        else:
            col_list.append('GRID')
            name_for_check = grid_name
    else:
        name_for_check = None
    return name_for_check, col_list
Example #13
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
Example #14
0
def add_one_grid_property_array(epc_file,
                                a,
                                property_kind,
                                grid_uuid=None,
                                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,
                                const_value=None,
                                expand_const_arrays=False,
                                points=False,
                                extra_metadata={},
                                new_epc_file=None):
    """Adds a grid property from a numpy array to an existing resqml dataset.

    arguments:
       epc_file (string): file name to load resqml model from (and rewrite to if new_epc_file is None)
       a (3D numpy array): the property array to be added to the model; for a constant array set this None
          and use the const_value argument, otherwise this array is required
       property_kind (string): the resqml property kind
       grid_uuid (uuid object or string, optional): the uuid of the grid to which the property relates;
          if None, the property is attached to the 'main' grid
       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; for simulation
          models it is desirable to use the simulation keyword when appropriate
       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 grid)
       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
       const_value (float or int, optional): if present, a constant array is added 'filled' with this value, in which
          case argument a should be None
       expand_const_arrays (bool, default False): if True and a const_value is provided, a fully expanded array is
          added to the model instead of a const 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 grid 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)
    if grid_uuid is None:
        grid = model.grid()
        grid_uuid = grid.uuid
    else:
        grid = model.grid_for_uuid_from_grid_list(grid_uuid)
        if grid is None:
            grid = grr.any_grid(model, uuid=grid_uuid, find_properties=False)
    assert grid is not None, 'failed to establish grid object'

    if not discrete:
        string_lookup_uuid = None

    if const_value is not None and expand_const_arrays:
        assert count_per_element == 1 and not points, 'attempt to expand const array for non-standard shape'
        if isinstance(const_value, bool):
            dtype = bool
        elif discrete:
            dtype = int
        else:
            dtype = float
        a = np.full(grid.extent_kji, const_value, dtype=dtype)
        const_value = None

    # create an empty property collection and add the new array to its 'imported' list
    gpc = rqp.GridPropertyCollection()
    gpc.set_grid(grid)
    gpc.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,
        const_value=const_value,
        points=points)

    # write or re-write model
    model.h5_release()
    if new_epc_file:
        grid_title = rqet.citation_title_for_node(grid.root)
        uuid_list = _write_grid(new_epc_file,
                                grid,
                                property_collection=gpc,
                                grid_title=grid_title,
                                mode='w',
                                time_series_uuid=time_series_uuid,
                                string_lookup_uuid=string_lookup_uuid,
                                extra_metadata=extra_metadata)
    else:
        # add arrays to hdf5 file holding source grid geometry
        uuid_list = _write_grid(epc_file,
                                grid,
                                property_collection=gpc,
                                mode='a',
                                geometry=False,
                                time_series_uuid=time_series_uuid,
                                string_lookup_uuid=string_lookup_uuid,
                                extra_metadata=extra_metadata)

    if uuid_list is None or len(uuid_list) == 0:
        return None
    return uuid_list[0]
Example #15
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
Example #16
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
Example #17
0
    def create_xml(self,
                   ext_uuid=None,
                   add_as_part=True,
                   add_relationships=True,
                   title=None,
                   originator=None):
        """Create xml from polyline.

        args:
            ext_uuid: the uuid of the hdf5 external part

        :meta common:
        """

        if ext_uuid is None:
            ext_uuid = self.model.h5_uuid()

        if title is not None:
            self.title = title
        if self.title is None:
            self.title = 'polyline'

        polyline = super().create_xml(add_as_part=False, originator=originator)

        if self.rep_int_root is not None:
            rep_int = self.rep_int_root
            if "FaultInterpretation" in str(rqet.content_type(rep_int)):
                content_type = 'obj_FaultInterpretation'
            else:
                content_type = 'obj_HorizonInterpretation'
            self.model.create_ref_node('RepresentedInterpretation',
                                       rqet.citation_title_for_node(rep_int),
                                       rep_int.attrib['uuid'],
                                       content_type=content_type,
                                       root=polyline)

        isclosed = rqet.SubElement(polyline, ns['resqml2'] + 'IsClosed')
        isclosed.set(ns['xsi'] + 'type', ns['xsd'] + 'boolean')
        isclosed.text = str(self.isclosed).lower()

        nodepatch = rqet.SubElement(polyline, ns['resqml2'] + 'NodePatch')
        nodepatch.set(ns['xsi'] + 'type', ns['resqml2'] + 'NodePatch')
        nodepatch.text = '\n'

        patchindex = rqet.SubElement(nodepatch, ns['resqml2'] + 'PatchIndex')
        patchindex.set(ns['xsi'] + 'type', ns['xsd'] + 'nonNegativeInteger')
        patchindex.text = str(self.nodepatch[0])

        count = rqet.SubElement(nodepatch, ns['resqml2'] + 'Count')
        count.set(ns['xsi'] + 'type', ns['xsd'] + 'positiveInteger')
        count.text = str(self.nodepatch[1])

        geom = rqet.SubElement(nodepatch, ns['resqml2'] + 'Geometry')
        geom.set(ns['xsi'] + 'type', ns['resqml2'] + 'PointGeometry')
        geom.text = '\n'

        self.model.create_crs_reference(crs_uuid=self.crs_uuid, root=geom)

        points = rqet.SubElement(geom, ns['resqml2'] + 'Points')
        points.set(ns['xsi'] + 'type', ns['resqml2'] + 'Point3dHdf5Array')
        points.text = '\n'

        coords = rqet.SubElement(points, ns['resqml2'] + 'Coordinates')
        coords.set(ns['xsi'] + 'type', ns['eml'] + 'Hdf5Dataset')
        coords.text = rqet.null_xml_text

        self.model.create_hdf5_dataset_ref(ext_uuid,
                                           self.uuid,
                                           'points_patch0',
                                           root=coords)

        if add_as_part:
            self.model.add_part('obj_PolylineRepresentation', self.uuid,
                                polyline)
            if add_relationships:
                crs_root = self.model.root_for_uuid(self.crs_uuid)
                self.model.create_reciprocal_relationship(
                    polyline, 'destinationObject', crs_root, 'sourceObject')
                if self.rep_int_root is not None:  # Optional
                    self.model.create_reciprocal_relationship(
                        polyline, 'destinationObject', self.rep_int_root,
                        'sourceObject')

                ext_part = rqet.part_name_for_object(
                    'obj_EpcExternalPartReference', ext_uuid, prefixed=False)
                ext_node = self.model.root_for_part(ext_part)
                self.model.create_reciprocal_relationship(
                    polyline, 'mlToExternalPartProxy', ext_node,
                    'externalPartProxyToMl')

        return polyline
Example #18
0
    def create_xml(self,
                   ext_uuid = None,
                   add_as_part = True,
                   add_relationships = True,
                   title = None,
                   originator = None,
                   save_polylines = False):
        """Create xml from polylineset.

        args:
            save_polylines: If true, polylines are also saved individually

        :meta common:
        """

        if ext_uuid is None:
            ext_uuid = self.model.h5_uuid()

        self.save_polys = save_polylines

        if title:
            self.title = title
        if not self.title:
            self.title = 'polyline set'

        if self.save_polys:
            for poly in self.polys:
                poly.create_xml(ext_uuid, add_relationships = add_relationships, originator = originator)

        polyset = super().create_xml(add_as_part = False, originator = originator)

        if self.rep_int_root is not None:
            rep_int = self.rep_int_root
            if "FaultInterpretation" in str(rqet.content_type(rep_int)):
                content_type = 'obj_FaultInterpretation'
            else:
                content_type = 'obj_HorizonInterpretation'
            self.model.create_ref_node('RepresentedInterpretation',
                                       rqet.citation_title_for_node(rep_int),
                                       rep_int.attrib['uuid'],
                                       content_type = content_type,
                                       root = polyset)

        # We convert all Polylines to the CRS of the first Polyline in the set, so set this as crs_uuid

        patch = rqet.SubElement(polyset, ns['resqml2'] + 'LinePatch')
        patch.set(ns['xsi'] + 'type', ns['resqml2'] + 'PolylineSetPatch')
        patch.text = '\n'

        pindex = rqet.SubElement(patch, ns['resqml2'] + 'PatchIndex')
        pindex.set(ns['xsi'] + 'type', ns['xsd'] + 'nonNegativeInteger')
        pindex.text = '0'

        if self.boolnotconstant:
            # We have mixed data - use a BooleanArrayFromIndexArray
            closed = rqet.SubElement(patch, ns['resqml2'] + 'ClosedPolylines')
            closed.set(ns['xsi'] + 'type', ns['xsd'] + 'BooleanArrayFromIndexArray')
            closed.text = '\n'

            bool_val = rqet.SubElement(closed, ns['resqml2'] + 'Value')
            bool_val.set(ns['xsi'] + 'type', ns['xsd'] + 'boolean')
            bool_val.text = str(self.boolvalue).lower()

            ind_val = rqet.SubElement(closed, ns['resqml2'] + 'Indices')
            ind_val.set(ns['xsi'] + 'type', ns['eml'] + 'Hdf5Dataset')
            ind_val.text = '\n'

            count = rqet.SubElement(closed, ns['resqml2'] + 'Count')
            count.set(ns['xsi'] + 'type', ns['xsd'] + 'positiveInteger')
            count.text = str(len(self.count_perpol))

            self.model.create_hdf5_dataset_ref(ext_uuid, self.uuid, 'indices_patch0', root = ind_val)
        else:
            # All bools are the same - use a BooleanConstantArray
            closed = rqet.SubElement(patch, ns['resqml2'] + 'ClosedPolylines')
            closed.set(ns['xsi'] + 'type', ns['resqml2'] + 'BooleanConstantArray')
            closed.text = '\n'
            bool_val = rqet.SubElement(closed, ns['resqml2'] + 'Value')
            bool_val.set(ns['xsi'] + 'type', ns['xsd'] + 'boolean')
            bool_val.text = str(self.boolvalue).lower()
            count = rqet.SubElement(closed, ns['resqml2'] + 'Count')
            count.set(ns['xsi'] + 'type', ns['xsd'] + 'positiveInteger')
            count.text = str(len(self.count_perpol))

        count_pp = rqet.SubElement(patch, ns['resqml2'] + 'NodeCountPerPolyline')
        count_pp.set(ns['xsi'] + 'type', ns['resqml2'] + 'IntegerHdf5Array')
        count_pp.text = '\n'

        null = rqet.SubElement(count_pp, ns['resqml2'] + 'NullValue')
        null.set(ns['xsi'] + 'type', ns['xsd'] + 'integer')
        null.text = '0'

        count_val = rqet.SubElement(count_pp, ns['resqml2'] + 'Values')
        count_val.set(ns['xsi'] + 'type', ns['eml'] + 'Hdf5Dataset')
        count_val.text = '\n'

        self.model.create_hdf5_dataset_ref(ext_uuid, self.uuid, 'NodeCountPerPolyline_patch0', root = count_val)

        geom = rqet.SubElement(patch, ns['resqml2'] + 'Geometry')
        geom.set(ns['xsi'] + 'type', ns['resqml2'] + 'PointGeometry')
        geom.text = '\n'

        self.model.create_crs_reference(crs_uuid = self.crs_uuid, root = geom)

        points = rqet.SubElement(geom, ns['resqml2'] + 'Points')
        points.set(ns['xsi'] + 'type', ns['resqml2'] + 'Point3dHdf5Array')
        points.text = '\n'

        coords = rqet.SubElement(points, ns['resqml2'] + 'Coordinates')
        coords.set(ns['xsi'] + 'type', ns['eml'] + 'Hdf5Dataset')
        coords.text = '\n'

        self.model.create_hdf5_dataset_ref(ext_uuid, self.uuid, 'points_patch0', root = coords)

        if add_as_part:
            self.model.add_part('obj_PolylineSetRepresentation', self.uuid, polyset)
            if add_relationships:
                crs_root = self.model.root_for_uuid(self.crs_uuid)
                self.model.create_reciprocal_relationship(polyset, 'destinationObject', crs_root, 'sourceObject')
                if self.rep_int_root is not None:  # Optional
                    self.model.create_reciprocal_relationship(polyset, 'destinationObject', self.rep_int_root,
                                                              'sourceObject')
                if self.save_polys:
                    for poly in self.polys:
                        self.model.create_reciprocal_relationship(polyset, 'destinationObject', poly.root_node,
                                                                  'sourceObject')

                ext_part = rqet.part_name_for_object('obj_EpcExternalPartReference', ext_uuid, prefixed = False)
                ext_node = self.model.root_for_part(ext_part)
                self.model.create_reciprocal_relationship(polyset, 'mlToExternalPartProxy', ext_node,
                                                          'externalPartProxyToMl')

        return polyset
Example #19
0
def add_faults(epc_file,
               source_grid,
               polylines=None,
               lines_file_list=None,
               lines_crs_uuid=None,
               full_pillar_list_dict=None,
               left_right_throw_dict=None,
               create_gcs=True,
               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 new curtain fault(s) added.

    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
       polylines (lines.PolylineSet or list of lines.Polyline, optional): list of poly lines for which curtain faults
          are to be added; either this or lines_file_list or full_pillar_list_dict must be present
       lines_file_list (list of str, optional): a list of file paths, each containing one or more poly lines in simple
          ascii format§; see notes; either this or polylines or full_pillar_list_dicr must be present
       lines_crs_uuid (uuid, optional): if present, the uuid of a coordinate reference system with which to interpret
          the contents of the lines files; if None, the crs used by the grid will be assumed
       full_pillar_list_dict (dict mapping str to list of pairs of ints, optional): dictionary mapping from a fault name
          to a list of pairs of ints being the ordered neigbouring primary pillar (j0, i0) defining the curtain fault;
          either this or polylines or lines_file_list must be present
       left_right_throw_dict (dict mapping str to pair of floats, optional): dictionary mapping from a fault name to a
          pair of floats being the semi-throw adjustment on the left and the right of the fault (see notes); semi-throw
          values default to (+0.5, -0.5)
       create_gcs (boolean, default True): if True, and faults are being defined by lines, a grid connection set is
          created with one feature per new fault and associated organisational objects are also created; ignored if
          lines_file_list is None
       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 a copy of the source grid with the structure modified to incorporate
       the new faults

    notes:
       full_pillar_list_dict is typically generated by Grid.make_face_sets_from_pillar_lists();
       pillars will be split as needed to model the new faults, though existing splits will be used as appropriate, so
       this function may also be used to add a constant to the throw of existing faults;
       the left_right_throw_dict contains a pair of floats for each fault name (as found in keys of full_pillar_list_dict);
       these throw values are lengths in the uom of the crs used by the grid (which must have the same xy units as z units);

       this function does not add a GridConnectionSet to the model – calling code may wish to do that
    """

    log.info('adding faults')

    assert epc_file or new_epc_file, 'epc file name not specified'
    assert epc_file or source_grid is not None, 'neither epc file name nor source grid supplied'
    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 in [
        'IjkGrid', 'IjkBlockGrid'
    ]  # unstructured grids not catered for
    assert model is not None
    assert len([
        arg for arg in (polylines, lines_file_list, full_pillar_list_dict)
        if arg is not None
    ]) == 1

    # take a copy of the resqpy grid object, without writing to hdf5 or creating xml
    # the copy will be a Grid, even if the source is a RegularGrid
    grid = copy_grid(source_grid, model)
    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)

    if isinstance(polylines, rql.PolylineSet):
        polylines = polylines.convert_to_polylines()

    composite_face_set_dict = {}

    # build pillar list dict for polylines if necessary
    if full_pillar_list_dict is None:
        full_pillar_list_dict = {}
        _populate_composite_face_sets_for_polylines(model, grid, polylines,
                                                    lines_crs_uuid, grid.crs,
                                                    lines_file_list,
                                                    full_pillar_list_dict,
                                                    composite_face_set_dict)

    else:  # populate composite face set dictionary from full pillar list
        _populate_composite_face_sets_for_pillar_lists(
            source_grid, full_pillar_list_dict, composite_face_set_dict)

    # log.debug(f'full_pillar_list_dict:\n{full_pillar_list_dict}')

    _process_full_pillar_list_dict(grid, full_pillar_list_dict,
                                   left_right_throw_dict)

    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 = 'copy of ' + str(
            rqet.citation_title_for_node(
                source_grid.root)) + ' with added faults'

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

        _write_grid(epc_file,
                    grid,
                    ext_uuid=ext_uuid,
                    property_collection=collection,
                    grid_title=new_grid_title,
                    mode='a')

    # create grid connection set if requested
    _create_gcs_if_requested(create_gcs, composite_face_set_dict, new_epc_file,
                             grid)

    return grid
Example #20
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
Example #21
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
Example #22
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
Example #23
0
def _citation_title_for_part(
        model, part):  # duplicate functionality to title_for_part()
    """Returns the citation title for the specified part."""

    return rqet.citation_title_for_node(_root_for_part(model, part))
Example #24
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
Example #25
0
def zonal_grid(epc_file,
               source_grid = None,
               zone_title = None,
               zone_uuid = None,
               zone_layer_range_list = None,
               k0_min = None,
               k0_max = None,
               use_dominant_zone = False,
               inactive_laissez_faire = True,
               new_grid_title = None,
               new_epc_file = None):
    """Extends an existing model with a new version of the source grid converted to a single, thick, layer per zone.

    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
       zone_title (string): if not None, a discrete property with this as the citation title is used as the zone property
       zone_uuid (string or uuid): if not None, a discrete property with this uuid is used as the zone property (see notes)
       zone_layer_range_list (list of (int, int, int)): each entry being (min_k0, max_k0, zone_index); alternative to
          working from a zone array
       k0_min (int, optional): the minimum layer number in the source grid (zero based) to include in the zonal version;
          default is zero (ie. top layer in source grid)
       k0_max (int, optional): the maximum layer number in the source grid (zero based) to include in the zonal version;
          default is nk - 1 (ie. bottom layer in source grid)
       use_dominant_zone (boolean, default False): if True, the most common zone value in each layer is used for the whole
          layer; if False, then variation of zone values in active cells in a layer will raise an assertion error
       inactive_laissez_faire (boolean, optional): if True, a cell in the zonal grid will be set active if any of the
          corresponding cells in the source grid are active; otherwise all corresponding cells in the source grid
          must be active for the zonal cell to be active; default is True
       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 zonal grid (& crs)

    returns:
       new grid object (grid.Grid) with one layer per zone of the source grid

    notes:
       usually one of zone_title or zone_uuid or zone_layer_range_list should be passed, if none are passed then a
       single layer grid is generated; zone_layer_range_list will take precendence if present
    """

    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 in ['IjkGrid', 'IjkBlockGrid']
    if source_grid.grid_representation == 'IjkBlockGrid':
        source_grid.make_regular_points_cached()
    assert model is not None
    single_layer_mode = (not zone_title and not zone_uuid and
                         (zone_layer_range_list is None or len(zone_layer_range_list) == 1))

    k0_min, k0_max = _set_or_check_k_min_max(k0_min, k0_max, source_grid)

    if not single_layer_mode:  # process zone array
        if zone_layer_range_list is None:
            zone_array = _fetch_zone_array(source_grid, zone_title, zone_uuid)
            zone_layer_range_list = zone_layer_ranges_from_array(zone_array,
                                                                 k0_min,
                                                                 k0_max,
                                                                 use_dominant_zone = use_dominant_zone)
        zone_count = len(zone_layer_range_list)
        # above is list of (zone_min_k0, zone_max_k0, zone) sorted by zone_min_k0
        log.info('following layer ranges are based on top layer being numbered 1 (simulator protocol)')
        for (zone_min_k0, zone_max_k0, zone) in zone_layer_range_list:
            log.info('zone id {0:1d} covers layers {1:1d} to {2:1d}'.format(zone, zone_min_k0 + 1, zone_max_k0 + 1))
    else:
        zone_layer_range_list = [(k0_min, k0_max, 0)]
        zone_count = 1
    assert zone_count > 0, 'unexpected lack of zones'

    # create a new, empty grid object
    is_regular = grr.is_regular_grid(source_grid.root) and single_layer_mode
    grid = _empty_grid(model, source_grid, is_regular, k0_min, k0_max, zone_count)

    # aggregate inactive cell mask depending on laissez faire argument
    _set_inactive_cell_mask(source_grid, grid, inactive_laissez_faire, single_layer_mode, k0_min, k0_max,
                            zone_layer_range_list, zone_count)

    if not is_regular:
        _process_geometry(source_grid, grid, single_layer_mode, k0_min, k0_max, zone_layer_range_list, zone_count)

    # establish title for the new grid
    if new_grid_title is None or len(new_grid_title) == 0:
        if single_layer_mode:
            preamble = 'single layer'
        else:
            preamble = 'zonal'
        new_grid_title = preamble + ' version of ' + str(rqet.citation_title_for_node(source_grid.root))

    # write the new grid
    model.h5_release()
    if new_epc_file:
        _write_grid(new_epc_file, grid, grid_title = new_grid_title, mode = 'w')
    else:
        _write_grid(epc_file, grid, ext_uuid = None, grid_title = new_grid_title, mode = 'a')

    return grid
Example #26
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
Example #27
0
def gather_ensemble(case_epc_list,
                    new_epc_file,
                    consolidate=True,
                    shared_grids=True,
                    shared_time_series=True,
                    create_epc_lookup=True):
    """Creates a composite resqml dataset by merging all parts from all models in list, assigning realization numbers.

    arguments:
       case_epc_list (list of strings): paths of individual realization epc files
       new_epc_file (string): path of new composite epc to be created (with paired hdf5 file)
       consolidate (boolean, default True): if True, simple parts are tested for equivalence and where similar enough
          a single shared object is established in the composite dataset
       shared_grids (boolean, default True): if True and consolidate is True, then grids are also consolidated
          with equivalence based on extent of grids (and citation titles if grid extents within the first case
          are not distinct); ignored if consolidate is False
       shared_time_series (boolean, default False): if True and consolidate is True, then time series are consolidated
          with equivalence based on title, without checking that timestamp lists are the same
       create_epc_lookup (boolean, default True): if True, a StringLookupTable is created to map from realization
          number to case epc path

    notes:
       property objects will have an integer realization number assigned, which matches the corresponding index into
       the case_epc_list;
       if consolidating with shared grids, then only properties will be gathered from realisations after the first and
       an exception will be raised if the grids are not matched between realisations
    """

    if not consolidate:
        shared_grids = False

    composite_model = rq.Model(new_epc_file,
                               new_epc=True,
                               create_basics=True,
                               create_hdf5_ext=True)

    epc_lookup_dict = {}

    for r, case_epc in enumerate(case_epc_list):
        t_r_start = time()  # debug
        log.info(f'gathering realszation {r}: {case_epc}')
        epc_lookup_dict[r] = case_epc
        case_model = rq.Model(case_epc)
        if r == 0:  # first case
            log.info('first case')  # debug
            composite_model.copy_all_parts_from_other_model(
                case_model, realization=0, consolidate=consolidate)
            if shared_time_series:
                host_ts_uuids = case_model.uuids(obj_type='TimeSeries')
                host_ts_titles = []
                for ts_uuid in host_ts_uuids:
                    host_ts_titles.append(case_model.title(uuid=ts_uuid))
            if shared_grids:
                host_grid_uuids = case_model.uuids(
                    obj_type='IjkGridRepresentation')
                host_grid_shapes = []
                host_grid_titles = []
                title_match_required = False
                for grid_uuid in host_grid_uuids:
                    grid_root = case_model.root(uuid=grid_uuid)
                    host_grid_shapes.append(
                        grr.extent_kji_from_root(grid_root))
                    host_grid_titles.append(
                        rqet.citation_title_for_node(grid_root))
                if len(set(host_grid_shapes)) < len(host_grid_shapes):
                    log.warning(
                        'shapes of representative grids are not distinct, grid titles must match during ensemble gathering'
                    )
                    title_match_required = True
        else:  # subsequent cases
            log.info('subsequent case')  # debug
            composite_model.consolidation = None  # discard any previous mappings to limit dictionary growth
            if shared_time_series:
                for ts_uuid in case_model.uuids(obj_type='TimeSeries'):
                    ts_title = case_model.title(uuid=ts_uuid)
                    ts_index = host_ts_titles.index(ts_title)
                    host_ts_uuid = host_ts_uuids[ts_index]
                    composite_model.force_consolidation_uuid_equivalence(
                        ts_uuid, host_ts_uuid)
            if shared_grids:
                log.info('shared grids')  # debug
                for grid_uuid in case_model.uuids(
                        obj_type='IjkGridRepresentation'):
                    grid_root = case_model.root(uuid=grid_uuid)
                    grid_extent = grr.extent_kji_from_root(grid_root)
                    host_index = None
                    if grid_extent in host_grid_shapes:
                        if title_match_required:
                            case_grid_title = rqet.citation_title_for_node(
                                grid_root)
                            for host_grid_index in len(host_grid_uuids):
                                if grid_extent == host_grid_shapes[
                                        host_grid_index] and case_grid_title == host_grid_titles[
                                            host_grid_index]:
                                    host_index = host_grid_index
                                    break
                        else:
                            host_index = host_grid_shapes.index(grid_extent)
                    assert host_index is not None, 'failed to match grids when gathering ensemble'
                    composite_model.force_consolidation_uuid_equivalence(
                        grid_uuid, host_grid_uuids[host_index])
                    grid_relatives = case_model.parts(related_uuid=grid_uuid)
                    t_props = 0.0
                    composite_h5_file_name = composite_model.h5_file_name()
                    composite_h5_uuid = composite_model.h5_uuid()
                    case_h5_file_name = case_model.h5_file_name()
                    for part in grid_relatives:
                        if 'Property' in part:
                            t_p_start = time()
                            composite_model.copy_part_from_other_model(
                                case_model,
                                part,
                                realization=r,
                                consolidate=True,
                                force=shared_time_series,
                                self_h5_file_name=composite_h5_file_name,
                                h5_uuid=composite_h5_uuid,
                                other_h5_file_name=case_h5_file_name)
                            t_props += time() - t_p_start
                    log.info(f'time props: {t_props:.3f} sec')  # debug
            else:
                log.info('non shared grids')  # debug
                composite_model.copy_all_parts_from_other_model(
                    case_model, realization=r, consolidate=consolidate)
        log.info(f'case time: {time() - t_r_start:.2f} secs')  # debug

    if create_epc_lookup and len(epc_lookup_dict):
        epc_lookup = rqp.StringLookup(composite_model,
                                      int_to_str_dict=epc_lookup_dict,
                                      title='ensemble epc table')
        epc_lookup.create_xml()

    composite_model.store_epc()

    log.info(
        f'{len(epc_lookup_dict)} realizations merged into ensemble {new_epc_file}'
    )
Example #28
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
Example #29
0
def well_name(well_object, model=None):
    """Returns the 'best' citation title from the object or related well objects.

    arguments:
       well_object (object, uuid or root): Object for which a well name is required. Can be a
          Trajectory, WellboreInterpretation, WellboreFeature, BlockedWell, WellboreMarkerFrame,
          WellboreFrame, DeviationSurvey or MdDatum object
       model (model.Model, optional): required if passing a uuid or root; not recommended otherwise

    returns:
       string being the 'best' citation title to serve as a well name, form the object or some related objects

    note:
       xml and relationships must be established for this function to work
    """
    def better_root(model, root_a, root_b):
        a = rqet.citation_title_for_node(root_a)
        b = rqet.citation_title_for_node(root_b)
        if a is None or len(a) == 0:
            return root_b
        if b is None or len(b) == 0:
            return root_a
        parts_like_a = model.parts(title=a)
        parts_like_b = model.parts(title=b)
        if len(parts_like_a) > 1 and len(parts_like_b) == 1:
            return root_b
        elif len(parts_like_b) > 1 and len(parts_like_a) == 1:
            return root_a
        a_digits = 0
        for c in a:
            if c.isdigit():
                a_digits += 1
        b_digits = 0
        for c in b:
            if c.isdigit():
                b_digits += 1
        if a_digits < b_digits:
            return root_b
        return root_a

    def best_root(model, roots_list):
        if len(roots_list) == 0:
            return None
        if len(roots_list) == 1:
            return roots_list[0]
        if len(roots_list) == 2:
            return better_root(model, roots_list[0], roots_list[1])
        return better_root(model, roots_list[0],
                           best_root(model, roots_list[1:]))

    def best_root_for_object(well_object, model=None):

        if well_object is None:
            return None
        if model is None:
            model = well_object.model
        root_list = []
        obj_root = None
        obj_uuid = None
        obj_type = None
        traj_root = None

        if isinstance(well_object, str):
            obj_uuid = bu.uuid_from_string(well_object)
            assert obj_uuid is not None, 'well_name string argument could not be interpreted as uuid'
            well_object = obj_uuid
        if isinstance(well_object, bu.uuid.UUID):
            obj_uuid = well_object
            obj_root = model.root_for_uuid(obj_uuid)
            assert obj_root is not None, 'uuid not found in model when looking for well name'
            obj_type = rqet.node_type(obj_root)
        elif rqet.is_node(well_object):
            obj_root = well_object
            obj_type = rqet.node_type(obj_root)
            obj_uuid = rqet.uuid_for_part_root(obj_root)
        elif isinstance(well_object, Trajectory):
            obj_type = 'WellboreTrajectoryRepresentation'
            traj_root = well_object.root
        elif isinstance(well_object, rqo.WellboreFeature):
            obj_type = 'WellboreFeature'
        elif isinstance(well_object, rqo.WellboreInterpretation):
            obj_type = 'WellboreInterpretation'
        elif isinstance(well_object, BlockedWell):
            obj_type = 'BlockedWellboreRepresentation'
            if well_object.trajectory is not None:
                traj_root = well_object.trajectory.root
        elif isinstance(well_object,
                        WellboreMarkerFrame):  # note: trajectory might be None
            obj_type = 'WellboreMarkerFrameRepresentation'
            if well_object.trajectory is not None:
                traj_root = well_object.trajectory.root
        elif isinstance(well_object,
                        WellboreFrame):  # note: trajectory might be None
            obj_type = 'WellboreFrameRepresentation'
            if well_object.trajectory is not None:
                traj_root = well_object.trajectory.root
        elif isinstance(well_object, DeviationSurvey):
            obj_type = 'DeviationSurveyRepresentation'
        elif isinstance(well_object, MdDatum):
            obj_type = 'MdDatum'

        assert obj_type is not None, 'argument type not recognized for well_name'
        if obj_type.startswith('obj_'):
            obj_type = obj_type[4:]
        if obj_uuid is None:
            obj_uuid = well_object.uuid
            obj_root = model.root_for_uuid(obj_uuid)

        if obj_type == 'WellboreFeature':
            interp_parts = model.parts(obj_type='WellboreInterpretation')
            interp_parts = model.parts_list_filtered_by_related_uuid(
                interp_parts, obj_uuid)
            all_parts = interp_parts
            all_traj_parts = model.parts(
                obj_type='WellboreTrajectoryRepresentation')
            if interp_parts is not None:
                for part in interp_parts:
                    traj_parts = model.parts_list_filtered_by_related_uuid(
                        all_traj_parts, model.uuid_for_part(part))
                    all_parts += traj_parts
            if all_parts is not None:
                root_list = [model.root_for_part(part) for part in all_parts]
        elif obj_type == 'WellboreInterpretation':
            feat_roots = model.roots(
                obj_type='WellboreFeature',
                related_uuid=obj_uuid)  # should return one root
            traj_roots = model.roots(
                obj_type='WellboreTrajectoryRepresentation',
                related_uuid=obj_uuid)
            root_list = feat_roots + traj_roots
        elif obj_type == 'WellboreTrajectoryRepresentation':
            interp_parts = model.parts(obj_type='WellboreInterpretation')
            interp_parts = model.parts_list_filtered_by_related_uuid(
                interp_parts, obj_uuid)
            all_parts = interp_parts
            all_feat_parts = model.parts(obj_type='WellboreFeature')
            if interp_parts is not None:
                for part in interp_parts:
                    feat_parts = model.parts_list_filtered_by_related_uuid(
                        all_feat_parts, model.uuid_for_part(part))
                    all_parts += feat_parts
            if all_parts is not None:
                root_list = [model.root_for_part(part) for part in all_parts]
        elif obj_type in [
                'BlockedWellboreRepresentation',
                'WellboreMarkerFrameRepresentation',
                'WellboreFrameRepresentation'
        ]:
            if traj_root is None:
                traj_root = model.root(
                    obj_type='WellboreTrajectoryRepresentation',
                    related_uuid=obj_uuid)
            root_list = [best_root_for_object(traj_root, model=model)]
        elif obj_type == 'DeviationSurveyRepresentation':
            root_list = [
                best_root_for_object(model.root(obj_type='MdDatum',
                                                related_uuid=obj_uuid),
                                     model=model)
            ]
        elif obj_type == 'MdDatum':
            pass

        root_list.append(obj_root)

        return best_root(model, root_list)

    return rqet.citation_title_for_node(
        best_root_for_object(well_object, model=model))