Beispiel #1
0
    def test_one_half_space(self):

        n = np.array([[-1], [0], [0]])
        x0 = np.array([[0], [0], [0]])
        pts = np.array([[1, -1], [0, 0], [0, 0]])
        out = half_space.half_space_int(n, x0, pts)
        self.assertTrue(np.all(out == np.array([True, False])))
Beispiel #2
0
def update_cell_connectivity(g: pp.Grid, face_id: np.ndarray,
                             normal: np.ndarray, x0: np.ndarray) -> int:
    """
    After the faces in a grid are duplicated, we update the cell connectivity
    list. Cells on the right side of the fracture do not change, but the cells
    on the left side are attached to the face duplicates. We assume that all
    faces that have been duplicated lie in the same plane. This plane is
    described by a normal and a point, x0. We attach cell on the left side of
    the plane to the duplicate of face_id. The cell on the right side is
    attached to the face frac_id.

    Parameters:
    ----------
    g         - The grid for wich the cell_face mapping is uppdated
    frac_id   - Indices of the faces that have been duplicated
    normal    - Normal of faces that have been duplicated. Note that we assume
                that all faces have the same normal
    x0        - A point in the plane where the faces lie

    Returns:
    ----------
    int: Flag that informs on what action has been taken. 0 means g.cell_faces has been
        split. -1 means the fracture was on the boundary, and no action taken.

    Raises:
    ----------
    ValueError: If the fracture is not planar

    """

    # We find the cells attached to the tagged faces.
    g.cell_faces = g.cell_faces.tocsr()
    cell_frac = g.cell_faces[face_id, :]
    cell_face_id = np.argwhere(cell_frac)

    # We devide the cells into the cells on the right side of the fracture
    # and cells on the left side of the fracture.
    left_cell = half_space_int(normal, x0, g.cell_centers[:, cell_face_id[:,
                                                                          1]])

    if np.all(left_cell) or not np.any(left_cell):
        # Fracture is on boundary of domain. There is nothing to do.
        # Remove the extra faces. We have not yet updated cell_faces,
        # so we should not delete anything from this matrix.
        rem = np.arange(g.cell_faces.shape[0], g.num_faces)
        remove_faces(g, rem, rem_cell_faces=False)
        return -1

    # Assume that fracture is either on boundary (above case) or completely
    # innside domain. Check that each face added two cells:
    if sum(left_cell) * 2 != left_cell.size:
        raise ValueError("Fractures must either be"
                         "on boundary or completely innside domain")

    # We create a cell_faces mapping for the new faces. This will be added
    # on the end of the excisting cell_faces mapping. We have here assumed
    # that we do not add any mapping during the duplication of faces.
    col = cell_face_id[left_cell, 1]
    row = cell_face_id[left_cell, 0]
    data = np.ravel(g.cell_faces[np.ravel(face_id[row]), col])
    assert data.size == face_id.size
    cell_frac_left = sps.csr_matrix((data, (row, col)),
                                    (face_id.size, g.cell_faces.shape[1]))

    # We now update the cell_faces map of the faces on the right side of
    # the fracture. These faces should only be attached to the right cells.
    # We therefore remove their connection to the cells on the left side of
    # the fracture.
    col = cell_face_id[~left_cell, 1]
    row = cell_face_id[~left_cell, 0]
    data = np.ravel(g.cell_faces[np.ravel(face_id[row]), col])
    cell_frac_right = sps.csr_matrix((data, (row, col)),
                                     (face_id.size, g.cell_faces.shape[1]))

    assert g.cell_faces.getformat() == "csr"

    sparse_mat.merge_matrices(g.cell_faces, cell_frac_right, face_id)

    # And then we add the new left-faces to the cell_face map. We do not
    # change the sign of the matrix since we did not flip the normals.
    # This means that the normals of right and left cells point in the same
    # direction, but their cell_faces values have oposite signs.
    sparse_mat.stack_mat(g.cell_faces, cell_frac_left)
    g.cell_faces = g.cell_faces.tocsc()

    return 0
Beispiel #3
0
def cart_grid_3d(fracs, nx, physdims=None):
    """
    Create grids for a domain with possibly intersecting fractures in 3d.

    Based on rectangles describing the individual fractures, the method
    constructs grids in 3d (the whole domain), 2d (one for each individual
    fracture), 1d (along fracture intersections), and 0d (meeting between
    intersections).

    Parameters
    ----------
    fracs (list of np.ndarray, each 3x4): Vertexes in the rectangle for each
        fracture. The vertices must be sorted and aligned to the axis.
        The fractures will snap to the closest grid faces.
    nx (np.ndarray): Number of cells in each direction. Should be 3D.
    physdims (np.ndarray): Physical dimensions in each direction.
        Defaults to same as nx, that is, cells of unit size.

    Returns
    -------
    list (length 4): For each dimension (3 -> 0), a list of all grids in
        that dimension.

    Examples
    --------
    frac1 = np.array([[1,1,4,4], [1,4,4,1], [2,2,2,2]])
    frac2 = np.array([[2,2,2,2], [1,1,4,4], [1,4,4,1]])
    fracs = [frac1, frac2]
    gb = cart_grid_3d(fracs, [5,5,5])
    """

    nx = np.asarray(nx)
    if physdims is None:
        physdims = nx
    elif np.asarray(physdims).size != nx.size:
        raise ValueError("Physical dimension must equal grid dimension")
    else:
        physdims = np.asarray(physdims)

    # We create a 3D cartesian grid. The global node mapping is trivial.
    g_3d = structured.CartGrid(nx, physdims=physdims)
    g_3d.global_point_ind = np.arange(g_3d.num_nodes)
    g_3d.compute_geometry()
    g_2d = []
    g_1d = []
    g_0d = []
    # We set the tolerance for finding points in a plane. This can be any
    # small number, that is smaller than .25 of the cell sizes.
    tol = 0.1 * physdims / nx

    # Create 2D grids
    for fi, f in enumerate(fracs):
        assert np.all(f.shape == (3, 4)), "fractures must have shape [3,4]"
        is_xy_frac = np.allclose(f[2, 0], f[2])
        is_xz_frac = np.allclose(f[1, 0], f[1])
        is_yz_frac = np.allclose(f[0, 0], f[0])
        assert (is_xy_frac + is_xz_frac +
                is_yz_frac == 1), "Fracture must align to x-, y- or z-axis"
        # snap to grid
        f_s = (np.round(f * nx[:, np.newaxis] / physdims[:, np.newaxis]) *
               physdims[:, np.newaxis] / nx[:, np.newaxis])
        if is_xy_frac:
            flat_dim = [2]
            active_dim = [0, 1]
        elif is_xz_frac:
            flat_dim = [1]
            active_dim = [0, 2]
        else:
            flat_dim = [0]
            active_dim = [1, 2]
        # construct normal vectors. If the rectangle is ordered
        # clockwise we need to flip the normals so they point
        # outwards.
        sign = 2 * pp.geometry_property_checks.is_ccw_polygon(
            f_s[active_dim]) - 1
        tangent = f_s.take(np.arange(f_s.shape[1]) + 1, axis=1,
                           mode="wrap") - f_s
        normal = tangent
        normal[active_dim] = tangent[active_dim[1::-1]]
        normal[active_dim[1]] = -normal[active_dim[1]]
        normal = sign * normal
        # We find all the faces inside the convex hull defined by the
        # rectangle. To find the faces on the fracture plane, we remove any
        # faces that are further than tol from the snapped fracture plane.
        in_hull = half_space.half_space_int(normal, f_s, g_3d.face_centers)
        f_tag = np.logical_and(
            in_hull,
            np.logical_and(
                f_s[flat_dim, 0] - tol[flat_dim] <=
                g_3d.face_centers[flat_dim],
                g_3d.face_centers[flat_dim] < f_s[flat_dim, 0] + tol[flat_dim],
            ),
        )
        f_tag = f_tag.ravel()
        nodes = sps.find(g_3d.face_nodes[:, f_tag])[0]
        nodes = np.unique(nodes)
        loc_coord = g_3d.nodes[:, nodes]
        g = _create_embedded_2d_grid(loc_coord, nodes)

        g.frac_num = fi
        g_2d.append(g)

    # Create 1D grids:
    # Here we make use of the network class to find the intersection of
    # fracture planes. We could maybe avoid this by doing something similar
    # as for the 2D-case, and count the number of faces belonging to each edge,
    # but we use the FractureNetwork class for now.
    frac_list = []
    for f in fracs:
        frac_list.append(fractures.Fracture(f))
    # Combine the fractures into a network
    network = pp.FractureNetwork3d(frac_list)
    # Impose domain boundary. For the moment, the network should be immersed in
    # the domain, or else gmsh will complain.
    box = {
        "xmin": 0,
        "ymin": 0,
        "zmin": 0,
        "xmax": physdims[0],
        "ymax": physdims[1],
        "zmax": physdims[2],
    }
    network.impose_external_boundary(box)

    # Find intersections and split them.
    network.find_intersections()
    network.split_intersections()

    # Extract geometrical network information.
    pts = network.decomposition["points"]
    edges = network.decomposition["edges"]
    poly = network._poly_2_segment()
    # And tags identifying points and edges corresponding to normal
    # fractures, domain boundaries and subdomain boundaries. Only the
    # entities corresponding to normal fractures should actually be gridded.
    edge_tags, intersection_points = network._classify_edges(poly)
    const = constants.GmshConstants()
    auxiliary_points, edge_tags = network.on_domain_boundary(edges, edge_tags)
    bound_and_aux = np.array([const.DOMAIN_BOUNDARY_TAG, const.AUXILIARY_TAG])
    edges = np.vstack((edges, edge_tags))

    # Loop through the edges to make 1D grids. Ommit the auxiliary edges.
    for e in np.ravel(
            np.where(edges[2] == const.FRACTURE_INTERSECTION_LINE_TAG)):
        # We find the start and end point of each fracture intersection (1D
        # grid) and then the corresponding global node index.
        if np.isin(edge_tags[e], bound_and_aux):
            continue
        s_pt = pts[:, edges[0, e]]
        e_pt = pts[:, edges[1, e]]
        nodes = _find_nodes_on_line(g_3d, nx, s_pt, e_pt)
        loc_coord = g_3d.nodes[:, nodes]
        assert (loc_coord.shape[1] > 1), "1d grid in intersection should span\
            more than one node"

        g = mesh_2_grid.create_embedded_line_grid(loc_coord, nodes)
        g_1d.append(g)

    # Create 0D grids
    # Here we also use the intersection information from the FractureNetwork
    # class. No grids for auxiliary points.
    for p in intersection_points:
        if auxiliary_points[p]:
            continue
        node = np.argmin(pp.distances.point_pointset(pts[:, p], g_3d.nodes))
        assert np.allclose(g_3d.nodes[:, node], pts[:, p])
        g = point_grid.PointGrid(g_3d.nodes[:, node])
        g.global_point_ind = np.asarray(node)
        g_0d.append(g)

    grids = [[g_3d], g_2d, g_1d, g_0d]
    return grids
Beispiel #4
0
def update_cell_connectivity(g, face_id, normal, x0):
    """
    After the faces in a grid is duplicated, we update the cell connectivity list.
    Cells on the right side of the fracture does not change, but the cells
    on the left side are attached to the face duplicates. We assume that all
    faces that have been duplicated lie in the same plane. This plane is
    described by a normal and a point, x0. We attach cell on the left side of the
    plane to the duplicate of face_id. The cells on the right side is attached
    to the face frac_id

    Parameters:
    ----------
    g         - The grid for wich the cell_face mapping is uppdated
    frac_id   - Indices of the faces that have been duplicated
    normal    - Normal of faces that have been duplicated. Note that we assume
                that all faces have the same normal
    x0        - A point in the plane where the faces lie
    """

    # We find the cells attached to the tagged faces.
    cell_frac = g.cell_faces[face_id, :]
    cell_face_id = np.argwhere(cell_frac)

    # We devide the cells into the cells on the right side of the fracture
    # and cells on the left side of the fracture.
    left_cell = half_space_int(normal, x0, g.cell_centers[:, cell_face_id[:,
                                                                          1]])

    if np.all(left_cell) or not np.any(left_cell):
        # Fracture is on boundary of domain. There is nothing to do.
        # Remove the extra faces. We have not yet updated cell_faces,
        # so we should not delete anything from this matrix.
        rem = np.arange(g.cell_faces.shape[0], g.num_faces)
        remove_faces(g, rem, rem_cell_faces=False)
        return -1

    # Assume that fracture is either on boundary (above case) or completely
    # innside domain. Check that each face added two cells:
    assert sum(left_cell) * 2 == left_cell.size, 'Fractures must either be' \
        'on boundary or completely innside domain'

    # We create a cell_faces mapping for the new faces. This will be added
    # on the end of the excisting cell_faces mapping. We have here assumed
    # that we do not add any mapping during the duplication of faces.
    col = cell_face_id[left_cell, 1]
    row = cell_face_id[left_cell, 0]
    data = np.ravel(g.cell_faces[np.ravel(face_id[row]), col])
    assert data.size == face_id.size
    cell_frac_left = sps.csc_matrix((data, (row, col)),
                                    (face_id.size, g.cell_faces.shape[1]))

    # We now update the cell_faces map of the faces on the right side of
    # the fracture. These faces should only be attached to the right cells.
    # We therefore remove their connection to the cells on the left side of
    # the fracture.
    col = cell_face_id[~left_cell, 1]
    row = cell_face_id[~left_cell, 0]
    data = np.ravel(g.cell_faces[np.ravel(face_id[row]), col])
    cell_frac_right = sps.csc_matrix((data, (row, col)),
                                     (face_id.size, g.cell_faces.shape[1]))
    g.cell_faces[face_id, :] = cell_frac_right

    # And then we add the new left-faces to the cell_face map. We do not
    # change the sign of the matrix since we did not flip the normals.
    # This means that the normals of right and left cells point in the same
    # direction, but their cell_faces values have oposite signs.
    g.cell_faces = sps.vstack((g.cell_faces, cell_frac_left), format='csc')

    return 0
Beispiel #5
0
 def test_two_half_spaces(self):
     n = np.array([[-1, 0], [0, -1], [0, 0]])
     x0 = np.array([[0, 0], [0, 1], [0, 0]])
     pts = np.array([[1, -1, 1, 0], [2, 0, 2, 0], [0, 0, 0, 0]])
     out = half_space.half_space_int(n, x0, pts)
     self.assertTrue(np.all(out == np.array([True, False, True, False])))