Exemplo n.º 1
0
    def __init__(self, g):
        """
        Constructor for subcell topology

        Parameters
        ----------
        g grid
        """
        self.g = g

        # Indices of neighboring faces and cells. The indices are sorted to
        # simplify later treatment
        g.cell_faces.sort_indices()
        face_ind, cell_ind = g.cell_faces.nonzero()

        # Number of faces per node
        num_face_nodes = np.diff(g.face_nodes.indptr)

        # Duplicate cell and face indices, so that they can be matched with
        # the nodes
        cells_duplicated = matrix_compression.rldecode(
            cell_ind, num_face_nodes[face_ind])
        faces_duplicated = matrix_compression.rldecode(
            face_ind, num_face_nodes[face_ind])
        M = sps.coo_matrix((np.ones(face_ind.size),
                            (face_ind, np.arange(face_ind.size))),
                           shape=(face_ind.max() + 1, face_ind.size))
        nodes_duplicated = g.face_nodes * M
        nodes_duplicated = nodes_duplicated.indices

        face_nodes_indptr = g.face_nodes.indptr
        face_nodes_indices = g.face_nodes.indices
        face_nodes_data = np.arange(face_nodes_indices.size) + 1
        sub_face_mat = sps.csc_matrix((face_nodes_data, face_nodes_indices,
                                       face_nodes_indptr))
        sub_faces = sub_face_mat * M
        sub_faces = sub_faces.data - 1

        # Sort data
        idx = np.lexsort((sub_faces, faces_duplicated, nodes_duplicated,
                          cells_duplicated))
        self.nno = nodes_duplicated[idx]
        self.cno = cells_duplicated[idx]
        self.fno = faces_duplicated[idx]
        self.subfno = sub_faces[idx].astype(int)
        self.subhfno = np.arange(idx.size, dtype='>i4')
        self.num_subfno = self.subfno.max() + 1
        self.num_cno = self.cno.max() + 1

        # Make subface indices unique, that is, pair the indices from the two
        # adjacent cells
        _, unique_subfno = np.unique(self.subfno, return_index=True)

        # Reduce topology to one field per subface
        self.nno_unique = self.nno[unique_subfno]
        self.fno_unique = self.fno[unique_subfno]
        self.cno_unique = self.cno[unique_subfno]
        self.subfno_unique = self.subfno[unique_subfno]
        self.num_subfno_unique = self.subfno_unique.max() + 1
        self.unique_subfno = unique_subfno
Exemplo n.º 2
0
    def cell_face_as_dense(self):
        """
        Obtain the cell-face relation in the from of two rows, rather than a
        sparse matrix. This alterative format can be useful in some cases.

        Each column in the array corresponds to a face, and the elements in
        that column refers to cell indices. The value -1 signifies a boundary.
        The normal vector of the face points from the first to the second row.

        Returns:
            np.ndarray, 2 x num_faces: Array representation of face-cell
                relations
        """
        n = self.cell_faces.tocsr()
        d = np.diff(n.indptr)
        rows = matrix_compression.rldecode(np.arange(d.size), d)
        # Increase the data by one to distinguish cell indices from boundary
        # cells
        data = n.indices + 1
        cols = ((n.data + 1) / 2).astype('i')
        neighs = sps.coo_matrix((data, (rows, cols))).todense()
        # Subtract 1 to get back to real cell indices
        neighs -= 1
        neighs = neighs.transpose().A.astype('int')
        # Finally, we need to switch order of rows to get normal vectors
        # pointing from first to second row.
        return neighs[::-1]
Exemplo n.º 3
0
def block_diag_index(m, n=None):
    """
    Get row and column indices for block diagonal matrix

    This is intended as the equivalent of the corresponding method in MRST.

    Examples:
    >>> m = np.array([2, 3])
    >>> n = np.array([1, 2])
    >>> i, j = block_diag_index(m, n)
    >>> i, j
    (array([0, 1, 2, 3, 4, 2, 3, 4]), array([0, 0, 1, 1, 1, 2, 2, 2]))
    >>> a = np.array([1, 3])
    >>> i, j = block_diag_index(a)
    >>> i, j
    (array([0, 1, 2, 3, 1, 2, 3, 1, 2, 3]), array([0, 1, 1, 1, 2, 2, 2, 3, 3, 3]))

    Parameters:
        m - ndarray, dimension 1
        n - ndarray, dimension 1, defaults to m

    """
    if n is None:
        n = m

    start = np.hstack((np.zeros(1, dtype='int'), m))
    pos = np.cumsum(start)
    p1 = pos[0:-1]
    p2 = pos[1:]-1
    p1_full = matrix_compression.rldecode(p1, n)
    p2_full = matrix_compression.rldecode(p2, n)

    i = mcolon.mcolon(p1_full, p2_full)
    sumn = np.arange(np.sum(n))
    m_n_full = matrix_compression.rldecode(m, n)
    j = matrix_compression.rldecode(sumn, m_n_full)
    return i, j
Exemplo n.º 4
0
def compute_dist_face_cell(g, subcell_topology, eta):
    """
    Compute vectors from cell centers continuity points on each sub-face.

    The location of the continuity point is given by

        x_cp = (1-eta) * x_facecenter + eta * x_vertex

    On the boundary, eta is set to zero, thus the continuity point is at the
    face center

    Parameters
    ----------
    g: Grid
    subcell_topology: Of class subcell topology in this module
    eta: [0,1), eta = 0 gives cont. pt. at face midpoint, eta = 1 means at
    the vertex

    Returns
    -------
    sps.csr() matrix representation of vectors. Size g.nf x (g.nc * g.nd)
    """
    _, blocksz = matrix_compression.rlencode(np.vstack((
        subcell_topology.cno, subcell_topology.nno)))
    dims = g.dim

    rows, cols = np.meshgrid(subcell_topology.subhfno, np.arange(dims))
    cols += matrix_compression.rldecode(np.cumsum(blocksz)-blocksz[0], blocksz)

    eta_vec = eta*np.ones(subcell_topology.fno.size)
    # Set eta values to zero at the boundary
    bnd = np.argwhere(np.abs(g.cell_faces).sum(axis=1).A.squeeze()
                            == 1).squeeze()
    eta_vec[bnd] = 0
    cp = g.face_centers[:, subcell_topology.fno] \
        + eta_vec * (g.nodes[:, subcell_topology.nno] -
                      g.face_centers[:, subcell_topology.fno])
    dist = cp - g.cell_centers[:, subcell_topology.cno]
    mat = sps.coo_matrix((dist.ravel(), (rows.ravel(), cols.ravel()))).tocsr()
    return subcell_topology.pair_over_subfaces(mat)
Exemplo n.º 5
0
def _tensor_vector_prod(g, constit, subcell_topology):
    # Stack cells and nodes, and remove duplicate rows. Since subcell_mapping
    # defines cno and nno (and others) working cell-wise, this will
    # correspond to a unique rows (Matlab-style) from what I understand.
    # This also means that the pairs in cell_node_blocks uniquely defines
    # subcells, and can be used to index gradients etc.
    cell_node_blocks, blocksz = matrix_compression.rlencode(np.vstack((
        subcell_topology.cno, subcell_topology.nno)))

    nd = g.dim

    # Duplicates in [cno, nno] corresponds to different faces meeting at the
    # same node. There should be exactly nd of these. This test will fail
    # for pyramids in 3D
    assert np.all(blocksz == nd)

    # Define row and column indices to be used for normal vector matrix
    # Rows are based on sub-face numbers.
    # Columns have nd elements for each sub-cell (to store a vector) and
    # is adjusted according to block sizes
    rn, cn = np.meshgrid(subcell_topology.subhfno, np.arange(nd))
    sum_blocksz = np.cumsum(blocksz)
    cn += matrix_compression.rldecode(sum_blocksz - blocksz[0], blocksz)

    # Distribute faces equally on the sub-faces, and store in a matrix
    num_nodes = np.diff(g.face_nodes.indptr)
    normals = g.face_normals[:, subcell_topology.fno] / num_nodes[
        subcell_topology.fno]
    normals_mat = sps.coo_matrix((normals.ravel(1), (rn.ravel('F'),
                                                     cn.ravel('F')))).tocsr()

    # Then row and columns for stiffness matrix. There are nd^2 elements in
    # the gradient operator, and so the structure is somewhat different from
    # the normal vectors
    rc, cc = np.meshgrid(subcell_topology.subhfno, np.arange(nd**2))
    sum_blocksz = np.cumsum(blocksz**2)
    cc += matrix_compression.rldecode(sum_blocksz - blocksz[0]**2, blocksz)

    # Splitt stiffness matrix into symmetric and anti-symmatric part
    sym_tensor, asym_tensor = _split_stiffness_matrix(constit)

    # Getting the right elements out of the constitutive laws was a bit
    # tricky, but the following code turned out to do the trick
    sym_tensor_swp = np.swapaxes(sym_tensor, 2, 0)
    asym_tensor_swp = np.swapaxes(asym_tensor, 2, 0)

    # The first dimension in csym and casym represent the contribution from
    # all dimensions to the stress in one dimension (in 2D, csym[0:2,:,
    # :] together gives stress in the x-direction etc.
    # Define index vector to access the right rows
    rind = np.arange(nd)

    # Empty matrices to initialize matrix-tensor products. Will be expanded
    # as we move on
    zr = np.zeros(0)
    ncsym = sps.coo_matrix((zr, (zr,
                                 zr)), shape=(0, cc.max() + 1)).tocsr()
    ncasym = sps.coo_matrix((zr, (zr, zr)), shape=(0, cc.max() + 1)).tocsr()

    # For the asymmetric part of the tensor, we will apply volume averaging.
    # Associate a volume with each sub-cell, and a node-volume as the sum of
    # all surrounding sub-cells
    num_cell_nodes = g.num_cell_nodes()
    cell_vol = g.cell_volumes / num_cell_nodes
    node_vol = np.bincount(subcell_topology.nno, weights=cell_vol[
        subcell_topology.cno]) / g.dim

    num_elem = cell_node_blocks.shape[1]
    map_mat = sps.coo_matrix((np.ones(num_elem),
                              (np.arange(num_elem), cell_node_blocks[1])))
    weight_mat = sps.coo_matrix((cell_vol[cell_node_blocks[0]] / node_vol[
        cell_node_blocks[1]], (cell_node_blocks[1], np.arange(num_elem))))
    # Operator for carying out the average
    average = sps.kron(map_mat * weight_mat, sps.identity(nd)).tocsr()

    for iter1 in range(nd):
        # Pick out part of Hook's law associated with this dimension
        # The code here looks nasty, it should be possible to get the right
        # format of the submatrices in a simpler way, but I couldn't do it.
        sym_dim = np.hstack(sym_tensor_swp[:, :, rind]).transpose()
        asym_dim = np.hstack(asym_tensor_swp[:, :, rind]).transpose()

        # Distribute (relevant parts of) Hook's law on subcells
        # This will be nd rows, thus cell ci is associated with indices
        # ci*nd+np.arange(nd)
        sub_cell_ind = __expand_indices_nd(cell_node_blocks[0], nd)
        sym_vals = sym_dim[sub_cell_ind]
        asym_vals = asym_dim[sub_cell_ind]

        # Represent this part of the stiffness matrix in matrix form
        csym_mat = sps.coo_matrix((sym_vals.ravel('C'),
                                   (rc.ravel('F'), cc.ravel('F')))).tocsr()
        casym_mat = sps.coo_matrix((asym_vals.ravel(0),
                                    (rc.ravel('F'), cc.ravel('F')))).tocsr()

        # Compute average around vertexes
        casym_mat = average * casym_mat

        # Compute products of normal vectors and stiffness tensors,
        # and stack dimensions vertically
        ncsym = sps.vstack((ncsym, normals_mat * csym_mat))
        ncasym = sps.vstack((ncasym, normals_mat * casym_mat))

        # Increase index vector, so that we get rows contributing to forces
        # in the next dimension
        rind += nd

    grad_ind = cc[:, ::nd]

    return ncsym, ncasym, cell_node_blocks, grad_ind
Exemplo n.º 6
0
def _tensor_vector_prod(g, k, subcell_topology):
    """
    Compute product of normal vectors and tensors on a sub-cell level.

    This is essentially defining Darcy's law for each sub-face in terms of
    sub-cell gradients. Thus, we also implicitly define the global ordering
    of sub-cell gradient variables (via the interpretation of the columns in
    nk).

    NOTE: In the local numbering below, in particular in the variables i and j,
    it is tacitly assumed that g.dim == g.nodes.shape[0] ==
    g.face_normals.shape[0] etc. See implementation note in main method.

    Parameters:
        g (core.grids.grid): Discretization grid
        k (core.constit.second_order_tensor): The permeability tensor
        subcell_topology (fvutils.SubcellTopology): Wrapper class containing
            subcell numbering.

    Returns:
        nk: sub-face wise product of normal vector and permeability tensor.
        cell_node_blocks pairings of node and cell indices, which together
            define a sub-cell.
        sub_cell_ind: index of all subcells

    """

    # Stack cell and nodes, and remove duplicate rows. Since subcell_mapping
    # defines cno and nno (and others) working cell-wise, this will
    # correspond to a unique rows (Matlab-style) from what I understand.
    # This also means that the pairs in cell_node_blocks uniquely defines
    # subcells, and can be used to index gradients etc.
    cell_node_blocks, blocksz = matrix_compression.rlencode(
        np.vstack((subcell_topology.cno, subcell_topology.nno)))

    nd = g.dim

    # Duplicates in [cno, nno] corresponds to different faces meeting at the
    # same node. There should be exactly nd of these. This test will fail
    # for pyramids in 3D
    assert np.all(blocksz == nd)

    # Define row and column indices to be used for normal_vectors * perm.
    # Rows are based on sub-face numbers.
    # Columns have nd elements for each sub-cell (to store a gradient) and
    # is adjusted according to block sizes
    i, j = np.meshgrid(subcell_topology.subhfno, np.arange(nd))
    sum_blocksz = np.cumsum(blocksz)
    j += matrix_compression.rldecode(sum_blocksz - blocksz[0], blocksz)

    # Distribute faces equally on the sub-faces
    num_nodes = np.diff(g.face_nodes.indptr)
    normals = g.face_normals[:, subcell_topology.fno] / num_nodes[
        subcell_topology.fno]

    # Represent normals and permeability on matrix form
    normals_mat = sps.coo_matrix(
        (normals.ravel('F'), (i.ravel('F'), j.ravel('F')))).tocsr()
    k_mat = sps.coo_matrix((k.perm[::, ::, cell_node_blocks[0]].ravel('F'),
                            (i.ravel('F'), j.ravel('F')))).tocsr()

    nk = normals_mat * k_mat

    # Unique sub-cell indexes are pulled from column indices, we only need
    # every nd column (since nd faces of the cell meet at each vertex)
    sub_cell_ind = j[::, 0::nd]
    return nk, cell_node_blocks, sub_cell_ind
Exemplo n.º 7
0
    def __compute_geometry_3d(self):
        """
        Helper function to compute geometry for 3D grids

        The implementation is motivated by the similar MRST function.

        NOTE: The function is very long, and could have been broken up into
        parts (face and cell computations are an obvious solution).

        """
        xn = self.nodes
        num_face_nodes = self.face_nodes.nnz
        face_node_ptr = self.face_nodes.indptr

        num_nodes_per_face = face_node_ptr[1:] - face_node_ptr[:-1]

        # Face-node relationships. Note that the elements here will also
        # serve as a representation of an edge along the face (face_nodes[i]
        #  represents the edge running from face_nodes[i] to face_nodes[i+1])
        face_nodes = self.face_nodes.indices
        # For each node, index of its parent face
        face_node_ind = matrix_compression.rldecode(np.arange(self.num_faces),
                                                    num_nodes_per_face)

        # Index of next node on the edge list. Note that this assumes the
        # elements in face_nodes is stored in an ordered fasion
        next_node = np.arange(num_face_nodes) + 1
        # Close loops, for face i, the next node is the first of face i
        next_node[face_node_ptr[1:] - 1] = face_node_ptr[:-1]

        # Mapping from cells to faces
        edge_2_face = sps.coo_matrix(
            (np.ones(num_face_nodes), (np.arange(num_face_nodes),
                                       face_node_ind))).tocsc()

        # Define temporary face center as the mean of the face nodes
        tmp_face_center = xn[:, face_nodes] * edge_2_face / num_nodes_per_face
        # Associate this value with all the edge of this face
        tmp_face_center = edge_2_face * tmp_face_center.transpose()

        # Vector along each edge
        along_edge = xn[:, face_nodes[next_node]] - xn[:, face_nodes]
        # Vector from face center to start node of each edge
        face_2_node = tmp_face_center.transpose() - xn[:, face_nodes]

        # Assign a normal vector with this edge, by taking the cross product
        # between along_edge and face_2_node
        # Divide by two to ensure that the normal vector has length equal to
        # the area of the face triangle (by properties of cross product)
        sub_normals = np.vstack((
            along_edge[1] * face_2_node[2] - along_edge[2] * face_2_node[1],
            along_edge[2] * face_2_node[0] - along_edge[0] * face_2_node[2],
            along_edge[0] * face_2_node[1] - along_edge[1] * face_2_node[0],
        )) / 2

        def nrm(v):
            return np.sqrt(np.sum(v * v, axis=0))

        # Calculate area of sub-face associated with each edge - note that
        # the sub-normals are area weighted
        sub_areas = nrm(sub_normals)

        # Centers of sub-faces are given by the centroid coordinates,
        # e.g. the mean coordinate of the edge endpoints and the temporary
        # face center
        sub_centroids = (xn[:, face_nodes] + xn[:, face_nodes[next_node]] +
                         tmp_face_center.transpose()) / 3

        # Face normals are given as the sum of the sub-components
        face_normals = sub_normals * edge_2_face
        # Similar with face areas
        face_areas = edge_2_face.transpose() * sub_areas

        # Test whether the sub-normals are pointing in the same direction as
        # the main normal: Distribute the main normal onto the edges,
        # and take scalar product by element-wise multiplication with
        # sub-normals, and sum over the components (axis=0).
        # NOTE: There should be a built-in function for this in numpy?
        sub_normals_sign = np.sign(
            np.sum(sub_normals *
                   (edge_2_face * face_normals.transpose()).transpose(),
                   axis=0))

        # Finally, face centers are the area weighted means of centroids of
        # the sub-faces
        face_centers = sub_areas * sub_centroids * edge_2_face / face_areas

        # .. and we're done with the faces. Store information
        self.face_centers = face_centers
        self.face_normals = face_normals
        self.face_areas = face_areas

        # Cells

        # Temporary cell center coordinates as the mean of the face center
        # coordinates. The cells are divided into sub-tetrahedra (
        # corresponding to triangular sub-faces above), with the temporary
        # cell center as the final node

        # Mapping from edges to cells. Take absolute value of cell_faces,
        # since the elements are signed (contains the divergence).
        # Note that edge_2_cell will contain more elements than edge_2_face,
        # since the former will count internal faces twice (one for each
        # adjacent cell)
        edge_2_cell = edge_2_face * np.abs(self.cell_faces)
        # Sort indices to avoid messing up the mappings later
        edge_2_cell.sort_indices()

        # Obtain relations between edges, faces and cells, in the form of
        # index lists. Each element in the list corresponds to an edge seen
        # from a cell (e.g. edges on internal faces are seen twice).

        # Cell numbers are obtained from the columns in edge_2_cell.
        cell_numbers = matrix_compression.rldecode(np.arange(self.num_cells),
                                                   np.diff(edge_2_cell.indptr))
        # Edge numbers from the rows. Here it is crucial that the indices
        # are sorted
        edge_numbers = edge_2_cell.indices
        # Face numbers are obtained from the face-node relations (with the
        # nodes doubling as representation of edges)
        face_numbers = face_node_ind[edge_numbers]

        # Number of edges per cell
        num_cell_edges = edge_2_cell.indptr[1:] - edge_2_cell.indptr[:-1]

        def bincount_nd(arr, weights):
            """ Utility function to sum vector quantities by np.bincount. We
            could probably have used np.apply_along_axis, but I could not
            make it work.

            Intended use: Map sub-cell centroids to a quantity for the cell.
            """
            dim = weights.shape[0]
            sz = arr.max() + 1

            count = np.zeros((dim, sz))
            for iter1 in range(dim):
                count[iter1] = np.bincount(arr,
                                           weights=weights[iter1],
                                           minlength=sz)
            return count

        # First estimate of cell centers as the mean of its faces' centers
        # Divide by num_cell_edges here since all edges bring in their faces
        tmp_cell_centers = bincount_nd(
            cell_numbers,
            face_centers[:, face_numbers] / num_cell_edges[cell_numbers])

        # Distance from the temporary cell center to the sub-centroids (of
        # the tetrahedra associated with each edge)
        dist_cellcenter_subface = sub_centroids[:, edge_numbers] \
            - tmp_cell_centers[:, cell_numbers]

        # Get sign of normal vectors, seen from all faces.
        # Make sure we get a numpy ndarray, and not a matrix (.A), and that
        # the array is 1D (squeeze)
        orientation = np.squeeze(self.cell_faces[face_numbers, cell_numbers].A)

        # Get outwards pointing sub-normals for all sub-faces: We need to
        # account for both the orientation of the face, and the orientation
        # of sub-faces relative to faces.
        outer_normals = sub_normals[:, edge_numbers] \
            * orientation * sub_normals_sign[edge_numbers]

        # Volumes of tetrahedra are now given by the dot product between the
        #  outer normal (which is area weighted, and thus represent the base
        #  of the tet), with the distancance from temporary cell center (the
        # dot product gives the hight).
        tet_volumes = np.sum(dist_cellcenter_subface * outer_normals,
                             axis=0) / 3

        # Sometimes the sub-tet volumes can have a volume of numerical zero.
        # Why this is so is not clear, but for the moment, we allow for a
        # slightly negative value.
        assert np.all(tet_volumes > -1e-12)  # On the fly test

        # The cell volumes are now found by summing sub-tetrahedra
        cell_volumes = np.bincount(cell_numbers, weights=tet_volumes)
        tri_centroids = 3 / 4 * dist_cellcenter_subface

        # Compute a correction to the temporary cell center, by a volume
        # weighted sum of the sub-tetrahedra
        rel_centroid = bincount_nd(cell_numbers, tet_volumes * tri_centroids) \
            / cell_volumes
        cell_centers = tmp_cell_centers + rel_centroid

        # ... and we're done
        self.cell_centers = cell_centers
        self.cell_volumes = cell_volumes
Exemplo n.º 8
0
def generate_coarse_grid(g, subdiv):
    """ Generate a coarse grid clustering the cells according to the flags
    given by subdiv. Subdiv should be long as the number of cells in the
    original grid, it contains integers (possibly not continuous) which
    represent the cells in the final mesh.

    The values computed in "compute_geometry" are not preserved and they should
    be computed out from this function.

    Note: there is no check for disconnected cells in the final grid.

    Parameters:
    g: the grid
    subdiv: a list of flags, one for each cell of the original grid

    How to use:
    subdiv = np.array([0,0,1,1,1,1,3,4,6,4,6,4])
    g = generate_coarse_grid( g, subdiv )

    """

    subdiv = np.asarray(subdiv)
    assert (subdiv.size == g.num_cells)

    # declare the storage array to build the cell_faces map
    cell_faces = np.empty(0, dtype=g.cell_faces.indptr.dtype)
    cells = np.empty(0, dtype=cell_faces.dtype)
    orient = np.empty(0, dtype=g.cell_faces.data.dtype)

    # declare the storage array to build the face_nodes map
    face_nodes = np.empty(0, dtype=g.face_nodes.indptr.dtype)
    nodes = np.empty(0, dtype=face_nodes.dtype)
    visit = np.zeros(g.num_faces, dtype=np.bool)

    # compute the face_node indexes
    num_nodes_per_face = g.face_nodes.indptr[1:] - g.face_nodes.indptr[:-1]
    face_node_ind = matrix_compression.rldecode(np.arange(g.num_faces), \
                                                num_nodes_per_face)

    cells_list = np.unique(subdiv)
    for cellId, cell in enumerate(cells_list):
        # extract the cells of the original mesh associated to a specific label
        cells_old = np.where(subdiv == cell)[0]

        # reconstruct the cell_faces mapping
        faces_old, _, orient_old = sps.find(g.cell_faces[:, cells_old])
        mask = np.ones(faces_old.size, dtype=np.bool)
        mask[np.unique(faces_old, return_index=True)[1]] = False
        # extract the indexes of the internal edges, to be discared
        index = np.array([ np.where( faces_old == f )[0] \
                                              for f in faces_old[mask]]).ravel()
        faces_new = np.delete(faces_old, index)
        cell_faces = np.r_[cell_faces, faces_new]
        cells = np.r_[cells, np.repeat(cellId, faces_new.shape[0])]
        orient = np.r_[orient, np.delete(orient_old, index)]

        # reconstruct the face_nodes mapping
        # consider only the unvisited faces
        not_visit = ~visit[faces_new]
        if not_visit.size == 0: continue
        # mask to consider only the external faces
        mask = np.sum( [ face_node_ind == f for f in faces_new[not_visit] ], \
                        axis = 0, dtype = np.bool )
        face_nodes = np.r_[face_nodes, face_node_ind[mask]]
        nodes_new = g.face_nodes.indices[mask]
        nodes = np.r_[nodes, nodes_new]
        visit[faces_new] = True

    # Rename the faces
    cell_faces_unique = np.unique(cell_faces)
    cell_faces_id = np.arange(cell_faces_unique.size, dtype=cell_faces.dtype)
    cell_faces = np.array([cell_faces_id[np.where( cell_faces_unique == f )[0]]\
                                                   for f in cell_faces]).ravel()

    shape = (cell_faces_unique.size, cells_list.size)
    cell_faces = sps.csc_matrix((orient, (cell_faces, cells)), shape=shape)

    # Rename the nodes
    face_nodes = np.array([cell_faces_id[np.where( cell_faces_unique == f )[0]]\
                                                   for f in face_nodes]).ravel()
    nodes_list = np.unique(nodes)
    nodes_id = np.arange(nodes_list.size, dtype=nodes.dtype)
    nodes = np.array([nodes_id[np.where( nodes_list == n )[0]] \
                                                        for n in nodes]).ravel()

    # sort the nodes
    nodes = nodes[np.argsort(face_nodes, kind='mergesort')]
    data = np.ones(nodes.size, dtype=g.face_nodes.data.dtype)
    indptr = np.r_[0, np.cumsum(np.bincount(face_nodes))]
    face_nodes = sps.csc_matrix((data, nodes, indptr))

    name = g.name
    name.append("coarse")
    return Grid(g.dim, g.nodes[:, nodes_list], face_nodes, cell_faces, name)