Exemple #1
0
def nd_sides_shearzone_injection_cell(
    params: FlowParameters, gb: pp.GridBucket, reset_frac_tags: bool = True,
) -> None:
    """ Tag the Nd cells surrounding a shear zone injection point

    Parameters
    ----------
    params : FlowParameters
        parameters that contain "source_scalar_borehole_shearzone"
        (with "shearzone", and "borehole") and "length_scale".
    gb : pp.GridBucket
        grid bucket
    reset_frac_tags : bool [Default: True]
        if set to False, keep injection tag in the shear zone.
    """
    # Shorthand
    shearzone = params.source_scalar_borehole_shearzone.get("shearzone")

    # First, tag the fracture cell, and get the tag
    shearzone_injection_cell(params, gb)
    fracture = gb.get_grids(lambda g: gb.node_props(g, "name") == shearzone)[0]
    tags = fracture.tags["well_cells"]
    # Second, map the cell to the Nd grid
    nd_grid: pp.Grid = gb.grids_of_dimension(gb.dim_max())[0]
    data_edge = gb.edge_props((fracture, nd_grid))
    mg: pp.MortarGrid = data_edge["mortar_grid"]

    slave_to_master_face = mg.mortar_to_master_int() * mg.slave_to_mortar_int()
    face_to_cell = nd_grid.cell_faces.T
    slave_to_master_cell = face_to_cell * slave_to_master_face
    nd_tags = np.abs(slave_to_master_cell) * tags

    # Set tags on the nd-grid
    nd_grid.tags["well_cells"] = nd_tags
    ndd = gb.node_props(nd_grid)
    pp.set_state(ndd, {"well": tags})

    if reset_frac_tags:
        # reset tags on the fracture
        zeros = np.zeros(fracture.num_cells)
        fracture.tags["well_cells"] = zeros
        d = gb.node_props(fracture)
        pp.set_state(d, {"well": zeros})
Exemple #2
0
def nd_injection_cell_center(params: FlowParameters, gb: pp.GridBucket) -> None:
    """ Tag the center cell of the nd-grid with 1 (injection)

    Parameters
    ----------
    params : FlowParameters
    gb : pp.GridBucket

    """

    # Get the center of the domain.
    box = gb.bounding_box()
    pts = (box[1] + box[0]) / 2  # center of domain
    pts = np.atleast_2d(pts).T

    # Get the Nd-grid
    nd_grid = gb.grids_of_dimension(gb.dim_max())[0]

    # Tag highest dim grid with 1 in the cell closest to the grid center
    _tag_injection_cell(gb, nd_grid, pts, params.length_scale)
def propagate_fractures(gb: pp.GridBucket, faces: Dict[pp.Grid,
                                                       np.ndarray]) -> None:
    """
    gb - grid bucket with matrix and fracture grids.
    faces_h - list of list of faces to be split in the highest-dimensional
        grid. The length of the outer list equals the number of fractures.
        Each entry in the list is a list containing the higher-dimensional
        indices of the faces to be split for the extension of the corresponding
        fracture.
    Changes to grids done in-place.
    The call changes:
        Geometry and connectivity fields of the two grids involved.
        The face_cells mapping between them
        Their respective face tags.
    Also adds the following to node data dictionaries:
        new_cells and new_faces tags, for use in e.g. local discretization
        updates.
        partial_update, a boolean flag indicating that the grids have been
        updated.

    """

    dim_h: int = gb.dim_max()
    g_h: pp.Grid = gb.grids_of_dimension(dim_h)[0]

    n_old_faces_h: int = g_h.num_faces

    # First initialise certain tags to get rid of any existing tags from
    # previous calls
    d_h: Dict = gb.node_props(g_h)
    d_h["new_cells"] = np.empty(0, dtype=int)
    d_h["new_faces"] = np.empty(0, dtype=int)
    d_h["split_faces"] = np.empty(0, dtype=int)

    # Data structure for keeping track of faces in g_h to be split
    split_faces = np.empty(0, dtype=np.int)

    # By default, we will not update the higher-dimensional grid. This will be
    # changed in the below for loop if the grid gets faces split.
    # This variable can be used e.g. to check if a rediscretization is necessary on
    # the higher-dimensional grid
    d_h["partial_update"] = False

    # Initialize mapping between old and new faces for g_h. We will store the updates
    # from splitting related to each lower-dimensional grid, and then merge towards the
    # end; the split data may be handy for debugging
    face_map_h: List[sps.spmatrix] = [
        sps.dia_matrix((np.ones(g_h.num_faces), 0),
                       (g_h.num_faces, g_h.num_faces))
    ]

    # The propagation is divided into two main steps:
    # First, update the geomtry of the fracture grids, and, simultaneously, the higher
    # dimensional grid (the former will be updated once, the latter may undergo several
    # update steps, depending on how many fractures propagate).
    # Second, update the mortar grids. This is done after all fractures have been
    # propagated.

    for g_l in gb.grids_of_dimension(dim_h - 1):

        # The propagation of a fracture consists of the following major steps:
        #   1. Find which faces in g_h should be split for this g_l.
        #   2. Add nodes to g_l where the fracture will propagate.
        #   3. Update face-node and cell-face relation in g_l.
        #   4. Update face geometry of g_l.
        #   5. Update cell geometry of g_l.
        #   6. Split the faces in g_h to make room for the new fracture.
        #   7. Update geometry in g_l and g_h.
        #
        # IMPLEMENTATION NOTE: While point 7 replaces information from 4 and 5, the
        # provisional fields may still be needed in point 6.

        # Initialize data on new faces and cells
        d_l = gb.node_props(g_l)
        d_l["new_cells"] = np.empty(0, dtype=int)
        d_l["new_faces"] = np.empty(0, dtype=int)

        # Step 1:
        # Uniquify the faces to be split. Amongs others, this avoids trouble when
        # a faces is requested split twice, from two neighboring faces
        faces_h = np.unique(np.atleast_1d(np.array(faces[g_l])))
        split_faces = np.append(split_faces, faces_h)

        if faces_h.size == 0:
            # If there is no propagation for this fracture, we continue
            # No need to update discretization of this grid
            d_l["partial_update"] = False

            # Variable mappings are unit mappings
            d_l["face_index_map"] = sps.identity(g_l.num_faces)
            d_l["cell_index_map"] = sps.identity(g_l.num_cells)

            # Identity mapping of faces in this step
            face_map_h.append(sps.identity(g_h.num_faces))

            # Move on to the next fracture
            continue

        # Keep track of original information:
        n_old_faces_l = g_l.num_faces
        n_old_cells_l = g_l.num_cells
        n_old_nodes_l = g_l.num_nodes
        n_old_nodes_h = g_h.num_nodes

        # It is convenient to tag the nodes lying on the domain boundary. This
        # helps updating the face tags later:
        pp.utils.tags.add_node_tags_from_face_tags(gb, "domain_boundary")

        # Step 2:
        # Get the "involved nodes", i.e., the union between the new nodes in
        # the lower dimension and the boundary nodes where the fracture
        # propagates. The former are added to the nodes in g_l - specifically,
        # both node coordinates and global_point_ind of g_l are amended.
        unique_node_ind_l, unique_node_ind_h = _update_nodes_fracture_grid(
            g_h, g_l, faces_h)

        # Step 3:
        # Update the connectivity matrices (cell_faces and face_nodes) and tag
        # the lower-dimensional faces, including re-classification of (former)
        # tips to internal faces, where appropriate.
        n_new_faces, new_face_centers = _update_connectivity_fracture_grid(
            g_l,
            g_h,
            unique_node_ind_l,
            unique_node_ind_h,
            n_old_nodes_l,
            n_old_faces_l,
            n_old_cells_l,
            faces_h,
        )

        # Step 4: Update fracture grid face geometry
        # Note: This simply expands arrays with face geometry, but it does not
        # compute reasonable values for the geometry
        _append_face_geometry_fracture_grid(g_l, n_new_faces, new_face_centers)

        # Step 5: Update fracture grid cell geometry
        # Same for cells. Here the geometry quantities are copied from the
        # face values of g_h, thus values should be reasonable.
        new_cells: np.ndarray = _update_cells_fracture_grid(g_h, g_l, faces_h)

        # Step 6: Split g_h along faces_h
        _split_fracture_extension(gb,
                                  g_h,
                                  g_l,
                                  faces_h,
                                  unique_node_ind_h,
                                  new_cells,
                                  non_planar=True)

        # Store information on which faces and cells have just been added.
        # Note that we only keep track of the faces and cells from the last
        # propagation call!
        new_faces_l = np.arange(g_l.num_faces - n_new_faces, g_l.num_faces)
        new_faces_h = g_h.frac_pairs[1, np.isin(g_h.frac_pairs[0], faces_h)]

        # Sanity check on the grid; most likely something will have gone wrong
        # long before if there is a problem.
        assert np.all(new_faces_h >= n_old_faces_h)
        if not np.min(new_cells) >= n_old_cells_l:
            raise ValueError(
                "New cells are assumed to be appended to cell array")
        if not np.min(new_faces_l) >= n_old_faces_l:
            raise ValueError(
                "New faces are assumed to be appended to face array")

        # Update the geometry
        _update_geometry(g_h, g_l, new_cells, n_old_cells_l, n_old_faces_l)

        # Finally some bookkeeping that can become useful in a larger-scale simulation.

        # Mark both grids for a partial update
        d_h["partial_update"] = True
        d_l["partial_update"] = True

        # Append arrays of new faces (g_l, g_h) and cells (g_l)
        d_h["new_faces"] = np.append(d_h["new_faces"], new_faces_h)
        d_l["new_cells"] = np.append(d_l["new_cells"], new_cells)
        d_l["new_faces"] = np.append(d_l["new_faces"], new_faces_l)

        # Create mappings between the old and and faces and cells in g_l
        arr = np.arange(n_old_faces_l)
        face_map_l = sps.coo_matrix(
            (np.ones(n_old_faces_l, dtype=np.int), (arr, arr)),
            shape=(g_l.num_faces, n_old_faces_l),
        ).tocsr()
        arr = np.arange(n_old_cells_l)
        cell_map_l = sps.coo_matrix(
            (np.ones(n_old_cells_l, dtype=np.int), (arr, arr)),
            shape=(g_l.num_cells, n_old_cells_l),
        ).tocsr()

        # These can be stored directly - there should be no more changes for g_l
        d_l["face_index_map"] = face_map_l
        d_l["cell_index_map"] = cell_map_l

        # For g_h we construct the map of faces for the splitting of this g_l
        # and append it to the list of face_maps

        # The size of the next map should be compatible with the number of faces in
        # the previous map.
        nfh = face_map_h[-1].shape[0]
        arr = np.arange(nfh)
        face_map_h.append(
            sps.coo_matrix(
                (np.ones(nfh, dtype=np.int), (arr, arr)),
                shape=(g_h.num_faces, nfh),
            ).tocsr())

        # Append default tags for the new nodes. Both high and low-dimensional grid
        _append_node_tags(g_l, g_l.num_nodes - n_old_nodes_l)
        _append_node_tags(g_h, g_h.num_nodes - n_old_nodes_h)

    # The standard node tags are updated from the face tags, which are updated on the
    # fly in the above loop.
    node_tags = ["domain_boundary", "tip", "fracture"]
    for tag in node_tags:
        # The node tag is set to true if at least one neighboring face is tagged
        pp.utils.tags.add_node_tags_from_face_tags(gb, tag)
    # Done with all splitting.

    # Compose the mapping of faces for g_l
    fm = face_map_h[0]
    for m in face_map_h[1:]:
        fm = m * fm
    d_h["face_index_map"] = fm
    # Also make a cell-map, this is a 1-1 mapping in this case
    d_h["cell_index_map"] = sps.identity(g_h.num_cells)

    d_h["split_faces"] = np.array(split_faces, dtype=int)

    ##
    # Second main step of propagation: Update mortar grid.

    # When all faces have been split, we can update the mortar grids
    for e, d_e in gb.edges_of_node(g_h):
        _, g_l = e
        d_l = gb.node_props(g_l)
        _update_mortar_grid(g_h, g_l, d_e, d_l["new_cells"], d_h["new_faces"])

        # Mapping of cell indices on the mortar grid is composed by the corresponding
        # map for g_l.
        cell_map = sps.kron(sps.identity(2), d_l["cell_index_map"]).tocsr()
        d_e["cell_index_map"] = cell_map

        # Also update projection operators
        pp.contact_conditions.set_projections(gb, [e])