Exemple #1
0
    def test_rt0_2d_triangle(self):

        dim = 2
        nodes = np.array([[0, 1, 0], [0, 0, 1], [0, 0, 0]])

        indices = [0, 1, 1, 2, 2, 0]
        indptr = [0, 2, 4, 6]
        face_nodes = sps.csc_matrix(([True] * 6, indices, indptr))
        cell_faces = sps.csc_matrix([[1], [1], [1]])
        name = "test"

        g = pp.Grid(dim, nodes, face_nodes, cell_faces, name)
        g.compute_geometry()

        kxx = np.ones(g.num_cells)
        perm = pp.SecondOrderTensor(kxx=kxx, kyy=kxx, kzz=1)

        bf = g.get_boundary_faces()
        bc = pp.BoundaryCondition(g, bf, bf.size * ["dir"])
        M = self._matrix(g, perm, bc)

        # Matrix computed with an already validated code
        M_known = np.matrix([
            [0.33333333, 0.0, -0.16666667, -1.0],
            [0.0, 0.16666667, 0.0, -1.0],
            [-0.16666667, 0.0, 0.33333333, -1.0],
            [-1.0, -1.0, -1.0, 0.0],
        ])

        self.assertTrue(np.allclose(M, M.T))
        self.assertTrue(np.allclose(M, M_known))
    def test_mvem_tetra(self):

        dim = 3
        nodes = np.array([[0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]])

        indices = [0, 2, 1, 1, 2, 3, 2, 0, 3, 3, 0, 1]
        indptr = [0, 3, 6, 9, 12]
        face_nodes = sps.csc_matrix(([True] * 12, indices, indptr))
        cell_faces = sps.csc_matrix([[1], [1], [1], [1]])
        name = "test"

        g = pp.Grid(dim, nodes, face_nodes, cell_faces, name)
        g.compute_geometry()

        kxx = np.ones(g.num_cells)
        perm = pp.SecondOrderTensor(kxx=kxx, kyy=kxx, kzz=kxx)

        bf = g.get_boundary_faces()
        bc = pp.BoundaryCondition(g, bf, bf.size * ["dir"])
        vect = np.vstack(
            (2 * g.cell_volumes, 3 * g.cell_volumes, np.zeros(g.num_cells))
        ).ravel(order="F")

        b = self._matrix(g, perm, bc, vect)
        b_known = np.array([0.41666667, 0.41666667, -0.25, -0.58333333, 0.0])

        self.assertTrue(np.allclose(b, b_known))
Exemple #3
0
    def test_rt0_tetra(self):

        dim = 3
        nodes = np.array([[0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]])

        indices = [0, 2, 1, 1, 2, 3, 2, 0, 3, 3, 0, 1]
        indptr = [0, 3, 6, 9, 12]
        face_nodes = sps.csc_matrix(([True] * 12, indices, indptr))
        cell_faces = sps.csc_matrix([[1], [1], [1], [1]])
        name = "test"

        g = pp.Grid(dim, nodes, face_nodes, cell_faces, name)
        g.compute_geometry()

        kxx = np.ones(g.num_cells)
        perm = pp.SecondOrderTensor(kxx=kxx, kyy=kxx, kzz=kxx)

        bf = g.get_boundary_faces()
        bc = pp.BoundaryCondition(g, bf, bf.size * ["dir"])
        M = self._matrix(g, perm, bc)
        M_known = np.matrix([
            [0.53333333, 0.03333333, -0.13333333, -0.13333333, -1.0],
            [0.03333333, 0.2, 0.03333333, 0.03333333, -1.0],
            [-0.13333333, 0.03333333, 0.53333333, -0.13333333, -1.0],
            [-0.13333333, 0.03333333, -0.13333333, 0.53333333, -1.0],
            [-1.0, -1.0, -1.0, -1.0, 0.0],
        ])

        self.assertTrue(np.allclose(M, M.T))
        self.assertTrue(np.allclose(M, M_known))
Exemple #4
0
    def grid_2d(self, pert_node=False, flip_normal=False):
        # pert_node pertubes one node in the grid. Leads to non-matching cells.
        # flip_normal flips one normal vector in 2d grid adjacent to the fracture.
        #   Tests that there is no assumptions on direction of fluxes in the
        #   mortar coupling.
        nodes = np.array([
            [0, 0, 0],
            [1, 0, 0],
            [1, 0.5, 0],
            [0.5, 0.5, 0],
            [0, 0.5, 0],
            [0, 0.5, 0],
            [0.5, 0.5, 0],
            [1, 0.5, 0],
            [1, 1, 0],
            [0, 1, 0],
        ]).T
        if pert_node:
            nodes[0, 3] = 0.75

        fn = np.array([
            [0, 1],
            [1, 2],
            [2, 3],
            [3, 4],
            [4, 0],
            [0, 3],
            [3, 1],
            [5, 6],
            [6, 7],
            [7, 8],
            [8, 9],
            [9, 5],
            [9, 6],
            [6, 8],
        ]).T
        cf = np.array([[3, 4, 5], [0, 6, 5], [1, 2, 6], [7, 12, 11],
                       [12, 13, 10], [8, 9, 13]]).T
        cols = np.tile(np.arange(fn.shape[1]), (fn.shape[0], 1)).ravel("F")
        face_nodes = sps.csc_matrix(
            (np.ones_like(cols), (fn.ravel("F"), cols)))

        cols = np.tile(np.arange(cf.shape[1]), (cf.shape[0], 1)).ravel("F")
        data = np.array(
            [1, 1, 1, 1, -1, -1, 1, 1, 1, 1, -1, 1, 1, 1, -1, 1, 1, -1])

        cell_faces = sps.csc_matrix((data, (cf.ravel("F"), cols)))

        g = pp.Grid(2, nodes, face_nodes, cell_faces, "TriangleGrid")
        g.compute_geometry()
        g.tags["fracture_faces"][[2, 3, 7, 8]] = 1

        if flip_normal:
            g.face_normals[:, [2]] *= -1
            g.cell_faces[2, 2] *= -1
        g.global_point_ind = np.arange(nodes.shape[1])

        return g
Exemple #5
0
    def test_two_cells(self):
        # 2d grid is a single triangle grid with nodes ordered in the counter clockwise
        # direction

        fn_2d = sps.csc_matrix(
            np.array(
                [[1, 0, 0, 1, 0], [1, 1, 0, 0, 1], [0, 1, 1, 0, 0], [0, 0, 1, 1, 1]]
            )
        )

        cf_2d = sps.csc_matrix(np.array([[1, 0], [0, 1], [0, 1], [1, 0], [-1, 1]]))

        nodes_2d = np.array([[0, 1, 1, 0], [0, 0, 1, 1], [0, 0, 0, 0]])

        g_2d = pp.Grid(2, nodes_2d, fn_2d, cf_2d, "tmp")

        z = np.array([0, 1])
        nz = z.size

        g_3d, cell_map, face_map = pp.grid_extrusion._extrude_2d(g_2d, z)

        self.assertTrue(g_3d.num_nodes == 8)
        self.assertTrue(g_3d.num_cells == 2)
        self.assertTrue(g_3d.num_faces == 9)

        nodes = np.tile(nodes_2d, nz)
        nodes[2, :4] = z[0]
        nodes[2, 4:] = z[1]
        self.assertTrue(np.allclose(g_3d.nodes, nodes))

        fn_3d = np.array(
            [
                [1, 1, 0, 0, 1, 1, 0, 0],
                [0, 1, 1, 0, 0, 1, 1, 0],
                [0, 0, 1, 1, 0, 0, 1, 1],  # Done with first vertical layer
                [1, 0, 0, 1, 1, 0, 0, 1],
                [0, 1, 0, 1, 0, 1, 0, 1],
                [1, 1, 0, 1, 0, 0, 0, 0],  # Second vertical layer
                [0, 1, 1, 1, 0, 0, 0, 0],
                [0, 0, 0, 0, 1, 1, 0, 1],
                [0, 0, 0, 0, 0, 1, 1, 1],
            ]
        ).T
        self.assertTrue(np.allclose(g_3d.face_nodes.toarray(), fn_3d))

        cf_3d = np.array(
            [[1, 0], [0, 1], [0, 1], [1, 0], [-1, 1], [-1, 0], [0, -1], [1, 0], [0, 1]]
        )
        self.assertTrue(np.allclose(g_3d.cell_faces.toarray(), cf_3d))

        self.assertTrue(check_cell_map(cell_map, g_2d, nz - 1))
        self.assertTrue(check_face_map(face_map, g_2d, nz - 1))
Exemple #6
0
    def test_merge_grids_split(self):
        g1 = pp.TensorGrid(np.linspace(0, 2, 2))
        g2 = pp.TensorGrid(np.linspace(2, 4, 2))
        g_nodes = np.hstack((g1.nodes, g2.nodes))
        g_face_nodes = sps.block_diag((g1.face_nodes, g2.face_nodes), "csc")
        g_cell_faces = sps.block_diag((g1.cell_faces, g2.cell_faces), "csc")
        g = pp.Grid(1, g_nodes, g_face_nodes, g_cell_faces, "pp.TensorGrid")

        h1 = pp.TensorGrid(np.linspace(0, 2, 3))
        h2 = pp.TensorGrid(np.linspace(2, 4, 3))
        h_nodes = np.hstack((h1.nodes, h2.nodes))
        h_face_nodes = sps.block_diag((h1.face_nodes, h2.face_nodes), "csc")
        h_cell_faces = sps.block_diag((h1.cell_faces, h2.cell_faces), "csc")
        h = pp.Grid(1, h_nodes, h_face_nodes, h_cell_faces, "pp.TensorGrid")

        g.compute_geometry()
        h.compute_geometry()
        weights, new, old = pp.match_grids.match_1d(g, h, tol=1e-4)

        # Weights give mappings from h to g. All cells are split in two
        self.assertTrue(np.allclose(weights, np.array([1.0, 1.0, 1.0, 1.0])))
        self.assertTrue(np.allclose(new, np.array([0, 0, 1, 1])))
        self.assertTrue(np.allclose(old, np.array([0, 1, 2, 3])))
def import_grid(folder, fname, dim, **kwargs):

    index_shift = kwargs.get("index_shift", 1)

    # load the nodes
    fnodes_root = kwargs.get("nodes", "points")
    fnodes = folder + "/" + fnodes_root + fname + ".txt"
    nodes = np.loadtxt(fnodes, dtype=np.float)
    nodes = np.atleast_2d(nodes).T
    nodes = np.vstack([nodes[1:], np.zeros(nodes.shape[1])])

    # load the face to node map
    fface_nodes_root = kwargs.get("face_nodes", "face2node")
    fface_nodes = folder + "/" + fface_nodes_root + fname + ".txt"
    face_nodes = np.loadtxt(fface_nodes, dtype=np.int).T

    col = face_nodes[0] - index_shift
    row = face_nodes[1] - index_shift
    data = face_nodes[2].astype(np.bool)
    face_nodes = sps.csc_matrix((data, (row, col)))

    # load the cell to face map
    fcell_faces_root = kwargs.get("cell_faces", "cell2face")
    fcell_faces = folder + "/" + fcell_faces_root + fname + ".txt"
    cell_faces = np.loadtxt(fcell_faces, dtype=np.int).T

    col = cell_faces[0] - index_shift
    row = cell_faces[1] - index_shift
    data = cell_faces[2]
    cell_faces = sps.csc_matrix((data, (row, col)))

    # it's not really triangular grid but it's useful somewhere
    name = "TriangleGrid" if dim == 2 else "TensorGrid"
    g = pp.Grid(dim, nodes, face_nodes, cell_faces, name)
    g.name.append(fname)
    g.compute_geometry()

    # load the point to local to global map
    fglobal_root = kwargs.get("global", "glob2loc")
    fglobal = folder + "/" + fglobal_root + fname + ".txt"
    if glob.glob(fglobal):
        g.global_point_ind = np.loadtxt(fglobal, dtype=np.int).T[-1]
    else:
        g.global_point_ind = np.arange(g.num_nodes) + 1
        warnings.warn(
            "Attention default global to local mapping used for grid\n" +
            str(g))

    return g
def unite_grids(gs):
    # it's simple index transformation
    gs = np.atleast_1d(gs)

    # collect the nodes
    nodes = np.hstack((g.nodes for g in gs))

    # collect the face_nodes map
    face_nodes = sps.block_diag([g.face_nodes for g in gs], "csc")

    # collect the cell_faces map
    cell_faces = sps.block_diag([g.cell_faces for g in gs], "csc")

    # dimension and name
    dim = gs[0].dim
    name = "United grid"

    # create the grid and give it back
    g = pp.Grid(dim, nodes, face_nodes, cell_faces, name)
    g.compute_geometry()
    return g
Exemple #9
0
    def test_rt0_2d_triangle(self):

        dim = 2
        nodes = np.array([[0, 1, 0], [0, 0, 1], [0, 0, 0]])

        indices = [0, 1, 1, 2, 2, 0]
        indptr = [0, 2, 4, 6]
        face_nodes = sps.csc_matrix(([True] * 6, indices, indptr))
        cell_faces = sps.csc_matrix([[1], [1], [1]])
        name = "test"

        g = pp.Grid(dim, nodes, face_nodes, cell_faces, name)
        g.compute_geometry()

        kxx = np.ones(g.num_cells)
        perm = pp.SecondOrderTensor(3, kxx=kxx, kyy=kxx, kzz=1)

        bf = g.get_boundary_faces()
        bc = pp.BoundaryCondition(g, bf, bf.size * ["dir"])
        solver = pp.RT0(physics="flow")

        param = pp.Parameters(g)
        param.set_tensor(solver, perm)
        param.set_bc(solver, bc)
        M = solver.matrix(g, {"param": param}).todense()

        # Matrix computed with an already validated code
        M_known = np.matrix(
            [
                [0.33333333, 0., -0.16666667, -1.],
                [0., 0.16666667, 0., -1.],
                [-0.16666667, 0., 0.33333333, -1.],
                [-1., -1., -1., 0.],
            ]
        )

        self.assertTrue(np.allclose(M, M.T))
        self.assertTrue(np.allclose(M, M_known))
Exemple #10
0
def __extract_cells_from_faces_2d(g, f):
    # Local cell-face and face-node maps.
    cell_nodes, unique_nodes = __extract_submatrix(g.face_nodes, f)

    cell_faces_indices = cell_nodes.indices
    data = -1 * cell_nodes.data
    _, ix = np.unique(cell_faces_indices, return_index=True)
    data[ix] *= -1

    cell_faces = sps.csc_matrix((data, cell_faces_indices, cell_nodes.indptr))

    num_faces = np.shape(cell_faces)[0]
    num_nodes = np.shape(cell_nodes)[0]
    num_nodes_per_face = np.ones(num_nodes)

    face_node_ind = pp.utils.matrix_compression.rldecode(
        np.arange(num_faces), num_nodes_per_face)

    face_nodes = sps.coo_matrix(
        (np.ones(num_nodes,
                 dtype=bool), (np.arange(num_faces), face_node_ind))).tocsc()

    # Append information on subgrid extraction to the new grid's history
    name = list(g.name)
    name.append("Extract subgrid")

    h = pp.Grid(g.dim - 1, g.nodes[:, unique_nodes], face_nodes, cell_faces,
                name)

    h.compute_geometry()

    assert np.all(np.isclose(g.face_areas[f], h.cell_volumes))
    h.cell_volumes = g.face_areas[f]
    assert np.all(np.isclose(g.face_centers[:, f], h.cell_centers))
    h.cell_centers = g.face_centers[:, f]

    h.parent_face_ind = f
    return h, f, unique_nodes
Exemple #11
0
    def test_rt0_tetra(self):

        dim = 3
        nodes = np.array([[0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]])

        indices = [0, 2, 1, 1, 2, 3, 2, 0, 3, 3, 0, 1]
        indptr = [0, 3, 6, 9, 12]
        face_nodes = sps.csc_matrix(([True] * 12, indices, indptr))
        cell_faces = sps.csc_matrix([[1], [1], [1], [1]])
        name = "test"

        g = pp.Grid(dim, nodes, face_nodes, cell_faces, name)
        g.compute_geometry()

        kxx = np.ones(g.num_cells)
        perm = pp.SecondOrderTensor(3, kxx=kxx, kyy=kxx, kzz=kxx)

        bf = g.get_boundary_faces()
        bc = pp.BoundaryCondition(g, bf, bf.size * ["dir"])
        solver = pp.RT0(physics="flow")

        param = pp.Parameters(g)
        param.set_tensor(solver, perm)
        param.set_bc(solver, bc)
        M = solver.matrix(g, {"param": param}).todense()
        M_known = np.matrix(
            [
                [0.53333333, 0.03333333, -0.13333333, -0.13333333, -1.],
                [0.03333333, 0.2, 0.03333333, 0.03333333, -1.],
                [-0.13333333, 0.03333333, 0.53333333, -0.13333333, -1.],
                [-0.13333333, 0.03333333, -0.13333333, 0.53333333, -1.],
                [-1., -1., -1., -1., 0.],
            ]
        )

        self.assertTrue(np.allclose(M, M.T))
        self.assertTrue(np.allclose(M, M_known))
Exemple #12
0
def extract_subgrid(g, c, sort=True, faces=False, is_planar=True):
    """
    Extract a subgrid based on cell/face indices.

    For simplicity the cell/face indices will be sorted before the subgrid is
    extracted.

    If the parent grid has geometry attributes (cell centers etc.) these are
    copied to the child.

    No checks are done on whether the cells/faces form a connected area. The
    method should work in theory for non-connected cells, the user will then
    have to decide what to do with the resulting grid. This option has however
    not been tested.

    Parameters:
        g (core.grids.Grid): Grid object, parent
        c (np.array, dtype=int): Indices of cells to be extracted
        sort=True (bool): If true c is sorted
        faces=False (bool): If true c are intrepetred as faces, and the
                            exptracted grid will be a lower dimensional grid
                            defined by the these faces
        is_planar: (optional) defaults to True. Only used when extracting faces from a
           3d grid. If True the faces f must be planar. Set to False to use this
           function for extracting a non-planar 2D grid, but use at own risk.

    Returns:
        Grid: Extracted subgrid. Will share (note, *not* copy)
            geometric fileds with the parent grid. Also has an additional
            field parent_cell_ind giving correspondance between parent and
            child cells.
        np.ndarray, dtype=int: Index of the extracted faces, ordered so that
            element i is the global index of face i in the subgrid.
        np.ndarray, dtype=int: Index of the extracted nodes, ordered so that
            element i is the global index of node i in the subgrid.

    Raises:
        IndexError if index is as boolean and do not match the array size.

    """
    if np.asarray(c).dtype == "bool":
        # convert to indices.
        # First check for dimension
        if faces and c.size != g.num_faces:
            raise IndexError("boolean index did not match number of faces")
        elif not faces and c.size != g.num_cells:
            raise IndexError("boolean index did not match number of cells")
        c = np.where(c)[0]

    if sort:
        c = np.sort(np.atleast_1d(c))

    if faces:
        return __extract_cells_from_faces(g, c, is_planar)
    # Local cell-face and face-node maps.
    cf_sub, unique_faces = __extract_submatrix(g.cell_faces, c)
    fn_sub, unique_nodes = __extract_submatrix(g.face_nodes, unique_faces)

    # Append information on subgrid extraction to the new grid's history
    name = list(g.name)
    name.append("Extract subgrid")

    # Construct new grid.
    h = pp.Grid(g.dim, g.nodes[:, unique_nodes], fn_sub, cf_sub, name)

    # Copy geometric information if any
    if hasattr(g, "cell_centers"):
        h.cell_centers = g.cell_centers[:, c]
    if hasattr(g, "cell_volumes"):
        h.cell_volumes = g.cell_volumes[c]
    if hasattr(g, "face_centers"):
        h.face_centers = g.face_centers[:, unique_faces]
    if hasattr(g, "face_normals"):
        h.face_normals = g.face_normals[:, unique_faces]
    if hasattr(g, "face_areas"):
        h.face_areas = g.face_areas[unique_faces]

    h.parent_cell_ind = c

    return h, unique_faces, unique_nodes
Exemple #13
0
def simplexify(gb, data=[]):
    "make the higher dimensional grid make of simplices by splitting the cells and return the map"
    new_gb = gb.copy()
    grids = gb.grids_of_dimension(gb.dim_max())

    for g in grids:
        g_cell_nodes = g.cell_nodes()
        face_nodes_indices = np.empty(0, dtype=np.int)  #g.num_faces)
        cell_faces_indices = np.empty(0, dtype=np.int)
        cell_faces_data = np.empty(0, dtype=np.int)
        # projection operator
        proj_I = np.empty(0, dtype=np.int)
        proj_J = np.empty(0, dtype=np.int)

        num_faces = g.num_faces
        num_cells = 0

        for c in np.arange(g.num_cells):
            # cell nodes
            c_nodes_pos = slice(g_cell_nodes.indptr[c],
                                g_cell_nodes.indptr[c + 1])
            c_nodes = g_cell_nodes.indices[c_nodes_pos]

            # cell faces
            c_faces_pos = slice(g.cell_faces.indptr[c],
                                g.cell_faces.indptr[c + 1])
            c_faces = g.cell_faces.indices[c_faces_pos]
            c_faces_data = g.cell_faces.data[c_faces_pos]

            out = np.ones((4, c_faces.size), dtype=np.int)
            out[0] = np.arange(c_faces.size) + num_faces
            out[1] = c_nodes
            out[2] = c + g.num_nodes

            for f, d in zip(c_faces, c_faces_data):
                f_nodes_pos = slice(g.face_nodes.indptr[f],
                                    g.face_nodes.indptr[f + 1])
                f_nodes = g.face_nodes.indices[f_nodes_pos]

                # check which new faces we need to consider
                mask = np.in1d(out[1], f_nodes, assume_unique=True)
                c_indices = np.hstack((out[0, mask], f))
                c_data = np.hstack((out[3, mask], d))
                out[3, mask] *= -1

                # save the data
                cell_faces_indices = np.hstack((cell_faces_indices, c_indices))
                cell_faces_data = np.hstack((cell_faces_data, c_data))

            proj_I = np.hstack((proj_I, num_cells + np.arange(c_faces.size)))
            proj_J = np.hstack(
                (proj_J, c * np.ones(c_faces.size, dtype=np.int)))

            num_cells += c_faces.size
            num_faces += c_faces.size
            face_nodes_indices = np.hstack(
                (face_nodes_indices, out[1:-1].ravel(order='F')))

        face_nodes_indices = np.hstack(
            (g.face_nodes.indices, face_nodes_indices))
        face_nodes_indptr = np.arange(num_faces + 1, dtype=np.int) * 2
        face_nodes_data = np.ones(face_nodes_indices.size, dtype=np.bool)
        face_nodes = sps.csc_matrix(
            (face_nodes_data, face_nodes_indices, face_nodes_indptr))

        cell_faces_indptr = np.arange(num_cells + 1, dtype=np.int) * 3
        cell_faces = sps.csc_matrix(
            (cell_faces_data, cell_faces_indices, cell_faces_indptr))

        nodes = np.hstack((g.nodes, g.cell_centers))

        new_g = pp.Grid(g.dim, nodes, face_nodes, cell_faces, g.name)
        new_g.compute_geometry()
        new_gb.update_nodes({g: new_g})

        # update the data
        proj = sps.csr_matrix((np.ones(proj_J.size), (proj_I, proj_J)))
        for d in data:
            if gb._nodes[g][pp.STATE].get(d, None) is not None:
                old_values = gb._nodes[g][pp.STATE][d]
                if old_values.ndim > 1:
                    new_gb._nodes[new_g][pp.STATE][d] = np.zeros(
                        (old_values.shape[0], proj.shape[0]))
                    for dim in np.arange(old_values.ndim):
                        new_gb._nodes[new_g][pp.STATE][d][
                            dim, :] = proj * old_values[dim, :]
                else:
                    new_gb._nodes[new_g][pp.STATE][d] = proj * old_values
            else:
                old_values = gb._nodes[g][d]
                new_gb._nodes[new_g][d] = proj * old_values

    return new_gb
Exemple #14
0
    def test_two_cells_one_quad(self):
        # 2d grid is a single triangle grid with nodes ordered in the counter clockwise
        # direction

        fn_2d = sps.csc_matrix(
            np.array(
                [
                    [1, 0, 0, 0, 1, 0],
                    [1, 1, 0, 0, 0, 1],
                    [0, 1, 1, 0, 0, 0],
                    [0, 0, 1, 1, 0, 0],
                    [0, 0, 0, 1, 1, 1],
                ]
            )
        )

        cf_2d = sps.csc_matrix(
            np.array([[1, 0], [0, -1], [0, 1], [0, 1], [-1, 0], [1, -1]])
        )

        nodes_2d = np.array([[0, 1, 2, 1, 0], [0, 0, 1, 2, 1], [0, 0, 0, 0, 0]])

        g_2d = pp.Grid(2, nodes_2d, fn_2d, cf_2d, "tmp")

        z = np.array([0, 1])
        nz = z.size

        g_3d, cell_map, face_map = pp.grid_extrusion._extrude_2d(g_2d, z)

        self.assertTrue(g_3d.num_nodes == 10)
        self.assertTrue(g_3d.num_cells == 2)
        self.assertTrue(g_3d.num_faces == 10)

        nodes = np.tile(nodes_2d, nz)
        nodes[2, :5] = z[0]
        nodes[2, 5:] = z[1]
        self.assertTrue(np.allclose(g_3d.nodes, nodes))

        fn_ind = np.array(
            [
                0,
                1,
                6,
                5,
                1,
                2,
                7,
                6,
                2,
                3,
                8,
                7,
                3,
                4,
                9,
                8,
                4,
                0,
                5,
                9,
                1,
                4,
                9,
                6,
                0,
                1,
                4,
                1,
                2,
                3,
                4,
                5,
                6,
                9,
                6,
                7,
                8,
                9,
            ]
        )
        fn_ind_ptr = np.array([0, 4, 8, 12, 16, 20, 24, 27, 31, 34, fn_ind.size])

        fn_3d = sps.csc_matrix(
            (np.ones(fn_ind.size), fn_ind, fn_ind_ptr), shape=(10, 10)
        )
        self.assertTrue(np.allclose(g_3d.face_nodes.toarray(), fn_3d.toarray()))

        cf_3d = np.array(
            [
                [1, 0],
                [0, -1],
                [0, 1],
                [0, 1],
                [-1, 0],
                [1, -1],
                [-1, 0],
                [0, -1],
                [1, 0],
                [0, 1],
            ]
        )
        self.assertTrue(np.allclose(g_3d.cell_faces.toarray(), cf_3d))

        self.assertTrue(check_cell_map(cell_map, g_2d, nz - 1))
        self.assertTrue(check_face_map(face_map, g_2d, nz - 1))
Exemple #15
0
    def grid_2d(self,
                flip_normals=False,
                pert_node=False,
                rotate_fracture=False):
        """ flip_normals: Some, but not all, faces in 2d grid on fracture
                surface are rotated. Divergence operator is modified accordingly.
                The solution should be invariant under this change.
            perturb_node: One node in 2d grid on the fracture surface is shifted.
                This breaks symmetry of the grid across the fracture.
                Should not be combined with rotate_fracture.
            rotate_fracture: Fracture is rotated from aligned with the y-axis
                to a slightly tilted position. Should not be combined with
                perturb_node
        """

        nodes = np.array([
            [0, 0, 0],  # 0
            [1, 0, 0],  # 1
            [1, 0.5, 0],  # 2
            [1, 1, 0],  # 3
            [0, 1, 0],  # 4
            [0, 0.5, 0],  # 5
            [0.75, 0.375, 0],  # 6
            [0.75, 0.75, 0],  # 7
            [0.25, 0.75, 0],  # 8
            [0.25, 0.25, 0],  # 9
            [0.5, 0.25, 0],  # 10
            [0.5, 0.5, 0],  # 11
            [0.5, 0.75, 0],  # 12
            [0.5, 0.5, 0],  # 13
        ]).T

        if pert_node:
            if rotate_fracture:
                raise ValueError("Incompatible options to grid construction")
            nodes[1, 13] = 0.6
        if rotate_fracture:
            nodes[0, 10] = 0.4
            nodes[0, 12] = 0.6

        fn = np.array(  # Faces always go from node of low to high index
            [
                [0, 1],  # 0
                [1, 2],  # 1
                [2, 3],  # 2
                [3, 4],  # 3
                [4, 5],  # 4
                [0, 5],  # 5
                [0, 9],  # 6
                [0, 10],  # 7
                [9, 10],  # 8
                [5, 9],  # 9
                [9, 13],  # 10
                [8, 13],  # 11
                [5, 8],  # 12
                [8, 9],  # 13
                [4, 8],  # 14
                [8, 12],  # 15
                [4, 12],  # 16
                [1, 10],  # 17
                [1, 6],  # 18
                [6, 10],  # 19
                [6, 11],  # 20
                [6, 7],  # 21
                [7, 11],  # 22
                [2, 6],  # 23
                [2, 7],  # 24
                [7, 12],  # 25
                [3, 7],  # 26
                [3, 12],  # 27
                [10, 13],  # 28
                [12, 13],  # 29
                [10, 11],  # 30
                [11, 12],  # 31
            ]).T
        cols = np.tile(np.arange(fn.shape[1]), (fn.shape[0], 1)).ravel("F")
        face_nodes = sps.csc_matrix(
            (np.ones_like(cols), (fn.ravel("F"), cols)))

        cf = np.array([
            [0, 17, 7],  # 0
            [6, 7, 8],  # 1
            [5, 6, 9],  # 2
            [8, 28, 10],  # 3
            [10, 11, 13],  # 4
            [9, 13, 12],  # 5
            [11, 29, 15],  # 6
            [4, 12, 14],  # 7
            [14, 15, 16],  # 8
            [3, 16, 27],  # 9
            [25, 31, 22],  # 10
            [25, 26, 27],  # 11
            [2, 26, 24],  # 12
            [21, 23, 24],  # 13
            [20, 21, 22],  # 14
            [19, 20, 30],  # 15
            [17, 18, 19],  # 16
            [1, 23, 18],  # 17
        ]).T
        cols = np.tile(np.arange(cf.shape[1]), (cf.shape[0], 1)).ravel("F")
        data = np.array([
            [1, 1, -1],  # 0
            [-1, 1, -1],  # 1
            [-1, 1, -1],  # 2
            [1, 1, -1],  # 3
            [1, -1, 1],  # 4
            [1, -1, -1],  # 5
            [1, -1, -1],  # 6
            [1, 1, -1],  # 7
            [1, 1, -1],  # 8
            [1, 1, -1],  # 9
            [1, -1, -1],  # 10
            [-1, -1, 1],  # 11
            [1, 1, -1],  # 12
            [-1, -1, 1],  # 13
            [-1, 1, 1],  # 14
            [-1, 1, -1],  # 15
            [-1, 1, 1],  # 16
            [1, 1, -1],  # 17
        ]).ravel()

        cell_faces = sps.csc_matrix((data, (cf.ravel("F"), cols)))
        g = pp.Grid(2, nodes, face_nodes, cell_faces, "TriangleGrid")
        g.compute_geometry()
        fracture_faces = [28, 29, 30, 31]
        g.tags["fracture_faces"][fracture_faces] = 1
        g.global_point_ind = np.arange(nodes.shape[1])

        if flip_normals:
            g.face_normals[:, fracture_faces] *= -1
            g.cell_faces[fracture_faces, [3, 6, 15, 10]] *= -1
            g.face_normals[:, 28] *= -1
            g.cell_faces[28, 3] *= -1

        return g
Exemple #16
0
    def test_single_cell_negative_z(self):
        # 2d grid is a single triangle grid with nodes ordered in the counter clockwise
        # direction

        fn_2d = sps.csc_matrix(np.array([[1, 0, 1], [1, 1, 0], [0, 1, 1]]))

        cf_2d = sps.csc_matrix(np.array([[-1], [1], [-1]]))

        nodes_2d = np.array([[0, 2, 1], [0, 0, 1], [0, 0, 0]])

        g_2d = pp.Grid(2, nodes_2d, fn_2d, cf_2d, "tmp")

        z = np.array([0, -1, -3])
        nz = z.size

        g_3d, cell_map, face_map = pp.grid_extrusion._extrude_2d(g_2d, z)

        self.assertTrue(g_3d.num_nodes == 9)
        self.assertTrue(g_3d.num_cells == 2)
        self.assertTrue(g_3d.num_faces == 9)

        nodes = np.tile(nodes_2d, nz)
        nodes[2, :3] = z[0]
        nodes[2, 3:6] = z[1]
        nodes[2, 6:] = z[2]
        self.assertTrue(np.allclose(g_3d.nodes, nodes))

        fn_3d = np.array(
            [
                [1, 1, 0, 1, 1, 0, 0, 0, 0],
                [0, 1, 1, 0, 1, 1, 0, 0, 0],
                [1, 0, 1, 1, 0, 1, 0, 0, 0],  # Done with first vertical layer
                [0, 0, 0, 1, 1, 0, 1, 1, 0],
                [0, 0, 0, 0, 1, 1, 0, 1, 1],
                [0, 0, 0, 1, 0, 1, 1, 0, 1],  # Second vertical layer
                [1, 1, 1, 0, 0, 0, 0, 0, 0],
                [0, 0, 0, 1, 1, 1, 0, 0, 0],
                [0, 0, 0, 0, 0, 0, 1, 1, 1],
            ]
        ).T
        self.assertTrue(np.allclose(g_3d.face_nodes.toarray(), fn_3d))

        cf_3d = np.array(
            [
                [-1, 0],
                [1, 0],
                [-1, 0],
                [0, -1],
                [0, 1],
                [0, -1],
                [-1, 0],
                [1, -1],
                [0, 1],
            ]
        )
        self.assertTrue(np.allclose(g_3d.cell_faces.toarray(), cf_3d))

        # Geometric quantities
        # Cell center
        cc = np.array([[1, 1], [1 / 3, 1 / 3], [-0.5, -2]])
        self.assertTrue(np.allclose(g_3d.cell_centers, cc))

        # Face centers
        fc = np.array(
            [
                [1, 0, -0.5],
                [1.5, 0.5, -0.5],
                [0.5, 0.5, -0.5],
                [1, 0, -2],
                [1.5, 0.5, -2],
                [0.5, 0.5, -2],
                [1, 1 / 3, 0],
                [1, 1 / 3, -1],
                [1, 1 / 3, -3],
            ]
        ).T
        self.assertTrue(np.allclose(g_3d.face_centers, fc))

        # Face areas
        fa = np.array(
            [2, np.sqrt(2), np.sqrt(2), 4, 2 * np.sqrt(2), 2 * np.sqrt(2), 1, 1, 1]
        )
        self.assertTrue(np.allclose(g_3d.face_areas, fa))

        # Normal vectors
        normals = np.array(
            [
                [0, 2, 0],
                [1, 1, 0],
                [1, -1, 0],
                [0, 4, 0],
                [2, 2, 0],
                [2, -2, 0],
                [0, 0, -1],
                [0, 0, -1],
                [0, 0, -1],
            ]
        ).T
        self.assertTrue(np.allclose(g_3d.face_normals, normals))

        self.assertTrue(check_cell_map(cell_map, g_2d, nz - 1))
        self.assertTrue(check_face_map(face_map, g_2d, nz - 1))
    def create_grid(self):
        """
        Domain is unit square. One fracture, centered on (0.5, 0.5), tilted
        according to self.angle.

        """

        angle = self.angle

        corners = np.array([[0, 1, 1, 0], [0, 0, 1, 1], [0, 0, 0, 0]])

        # The fracture points always have x coordinates 0.2 and 0.8
        # The y-coordinates are set so that the fracture forms the prescribed angle with
        # the x-axis
        frac_pt = np.array(
            [[0.2, 0.8],
             [0.5 - 0.3 * np.tan(angle), 0.5 + 0.3 * np.tan(angle)], [0, 0]])

        nodes = np.hstack((corners, frac_pt))

        rows = np.array([
            [0, 1],
            [1, 5],
            [5, 0],
            [1, 2],
            [2, 5],
            [2, 4],
            [2, 3],
            [3, 0],
            [3, 4],
            [4, 0],
            [4, 5],
            [4, 5],
        ]).ravel()
        cols = np.vstack((np.arange(12), np.arange(12))).ravel("F")
        data = np.ones_like(rows)

        fn_2d = sps.coo_matrix((data, (rows, cols)), shape=(6, 12)).tocsc()
        rows = np.array([[0, 1, 2], [1, 3, 4], [4, 5, 10], [5, 6, 8],
                         [7, 8, 9], [2, 11, 9]]).ravel()
        cols = np.tile(np.arange(6), (3, 1)).ravel("F")
        data = np.array(
            [-1, -1, -1, 1, -1, -1, 1, -1, -1, -1, 1, -1, 1, 1, -1, 1, 1, 1])
        cf_2d = sps.coo_matrix((data, (rows, cols)), shape=(12, 6)).tocsc()

        g_2d = pp.Grid(2, nodes, fn_2d, cf_2d, "mock_2d_grid")
        g_2d.compute_geometry()

        fn_1d = sps.csc_matrix(np.array([[1, 0], [0, 1]]))
        cf_1d = sps.csc_matrix(np.array([[-1], [1]]))

        g_1d = pp.Grid(1, frac_pt, fn_1d, cf_1d, "mock_1d_grid")
        g_1d.compute_geometry()

        gb = pp.GridBucket()
        gb.add_nodes([g_2d, g_1d])

        # Construct mortar grid
        side_grids = {
            mortar_grid.LEFT_SIDE: g_1d.copy(),
            mortar_grid.RIGHT_SIDE: g_1d.copy(),
        }

        data = np.array([1, 1])
        row = np.array([0, 0])
        col = np.array([10, 11])
        face_cells_mortar = sps.coo_matrix(
            (data, (row, col)),
            shape=(g_1d.num_cells, g_2d.num_faces)).tocsc()

        mg = pp.MortarGrid(1, side_grids, face_cells_mortar)

        edge = (g_2d, g_1d)
        gb.add_edge(edge, face_cells_mortar)
        d = gb.edge_props(edge)

        d["mortar_grid"] = mg

        self.gb = gb
        self._Nd = 2

        self.g1 = g_1d
        self.g2 = g_2d
        self.edge = edge
        self.mg = mg
Exemple #18
0
def __extract_cells_from_faces_3d(g, f, is_planar=True):
    """
    Extract a 2D grid from the faces of a 3D grid. One of the uses of this function
    is to obtain a 2D MortarGrid from the boundary of a 3D grid. The faces f
    is by default assumed to be planar, however, this is mainly because compute_geometry
    does not handle non-planar grids. compute_geometry is used to do a sanity check
    of the extracted grid. if is_planar is set to False, this function should handle
    non-planar grids, however, this has not been tested thouroghly, and it does not
    performe the geometric sanity checks.

    Parameters:
    ----------
    g: (Grid) A 3d grid
    f: (ndarray) faces of the 3d grid to by used as cells in the 2d grid
    is_planar: (optional) defaults to True: If False the faces f must be planar.
        Set to False to use this function for a non-planar 2D grid, but use at own risk

    Returns:
    -------
    (Grid) A 2d grid extracted from the 3d grid g and the faces f.

    Throws:
    ------
    ValueError: If the given faces is not planar and is_planar==True
    """
    # Local cell-face and face-node maps.
    cell_nodes, unique_nodes = __extract_submatrix(g.face_nodes, f)
    if is_planar and not pp.geometry_property_checks.points_are_planar(
            g.nodes[:, unique_nodes]):
        raise ValueError("The faces extracted from a 3D grid must be planar")
    num_cell_nodes = cell_nodes.nnz

    cell_node_ptr = cell_nodes.indptr
    num_nodes_per_cell = cell_node_ptr[1:] - cell_node_ptr[:-1]

    next_node = np.arange(num_cell_nodes) + 1
    next_node[cell_node_ptr[1:] - 1] = cell_node_ptr[:-1]
    face_start = cell_nodes.indices
    face_end = cell_nodes.indices[next_node]
    face_nodes_indices = np.vstack((face_start, face_end))
    face_nodes_sorted = np.sort(face_nodes_indices, axis=0)
    _, IA, IC = np.unique(face_nodes_sorted,
                          return_index=True,
                          return_inverse=True,
                          axis=1)

    face_nodes_indices = face_nodes_indices[:, IA].ravel("F")
    num_face_nodes = face_nodes_indices.size
    face_nodes_indptr = np.arange(0, num_face_nodes + 1, 2)
    face_nodes = sps.csc_matrix(
        (np.ones(num_face_nodes), face_nodes_indices, face_nodes_indptr))

    cell_idx = pp.utils.matrix_compression.rldecode(np.arange(num_face_nodes),
                                                    num_nodes_per_cell)

    data = np.ones(IC.shape)
    _, ix = np.unique(IC, return_index=True)
    data[ix] *= -1

    cell_faces = sps.coo_matrix((data, (IC, cell_idx))).tocsc()

    # Append information on subgrid extraction to the new grid's history
    name = list(g.name)
    name.append("Extract subgrid")

    h = pp.Grid(g.dim - 1, g.nodes[:, unique_nodes], face_nodes, cell_faces,
                name)

    if is_planar:
        # We could now just copy the corresponding geometric values from g to h, but we
        # run h.compute_geometry() to check if everything went ok.
        h.compute_geometry()
        if not np.all(np.isclose(g.face_areas[f], h.cell_volumes)):
            raise AssertionError(
                """Somethign went wrong in extracting subgrid. Face area of
            higher dim is not equal face centers of lower dim grid""")
        if not np.all(np.isclose(g.face_centers[:, f], h.cell_centers)):
            raise AssertionError(
                """Somethign went wrong in extracting subgrid. Face centers
            of higher dim is not equal cell centers of lower dim grid""")
    h.cell_volumes = g.face_areas[f]
    h.cell_centers = g.face_centers[:, f]

    h.parent_face_ind = f
    return h, f, unique_nodes
Exemple #19
0
def extract_subgrid(g, c, sort=True, faces=False):
    """
    Extract a subgrid based on cell/face indices.

    For simplicity the cell/face indices will be sorted before the subgrid is
    extracted.

    If the parent grid has geometry attributes (cell centers etc.) these are
    copied to the child.

    No checks are done on whether the cells/faces form a connected area. The
    method should work in theory for non-connected cells, the user will then
    have to decide what to do with the resulting grid. This option has however
    not been tested.

    Parameters:
        g (core.grids.Grid): Grid object, parent
        c (np.array, dtype=int): Indices of cells to be extracted
        sort=True (bool): If true c is sorted
        faces=False (bool): If true c are intrepetred as faces, and the
                            exptracted grid will be a lower dimensional grid
                            defined by the these faces
    Returns:
        Grid: Extracted subgrid. Will share (note, *not* copy)
            geometric fileds with the parent grid. Also has an additional
            field parent_cell_ind giving correspondance between parent and
            child cells.
        np.ndarray, dtype=int: Index of the extracted faces, ordered so that
            element i is the global index of face i in the subgrid.
        np.ndarray, dtype=int: Index of the extracted nodes, ordered so that
            element i is the global index of node i in the subgrid.

    """
    if sort:
        c = np.sort(np.atleast_1d(c))

    if faces:
        return __extract_cells_from_faces(g, c)
    # Local cell-face and face-node maps.
    cf_sub, unique_faces = __extract_submatrix(g.cell_faces, c)
    fn_sub, unique_nodes = __extract_submatrix(g.face_nodes, unique_faces)

    # Append information on subgrid extraction to the new grid's history
    name = list(g.name)
    name.append("Extract subgrid")

    # Construct new grid.
    h = pp.Grid(g.dim, g.nodes[:, unique_nodes], fn_sub, cf_sub, name)

    # Copy geometric information if any
    if hasattr(g, "cell_centers"):
        h.cell_centers = g.cell_centers[:, c]
    if hasattr(g, "cell_volumes"):
        h.cell_volumes = g.cell_volumes[c]
    if hasattr(g, "face_centers"):
        h.face_centers = g.face_centers[:, unique_faces]
    if hasattr(g, "face_normals"):
        h.face_normals = g.face_normals[:, unique_faces]
    if hasattr(g, "face_areas"):
        h.face_areas = g.face_areas[unique_faces]

    h.parent_cell_ind = c

    return h, unique_faces, unique_nodes
def _extrude_1d(
    g: pp.TensorGrid, z: np.ndarray
) -> Tuple[pp.Grid, np.ndarray, np.ndarray]:
    """ Extrude a 1d grid into 2d by prismatic extension in the z-direction.

    The original grid is assumed to be in the xy-plane, that is, any existing non-zero
    z-direction is ignored.

    Both the original and the new grid will have their geometry computed.

    Parameters:
        g (pp.Grid): Original grid to be extruded. Should have dimension 1.
        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:
        pp.Grid: A grid of dimension 2.
        np.array of np.arrays: Cell mappings, so that element ci gives all indices of
            cells in the extruded grid that comes from cell ci in the original grid.
        np.array of np.arrays: Face mappings, so that element fi gives all indices of
            faces in the extruded grid that comes from face fi in the original grid.

    """
    # Number of cells in the grid
    num_cell_layers = z.size - 1

    # Node coordinates can be extruded in using a tensor product
    x = g.nodes[0]
    y = g.nodes[1]

    x_2d, z_2d = np.meshgrid(x, z)
    y_2d, _ = np.meshgrid(y, z)
    nodes = np.vstack((x_2d.ravel(), y_2d.ravel(), z_2d.ravel()))

    # Bookkeeping
    nn_old = g.num_nodes
    nc_old = g.num_cells
    nf_old = g.num_faces

    nn_new = g.num_nodes * (num_cell_layers + 1)
    nc_new = g.num_cells * num_cell_layers
    nf_new = g.num_faces * num_cell_layers + g.num_cells * (num_cell_layers + 1)

    fn_old = g.face_nodes.indices
    # Vertical faces are made by extruding old face-node relation
    fn_vert = np.empty((2, 0), dtype=np.int)
    for k in range(num_cell_layers):
        fn_this = k * nn_old + np.vstack((fn_old, nn_old + fn_old))
        fn_vert = np.hstack((fn_vert, fn_this))

    # Horizontal faces are defined from the old cell-node relation
    # Implementation note: This operation is much simpler than in the 2d-3d operation,
    # since much less is assumed on the ordering of face-nodes for 2d grids than 3d
    cn_old = g.cell_nodes().indices.reshape((2, g.num_cells), order="F")
    # Bottom layer
    fn_hor = cn_old
    # All other layers
    for k in range(num_cell_layers):
        fn_hor = np.hstack((fn_hor, cn_old + (k + 1) * nn_old))

    # Finalize the face-node map
    fn_rows = np.hstack((fn_vert, fn_hor))
    fn_cols = np.tile(np.arange(fn_rows.shape[1]), (2, 1))
    fn_data = np.ones(fn_cols.size, dtype=np.bool)

    fn = sps.coo_matrix(
        (fn_data, (fn_rows.ravel("f"), fn_cols.ravel("f"))), shape=(nn_new, nf_new)
    ).tocsc()

    # Next, cell-faces
    # We know there are exactly four faces for each cell
    cf_rows = np.empty((4, 0), dtype=np.int)
    cf_old = g.cell_faces.indices.reshape((2, -1), order="f")

    # Create vertical and horizontal faces together
    for k in range(num_cell_layers):
        # Vertical faces are identified by the old cell-face relation
        cf_vert_this = nn_old * k + cf_old

        # Put horizontal faces on top and bottom
        cf_hor_this = np.vstack((np.arange(nc_old), np.arange(nc_old) + nc_old))
        # Add an offset of the number of vertical faces in the grid + previous horizontal
        # faces
        cf_hor_this += nf_old * num_cell_layers + k * nc_old

        cf_rows = np.hstack((cf_rows, np.vstack((cf_vert_this, cf_hor_this))))

    # Finalize Cell-face relation
    cf_rows = cf_rows.ravel("f")
    cf_cols = np.tile(np.arange(nc_new), (4, 1)).ravel("f")
    # Define positive and negative sides. The choices here are somewhat arbitrary.
    tmp = np.ones(nc_new, dtype=np.int)
    cf_data = np.vstack((-tmp, tmp, -tmp, tmp)).ravel("f")
    cf = sps.coo_matrix((cf_data, (cf_rows, cf_cols)), shape=(nf_new, nc_new)).tocsc()

    tags = _define_tags(g, num_cell_layers)

    # We are ready to define the new grid
    g_info = g.name.copy()
    g_info.append("Extrude 1d->2d")

    g_new = pp.Grid(2, nodes, fn, cf, g_info, tags=tags)
    g_new.compute_geometry()

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

    # Mappings between old and new cells and faces
    cell_map, face_map = _create_mappings(g, g_new, num_cell_layers)

    return g_new, cell_map, face_map
def _extrude_2d(g: pp.Grid, z: np.ndarray) -> Tuple[pp.Grid, np.ndarray, np.ndarray]:
    """ Extrude a 2d grid into 3d by prismatic extension.

    The original grid is assumed to be in the xy-plane, that is, any existing non-zero
    z-direction is ignored.

    Both the original and the new grid will have their geometry computed.

    Parameters:
        g (pp.Grid): Original grid to be extruded. Should have dimension 2.
        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:
        pp.Grid: A grid of dimension 3.
        np.array of np.arrays: Cell mappings, so that element ci gives all indices of
            cells in the extruded grid that comes from cell ci in the original grid.
        np.array of np.arrays: Face mappings, so that element fi gives all indices of
            faces in the extruded grid that comes from face fi in the original grid.

    """

    g.compute_geometry()

    negative_extrusion = np.all(z <= 0)

    ## Bookkeeping of the number of grid items

    # Number of nodes in the z-direction
    num_node_layers = z.size
    # Number of cell layers, one less than the nodes
    num_cell_layers = num_node_layers - 1

    # Short hand for the number of cells in the 2d grid
    nc_2d = g.num_cells
    nf_2d = g.num_faces
    nn_2d = g.num_nodes

    # The number of nodes in the 3d grid is given by the number of 2d nodes, and the
    # number of node layers
    nn_3d = nn_2d * num_node_layers
    # The 3d cell count is similar to that for the nodes
    nc_3d = nc_2d * num_cell_layers
    # The number of faces is more intricate: In each layer of cells, there will be as
    # many faces as there is in the 2d grid. In addition, in the direction of extrusion
    # there will be one set of faces per node layer, with each layer containing as many
    # faces as there are cells in the 2d grid
    nf_3d = nf_2d * num_cell_layers + nc_2d * num_node_layers

    ## Nodes - only coorinades are needed
    # The nodes in the 2d grid are copied for all layers, with the z-coordinates changed
    # for each layer. This means that for a vertical pilar, the face-node and cell-node
    # relations can be inferred from that in the original 2d grid, with index increments
    # of size nn_2d
    x_layer = g.nodes[0]
    y_layer = g.nodes[1]

    nodes = np.empty((3, 0))
    # Stack the layers of nodes
    for zloc in z:
        nodes = np.hstack((nodes, np.vstack((x_layer, y_layer, zloc * np.ones(nn_2d)))))

    ## Face-node relations
    # The 3d grid has two types of faces: Those formed by faces in the 2d grid, termed
    # 'vertical' below, and those on the top and bottom of the 3d cells, termed
    # horizontal

    # Face-node relation for the 2d grid. We know there are exactly two nodes in each
    # 2d face.
    fn_2d = g.face_nodes.indices.reshape((2, g.num_faces), order="F")

    # Nodes of the faces for the bottom layer of 3d cells. These are formed by
    # connecting nodes in the bottom layer with those immediately above
    fn_layer = np.vstack((fn_2d[0], fn_2d[1], fn_2d[1] + nn_2d, fn_2d[0] + nn_2d))

    # For the vertical cells, the flux direction indicated in cell_face map will be
    # inherited from the 2d grid (see below). The normal vector, which should be
    # consistent with this value, is effectively computed from the ordering of the
    # face-node relation (and the same is true for several other geometric quantities).
    # This requires that the face-nodes are sorted in a CCW order when seen from the
    # side of a positive cell_face value. To sort this out, we need to flip some of the
    # columns in fn_layer

    # Faces, cells and values of the 2d cell-face map
    [fi, ci, sgn] = sps.find(g.cell_faces)
    # Only consider each face once
    _, idx = np.unique(fi, return_index=True)

    # The node ordering in fn_layer will be CCW seen from cell ci if the cell center of
    # ci is CW relative to the line from the first to the second node of the 2d cell.
    #
    # Example: with p0 = [0, 0, 0], p1 = [1, 0, 0], the 3d face will have further nodes
    #               p2 = [1, 0, 1], p3 = [0, 0, 1].
    # This will be counterclockwise to a 2d cell center of, say, [0.5, -0.5, 0],
    #  (which is CW relative to p0 and p1)
    #
    p0 = g.nodes[:, fn_2d[0, fi[idx]]]
    p1 = g.nodes[:, fn_2d[1, fi[idx]]]
    pc = g.cell_centers[:, ci[idx]]
    ccw_2d = pp.geometry_property_checks.is_ccw_polyline(p0, p1, pc)

    # We should flip those columns in fn_layer where the sign is positive, and the 2d
    # is not ccw (meaning the 3d will be). Similarly, also flip negative signs and 2d
    # ccw.
    flip = np.logical_or(
        np.logical_and(sgn[idx] > 0, np.logical_not(ccw_2d)),
        np.logical_and(sgn[idx] < 0, ccw_2d),
    )

    # Finally, if the extrusion is in the negative direction, the ordering of all
    # face-node relations is the oposite of that indicated above.
    if negative_extrusion:
        flip = np.logical_not(flip)

    fn_layer[:, flip] = fn_layer[np.array([1, 0, 3, 2])][:, flip]

    # The face-node relation for the vertical cells are found by stacking those in the
    # bottom layer, with an appropriate offset. This also implies that the vertical
    # faces of a cell in layer k are the same as the faces of the corresponding 2d cell,
    # with the appropriate adjustments for the number of faces and cells in each layer
    fn_rows_vertical = np.empty((4, 0))
    # Loop over all layers of cells
    for k in range(num_cell_layers):
        fn_rows_vertical = np.hstack((fn_rows_vertical, fn_layer + nn_2d * k))

    # Reshape the node indices into a single array
    fn_rows_vertical = fn_rows_vertical.ravel("F")

    # All vertical faces have exactly four nodes
    nodes_per_face_vertical = 4
    # Aim for a csc-representation of the faces. Column pointers
    fn_cols_vertical = np.arange(
        0, nodes_per_face_vertical * nf_2d * num_cell_layers, nodes_per_face_vertical
    )

    # Next, deal with the horizontal faces. The face-node relation is based on the
    # cell-node relation of the 2d grid.
    # The structure of this constrution is a bit more involved than for the vertical
    # faces, since the 2d cells have an unknown, and generally varying, number of nodes
    cn_2d = g.cell_nodes()

    # Short hand for node indices of each cell.
    cn_ind_2d = cn_2d.indices.copy()

    # Similar to the vertical faces, the face-node relation in 3d should match the
    # sign in the cell-face relation, so that the generated normal vector points out of
    # the cell with cf-value 1.
    # This requires a sorting of the nodes for each cell
    for ci in range(nc_2d):
        # Node indices of this 2d cell
        start = cn_2d.indptr[ci]
        stop = cn_2d.indptr[ci + 1]
        ni = cn_ind_2d[start:stop]

        coord = g.nodes[:2, ni]
        # Sort the points.
        # IMPLEMENTATION NOTE: this probably assumes convexity of the 2d cell.
        sort_ind = pp.utils.sort_points.sort_point_plane(
            np.vstack((coord, np.zeros(coord.shape[1]))),
            g.cell_centers[:, ci].reshape((-1, 1)),
        )
        # Indices that sort the nodes. The sort function contains a rotation, which
        # implies that it is unknown whether the ordering is cw or ccw
        # If the sorted points are ccw, we store them, unless the extrusion is negative
        # in which case the ordering should be cw, and the points are turned.
        if pp.geometry_property_checks.is_ccw_polygon(coord[:, sort_ind]):
            if negative_extrusion:
                cn_ind_2d[start:stop] = cn_ind_2d[start:stop][sort_ind[::-1]]
            else:
                cn_ind_2d[start:stop] = cn_ind_2d[start:stop][sort_ind]
        # Else, the ordering should be negative.
        elif pp.geometry_property_checks.is_ccw_polygon(coord[:, sort_ind[::-1]]):
            if negative_extrusion:
                cn_ind_2d[start:stop] = cn_ind_2d[start:stop][sort_ind]
            else:
                cn_ind_2d[start:stop] = cn_ind_2d[start:stop][sort_ind[::-1]]
        else:
            raise ValueError("this should not happen. Is the cell non-convex??")

    # Compressed column storage for horizontal faces: Store node indices
    fn_rows_horizontal = np.array([], dtype=np.int)
    # .. and pointers to the start of new faces
    fn_cols_horizontal = np.array(0, dtype=np.int)
    # Loop over all layers of nodes (one more than number of cells)
    # This means that the horizontal faces of a given cell is given by its index (bottom)
    # and its index + the number of 2d cells, both offset with the total number of
    # vertical faces
    for k in range(num_node_layers):
        # The horizontal cell-node relation for this layer is the bottom one, plus an
        # offset of the number of 2d nodes, per layer
        fn_rows_horizontal = np.hstack((fn_rows_horizontal, cn_ind_2d + nn_2d * k))
        # The index pointers are those of the 2d cell-node relation.
        # Adjustment for the vertical faces is done below
        # Drop the final element of the 2d indptr, which effectively signifies the end
        # of this array (we will add the corresponding element for the full array below)
        fn_cols_horizontal = np.hstack(
            (fn_cols_horizontal, cn_2d.indptr[1:] + cn_ind_2d.size * k)
        )

    # Add the final element which marks the end of the array
    # fn_cols_horizontal = np.hstack((fn_cols_horizontal, fn_rows_horizontal.size))
    # The horizontal faces are appended to the vertical ones. The node indices are the
    # same, but the face indices must be increased by the number of vertical faces
    num_vertical_faces = nf_2d * num_cell_layers
    fn_cols_horizontal += num_vertical_faces * nodes_per_face_vertical

    # Put together the vertical and horizontal data, create the face-node relation
    indptr = np.hstack((fn_cols_vertical, fn_cols_horizontal)).astype(np.int)
    indices = np.hstack((fn_rows_vertical, fn_rows_horizontal)).astype(np.int)
    data = np.ones(indices.size, dtype=np.int)

    # Finally, construct the face-node sparse matrix
    face_nodes = sps.csc_matrix((data, indices, indptr), shape=(nn_3d, nf_3d))

    ### Next the cell-faces.
    # Similar to the face-nodes, the easiest option is first to deal with the vertical
    # faces, which can be inferred directly from faces in the 2d grid, and then the
    # horizontal direction.
    # IMPLEMENTATION NOTE: Since all cells have both horizontal and vertical faces, and
    # these are found in separate operations, the easiest way to assemble the 3d
    # cell-face matrix is to construct information for a coo-matrix (not compressed
    # storage), and then convert later. This has some overhead, but the alternative
    # is to combine and sort the face indices in the horizontal and vertical components
    # so that all faces of any cell is stored together. This is most conveniently
    # left to scipy sparse .tocsc() function

    ## Vertical faces
    # For the vertical faces, the information from the 2d grid can be copied

    cf_rows_2d = g.cell_faces.indices
    cf_cols_2d = g.cell_faces.indptr
    cf_data_2d = g.cell_faces.data

    cf_rows_vertical = np.array([], dtype=np.int)
    # For the cells, we will store the number of facqes for each cell. This will later
    # be expanded to a full set of cell indices
    cf_vertical_cell_count = np.array([], dtype=np.int)
    cf_data_vertical = np.array([])

    for k in range(num_cell_layers):
        # The face indices are found from the 2d information, with increaments that
        # reflect how many layers of vertical faces there are below
        cf_rows_vertical = np.hstack((cf_rows_vertical, cf_rows_2d + k * nf_2d))
        # The diff here gives the number of faces per cell
        cf_vertical_cell_count = np.hstack(
            (cf_vertical_cell_count, np.diff(cf_cols_2d))
        )
        # The data is just plus and minus ones, no need to adjust
        cf_data_vertical = np.hstack((cf_data_vertical, cf_data_2d))

    # Expand information of the number of faces per cell into a corresponding full set
    # of cell indices
    cf_cols_vertical = pp.utils.matrix_compression.rldecode(
        np.arange(nc_3d), cf_vertical_cell_count
    )

    ## Horizontal faces
    # There is one set of faces per layer of nodes.
    # The cell_face relation will assign -1 to the upper cells, and +1 to lower cells.
    # This corresponds to normal vectors pointing upwards.
    # The bottom and top layers are special, in that they have only one neighboring
    # cell. All other layers have two (they are internal)

    # Bottom layer
    cf_rows_horizontal = num_vertical_faces + np.arange(nc_2d)
    cf_cols_horizontal = np.arange(nc_2d)
    cf_data_horizontal = -np.ones(nc_2d, dtype=np.int)

    # Intermediate layers, note
    for k in range(1, num_cell_layers):
        # Face indices are given twice, for the lower and upper neighboring cell
        # The offset of the face index is the number of vertical faces plus the number
        # of horizontal faces in lower layers
        rows_here = (
            num_vertical_faces
            + k * nc_2d
            + np.hstack((np.arange(nc_2d), np.arange(nc_2d)))
        )
        cf_rows_horizontal = np.hstack((cf_rows_horizontal, rows_here))

        # Cell indices, first of the lower layer, then of the upper
        cols_here = np.hstack(
            ((k - 1) * nc_2d + np.arange(nc_2d), k * nc_2d + np.arange(nc_2d))
        )
        cf_cols_horizontal = np.hstack((cf_cols_horizontal, cols_here))
        # Data: +1 for the lower cells, -1 for the upper
        data_here = np.hstack((np.ones(nc_2d), -np.ones(nc_2d)))
        cf_data_horizontal = np.hstack((cf_data_horizontal, data_here))

    # Top layer, with index offset for all other faces
    cf_rows_horizontal = np.hstack(
        (
            cf_rows_horizontal,
            num_vertical_faces + num_cell_layers * nc_2d + np.arange(nc_2d),
        )
    )
    # Similarly, the cell indices of the topbost layer
    cf_cols_horizontal = np.hstack(
        (cf_cols_horizontal, (num_cell_layers - 1) * nc_2d + np.arange(nc_2d))
    )
    # Only +1 in the data (oposite to lowermost layer)
    cf_data_horizontal = np.hstack((cf_data_horizontal, np.ones(nc_2d)))

    # Merge horizontal and vertical layers
    cf_rows = np.hstack((cf_rows_horizontal, cf_rows_vertical))
    cf_cols = np.hstack((cf_cols_horizontal, cf_cols_vertical))
    cf_data = np.hstack((cf_data_horizontal, cf_data_vertical))

    cell_faces = sps.coo_matrix(
        (cf_data, (cf_rows, cf_cols)), shape=(nf_3d, nc_3d)
    ).tocsc()

    tags = _define_tags(g, num_cell_layers)

    name = g.name.copy()
    name.append("Extrude 2d->3d")
    g_info = g.name.copy()
    g_info.append("Extrude 1d->2d")

    g_new = pp.Grid(3, nodes, face_nodes, cell_faces, g_info, tags=tags)
    g_new.compute_geometry()

    # Mappings between old and new cells and faces
    cell_map, face_map = _create_mappings(g, g_new, num_cell_layers)

    return g_new, cell_map, face_map
Exemple #22
0
    def grid_2d(self, pert_node: bool = False, flip_normal: bool = False) -> pp.Grid:
        """
        Make a 2d unit square simplex grid with six cells:
        __________
        |\      /|
        | \    / |
        |  \  /  |
        |   \/   |
        |--------|
        |   /\   |
        |  /  \  |
        | /    \ |
        |/      \|
        ----------
        pert_node pertubes one node in the grid. Leads to non-matching cells.
        flip_normal flips one normal vector in 2d grid adjacent to the fracture.
        Tests that there is no assumptions on direction of fluxes in the
        mortar coupling.
        """
        nodes = np.array(
            [
                [0, 0, 0],
                [1, 0, 0],
                [1, 0.5, 0],
                [0.5, 0.5, 0],
                [0, 0.5, 0],
                [0, 0.5, 0],
                [0.5, 0.5, 0],
                [1, 0.5, 0],
                [1, 1, 0],
                [0, 1, 0],
            ]
        ).T
        if pert_node:
            nodes[0, 3] = 0.75

        fn = np.array(
            [
                [0, 1],
                [1, 2],
                [2, 3],
                [3, 4],
                [4, 0],
                [0, 3],
                [3, 1],
                [5, 6],
                [6, 7],
                [7, 8],
                [8, 9],
                [9, 5],
                [9, 6],
                [6, 8],
            ]
        ).T
        cf = np.array(
            [[3, 4, 5], [0, 6, 5], [1, 2, 6], [7, 12, 11], [12, 13, 10], [8, 9, 13]]
        ).T
        cols = np.tile(np.arange(fn.shape[1]), (fn.shape[0], 1)).ravel("F")
        face_nodes = sps.csc_matrix((np.ones_like(cols), (fn.ravel("F"), cols)))

        cols = np.tile(np.arange(cf.shape[1]), (cf.shape[0], 1)).ravel("F")
        data = np.array([1, 1, 1, 1, -1, -1, 1, 1, 1, 1, -1, 1, 1, 1, -1, 1, 1, -1])

        cell_faces = sps.csc_matrix((data, (cf.ravel("F"), cols)))

        g = pp.Grid(2, nodes, face_nodes, cell_faces, "TriangleGrid")
        g.compute_geometry()
        g.tags["fracture_faces"][[2, 3, 7, 8]] = 1

        if False:  # TODO: purge
            di = 0.1
            g.cell_centers[0, 0] = 0.25 - di
            g.cell_centers[0, 2] = 0.75 + di
            g.cell_centers[0, 3] = 0.25 - di
            g.cell_centers[0, 5] = 0.75 + di
            di = 0.021
            g.cell_centers[1, 0] = 0.5 - di
            g.cell_centers[1, 2] = 0.5 - di
            g.cell_centers[1, 3] = 0.5 + di
            g.cell_centers[1, 5] = 0.5 + di
        if flip_normal:
            g.face_normals[:, [UCC2]] *= -1
            g.cell_faces[2, 2] *= -1
        g.global_point_ind = np.arange(nodes.shape[1])

        return g