Ejemplo n.º 1
0
    def set_parameters_cell_basis(self, gb: pp.GridBucket, data: Dict):
        """
        Assign parameters for the micro gb. Very simple for now, this must be improved.

        Args:
            gb (TYPE): the micro gb.

        Returns:
            None.

        """
        # First initialize data
        for g, d in gb:

            d["Aavatsmark_transmissibilities"] = True

            domain_boundary = np.logical_and(
                g.tags["domain_boundary_faces"],
                np.logical_not(g.tags["fracture_faces"]),
            )

            boundary_faces = np.where(domain_boundary)[0]
            if domain_boundary.size > 0:
                bc_type = boundary_faces.size * ["dir"]
            else:
                bc_type = np.empty(0)

            bc = pp.BoundaryCondition(g, boundary_faces, bc_type)
            if hasattr(g, "face_on_macro_bound"):
                micro_ind = g.face_on_macro_bound
                macro_ind = g.macro_face_ind

                bc.is_neu[micro_ind] = data["bc_macro"]["bc"].is_neu[macro_ind]
                bc.is_dir[micro_ind] = data["bc_macro"]["bc"].is_dir[macro_ind]

            param = {"bc": bc}
            perm = data["g_data"](g)["second_order_tensor"]
            param["second_order_tensor"] = perm
            param["specific_volume"] = data["g_data"](g)["specific_volume"]

            # Use python inverter for mpfa for small problems, where it does not pay off
            # to fire up numba. The set threshold value is somewhat randomly picked.
            if g.num_cells < 100:
                param["mpfa_inverter"] = "python"

            pp.initialize_default_data(g, d, self.keyword, param)

        for e, d in gb.edges():
            mg = d["mortar_grid"]
            g1, g2 = gb.nodes_of_edge(e)
            param = {}
            if not hasattr(g1, "is_auxiliary") or not g1.is_auxiliary:
                check_P = mg.secondary_to_mortar_avg()
                param.update(data["e_data"](mg, g1, g2, check_P))

            pp.initialize_data(mg, d, self.keyword, param)
Ejemplo n.º 2
0
def set_projections(
        gb: pp.GridBucket,
        edges: Optional[List[Tuple[pp.Grid, pp.Grid]]] = None) -> None:
    """Define a local coordinate system, and projection matrices, for all
    grids of co-dimension 1.

    The function adds one item to the data dictionary of all GridBucket edges
    that neighbors a co-dimension 1 grid, defined as:
        key: tangential_normal_projection, value: pp.TangentialNormalProjection
            provides projection to the surface of the lower-dimensional grid

    Note that grids of co-dimension 2 and higher are ignored in this construction,
    as we do not plan to do contact mechanics on these objects.

    It is assumed that the surface is planar.

    """
    if edges is None:
        edges = [e for e, _ in gb.edges()]

    # Information on the vector normal to the surface is not available directly
    # from the surface grid (it could be constructed from the surface geometry,
    # which spans the tangential plane). We instead get the normal vector from
    # the adjacent higher dimensional grid.
    # We therefore access the grids via the edges of the mixed-dimensional grid.
    for e in edges:
        d_m = gb.edge_props(e)

        mg = d_m["mortar_grid"]
        # Only consider edges where the lower-dimensional neighbor is of co-dimension 1
        if not mg.dim == (gb.dim_max() - 1):
            continue

        # Neigboring grids
        g_l, g_h = gb.nodes_of_edge(e)

        # Find faces of the higher dimensional grid that coincide with the mortar
        # grid. Go via the primary to mortar projection
        # Convert matrix to csr, then the relevant face indices are found from
        # the (column) indices
        faces_on_surface = mg.primary_to_mortar_int().tocsr().indices

        # Find out whether the boundary faces have outwards pointing normal vectors
        # Negative sign implies that the normal vector points inwards.
        sgn, _ = g_h.signs_and_cells_of_boundary_faces(faces_on_surface)

        # Unit normal vector
        unit_normal = g_h.face_normals[:g_h.dim] / g_h.face_areas
        # Ensure all normal vectors on the relevant surface points outwards
        unit_normal[:, faces_on_surface] *= sgn

        # Now we need to pick out *one*  normal vector of the higher dimensional grid

        # which coincides with this mortar grid, so we kill off all entries for the
        # "other" side:
        unit_normal[:, mg._ind_face_on_other_side] = 0

        # Project to the mortar and then to the fracture
        outwards_unit_vector_mortar = mg.primary_to_mortar_int().dot(
            unit_normal.T).T
        normal_lower = mg.mortar_to_secondary_int().dot(
            outwards_unit_vector_mortar.T).T

        # NOTE: The normal vector is based on the first cell in the mortar grid,
        # and will be pointing from that cell towards the other side of the
        # mortar grid. This defines the positive direction in the normal direction.
        # Although a simpler implementation seems to be possible, going via the
        # first element in faces_on_surface, there is no guarantee that this will
        # give us a face on the positive (or negative) side, hence the more general
        # approach is preferred.
        #
        # NOTE: The basis for the tangential direction is determined by the
        # construction internally in TangentialNormalProjection.
        projection = pp.TangentialNormalProjection(normal_lower)

        d_l = gb.node_props(g_l)
        # Store the projection operator in the lower-dimensional data
        d_l["tangential_normal_projection"] = projection
Ejemplo n.º 3
0
def extrude_grid_bucket(gb: pp.GridBucket, z: np.ndarray) -> Tuple[pp.GridBucket, Dict]:
    """ Extrude a GridBucket by extending all fixed-dimensional grids in the z-direction.

    In practice, the original grid bucket will be 2d, and the result is 3d.

    The returned GridBucket is fully functional, including mortar grids on the gb edges.
    The data dictionaries on nodes and edges are mainly empty. Data can be transferred from
    the original GridBucket via the returned map between old and new grids.

    Parameters:
        gb (pp.GridBukcet): Mixed-dimensional grid to be extruded. Should be 2d.
        z (np.ndarray): z-coordinates of the nodes in the extruded grid. Should be
            either non-negative or non-positive, and be sorted in increasing or
            decreasing order, respectively.

    Returns:
        gb (pp.GridBucket): Mixed-dimensional grid, 3d. The data dictionaries on nodes and
            edges are mostly empty.
        dict: Mapping from individual grids in the old bucket to the corresponding
            extruded grids in the new one. The dictionary values are a namedtuple with
            elements grid (new grid), cell_map and face_map, where the two latter
            describe mapping between the new and old grid, see extrude_grid for details.

    """

    # New GridBucket. to be filled in
    gb_new = pp.GridBucket()

    # Data structure for mapping between old and new grids
    g_map = {}

    # Container for grid information
    Mapping = namedtuple("mapping", ["grid", "cell_map", "face_map"])

    # Loop over all grids in the old bucket, extrude the grid, save mapping information
    for g, _ in gb:
        g_new, cell_map, face_map = extrude_grid(g, z)

        if hasattr(g, "frac_num"):
            g_new.frac_num = g.frac_num

        gb_new.add_nodes([g_new])

        g_map[g] = Mapping(g_new, cell_map, face_map)

    # Loop over all edges in the old grid, create corresponding edges in the new gb.
    # Also define mortar_grids
    for e, d in gb.edges():

        # grids of the old edge, extruded version of each grid
        gl, gh = gb.nodes_of_edge(e)
        gl_new = g_map[gl].grid
        gh_new = g_map[gh].grid

        # Next, we need the cell-face mapping for the new grid.
        # The idea is to first find the old map, then replace each cell-face relation
        # with the set of cells and faces (exploiting first that the new grids are
        # matching due to the extrusion algorithm, and second that the cell-map and
        # face-map stores indices in increasing layer index, so that the first cell
        # and first face both are in the first layer, thus they match, etc.).
        face_cells_old = d["face_cells"]

        # cells (in low-dim grid) and faces in high-dim grid that define the same
        # geometric quantity
        cells, faces, _ = sps.find(face_cells_old)

        # Cell-map for the low-dimensional grid, face-map for the high-dim
        cell_map = g_map[gl].cell_map
        face_map = g_map[gh].face_map

        # Data structure for the new face-cell map
        rows = np.empty(0, dtype=np.int)
        cols = np.empty(0, dtype=np.int)

        # The standard MortarGrid __init__ assumes that when faces are split because of
        # a fracture, the faces are ordered with one side first, then the other. This
        # will not be True for this layered construction. Instead, keep track of all
        # faces that should be moved to the other side.
        face_on_other_side = np.empty(0, dtype=np.int)

        # Loop over cells in gl would not have been as clean, as each cell is associated
        # with faces on both sides
        # Faces are found from the high-dim grid, cells in the low-dim grid
        for idx in range(faces.size):
            rows = np.hstack((rows, cell_map[cells[idx]]))
            cols = np.hstack((cols, face_map[faces[idx]]))

            # Here, we tacitly assume that the original grid had its faces split in the
            # standard way, that is, all faces on one side have index lower than any
            # face on the other side.
            if faces[idx] > np.median(faces):
                face_on_other_side = np.hstack(
                    (face_on_other_side, face_map[faces[idx]])
                )

        data = np.ones(rows.size, dtype=np.bool)
        # Create new face-cell map
        face_cells_new = sps.coo_matrix(
            (data, (rows, cols)), shape=(gl_new.num_cells, gh_new.num_faces)
        ).tocsc()

        # Define the new edge
        e = (gh_new, gl_new)
        # Add to new gb, together with the new face-cell map
        gb_new.add_edge(e, face_cells_new)

        # Create a mortar grid, add to data of new edge
        side_g = {
            mortar_grid.LEFT_SIDE: gl_new.copy(),
            mortar_grid.RIGHT_SIDE: gl_new.copy(),
        }

        # Construct mortar grid, with instructions on which faces belong to which side
        mg = pp.MortarGrid(
            gl_new.dim, side_g, face_cells_new, face_duplicate_ind=face_on_other_side
        )

        d_new = gb_new.edge_props(e)

        d_new["mortar_grid"] = mg

    return gb_new, g_map
Ejemplo n.º 4
0
def set_projections(gb: pp.GridBucket) -> None:
    """ Define a local coordinate system, and projection matrices, for all
    grids of co-dimension 1.

    The function adds one item to the data dictionary of all GridBucket edges
    that neighbors a co-dimension 1 grid, defined as:
        key: tangential_normal_projection, value: pp.TangentialNormalProjection
            provides projection to the surface of the lower-dimensional grid

    Note that grids of co-dimension 2 and higher are ignored in this construction,
    as we do not plan to do contact mechanics on these objects.

    It is assumed that the surface is planar.

    """
    # Information on the vector normal to the surface is not available directly
    # from the surface grid (it could be constructed from the surface geometry,
    # which spans the tangential plane). We instead get the normal vector from
    # the adjacent higher dimensional grid.
    # We therefore access the grids via the edges of the mixed-dimensional grid.
    for e, d_m in gb.edges():

        mg = d_m["mortar_grid"]
        # Only consider edges where the lower-dimensional neighbor is of co-dimension 1
        if not mg.dim == (gb.dim_max() - 1):
            continue

        # Neigboring grids
        _, g_h = gb.nodes_of_edge(e)

        # Find faces of the higher dimensional grid that coincide with the mortar
        # grid. Go via the master to mortar projection
        # Convert matrix to csr, then the relevant face indices are found from
        # the (column) indices
        faces_on_surface = mg.master_to_mortar_int().tocsr().indices

        # Find out whether the boundary faces have outwards pointing normal vectors
        # Negative sign implies that the normal vector points inwards.
        sgn = g_h.sign_of_faces(faces_on_surface)

        # Unit normal vector
        unit_normal = g_h.face_normals[: g_h.dim] / g_h.face_areas
        # Ensure all normal vectors on the relevant surface points outwards
        unit_normal[:, faces_on_surface] *= sgn

        # Now we need to pick out *one*  normal vector of the higher dimensional grid
        # which coincides with this mortar grid. This could probably have been
        # done with face tags, but we instead project the normal vectors onto the
        # mortar grid to kill off all irrelevant faces. Restriction to a single
        # normal vector is done in the construction of the projection object
        # (below).
        # NOTE: Use a single normal vector to span the tangential and normal space,
        # thus assuming the surface is planar.
        outwards_unit_vector_mortar = mg.master_to_mortar_int().dot(unit_normal.T).T

        # NOTE: The normal vector is based on the first cell in the mortar grid,
        # and will be pointing from that cell towards the other side of the
        # mortar grid. This defines the positive direction in the normal direction.
        # Although a simpler implementation seems to be possible, going via the
        # first element in faces_on_surface, there is no guarantee that this will
        # give us a face on the positive (or negative) side, hence the more general
        # approach is preferred.
        #
        # NOTE: The basis for the tangential direction is determined by the
        # construction internally in TangentialNormalProjection.
        projection = pp.TangentialNormalProjection(
            outwards_unit_vector_mortar[:, 0].reshape((-1, 1))
        )

        # Store the projection operator in the mortar data
        d_m["tangential_normal_projection"] = projection