Example #1
0
 def test_1d_A_2d_n(self):
     A = np.array([7,8,9])
     n = np.array([[2],[3], [0]])
     B1 = rldecode(A, n, axis=0)
     assert np.array_equal(B1, np.array([7, 7, 8, 8, 8]))
     # A only has one axis, so axis=1 does not make sense
     with pytest.raises(ValueError):
         B2 = rldecode(A, n, axis=1)
Example #2
0
def getCellNoFaces(G):
    """
    Get a list of all half faces, accounting for possible NNC.

    Synopsis:
        cellNo, cellFaces, isNNC = getCellNoFaces(G)

    Description:
        This utility function is used to produce a listing of all half faces in
        a grid along with the respective cells they belong to. While relatively
        trivial for most grids, this function specifically accounts for
        non-neighboring connections / NNC.

    Arguments:
        G (Grid):
            Grid structure with optional .nnc.cells attribute.

    Returns:
        cellNo (ndarray):
            Column array with shape (M,1) where M is the number of geometric
            half-faces + 2 * number of NNC, where each entry corresponds to
            cell index of that half face.

        cellFaces (ndarray):
            Column array with shape (M,1) with M as above, where each entry is
            the connection index. For the first entries, this is simply the
            face number. Otherwise, it is the entry of the NNC connection.

    See also:
        prst.utils.rldecode
    """
    import prst.utils as utils  # circular import

    cellNo = utils.rldecode(np.arange(G.cells.num),
                            np.diff(G.cells.facePos, axis=0))[:, np.newaxis]
    cellFaces = G.cells.faces[:, 0][:, np.newaxis]

    # Mapping to show which entries in cellNo/cellFaces are resulting from
    # NNC and not actual geometric faces.
    isNNC = np.zeros((len(cellNo), 1), dtype=np.bool)

    # If NNC is present, we add these extra connections to cellNo
    # and cellFaces to allow consisten treatment.
    if hasattr(G, 'nnc') and hasattr(G.nnc, "cells"):
        prst.warning(
            "getCellNoFaces is untested for grids with NNC. Compare results with MRST."
        )
        # Stack columns into a single column
        nnc_cells = np.r_[G.nnc.cells[:, 0], G.nnc.cells[:, 1]][:, np.newaxis]
        # NNC is located at the end after the regular faces
        nnc_faceno = G.faces.num + np.arange(G.nnc.cells.shape[0])[:,
                                                                   np.newaxis]
        cellNo = np.r_[cellNo, nnc_cells]  # Stack as 2d column
        cellFaces = np.r_[cellFaces, nnc_faceno, nnc_faceno]  # Stack as column
        # Added connections are NNC
        isNNC = np.r_[isNNC, np.ones((len(nnc_cells), 1), dtype=np.bool)]
    return cellNo, cellFaces, isNNC
Example #3
0
def getCellNoFaces(G):
    """
    Get a list of all half faces, accounting for possible NNC.

    Synopsis:
        cellNo, cellFaces, isNNC = getCellNoFaces(G)

    Description:
        This utility function is used to produce a listing of all half faces in
        a grid along with the respective cells they belong to. While relatively
        trivial for most grids, this function specifically accounts for
        non-neighboring connections / NNC.

    Arguments:
        G (Grid):
            Grid structure with optional .nnc.cells attribute.

    Returns:
        cellNo (ndarray):
            Column array with shape (M,1) where M is the number of geometric
            half-faces + 2 * number of NNC, where each entry corresponds to
            cell index of that half face.

        cellFaces (ndarray):
            Column array with shape (M,1) with M as above, where each entry is
            the connection index. For the first entries, this is simply the
            face number. Otherwise, it is the entry of the NNC connection.

    See also:
        prst.utils.rldecode
    """
    import prst.utils as utils # circular import

    cellNo = utils.rldecode(np.arange(G.cells.num), np.diff(G.cells.facePos,axis=0))[:,np.newaxis]
    cellFaces = G.cells.faces[:,0][:,np.newaxis]

    # Mapping to show which entries in cellNo/cellFaces are resulting from
    # NNC and not actual geometric faces.
    isNNC = np.zeros((len(cellNo), 1), dtype=np.bool)

    # If NNC is present, we add these extra connections to cellNo
    # and cellFaces to allow consisten treatment.
    if hasattr(G, 'nnc') and hasattr(G.nnc, "cells"):
        prst.warning("getCellNoFaces is untested for grids with NNC. Compare results with MRST.")
        # Stack columns into a single column
        nnc_cells = np.r_[G.nnc.cells[:,0], G.nnc.cells[:,1]][:,np.newaxis]
        # NNC is located at the end after the regular faces
        nnc_faceno = G.faces.num + np.arange(G.nnc.cells.shape[0])[:,np.newaxis]
        cellNo = np.r_[cellNo, nnc_cells] # Stack as 2d column
        cellFaces = np.r_[cellFaces, nnc_faceno, nnc_faceno] # Stack as column
        # Added connections are NNC
        isNNC = np.r_[isNNC, np.ones((len(nnc_cells),1), dtype=np.bool)]
    return cellNo, cellFaces, isNNC
Example #4
0
def _grav_pressure(G, omega):
    """Computes innerproduct cf (face_centroid - cell_centroid) * g for each face"""
    g_vec = prst.gravity
    if np.linalg.norm(g_vec[:G.gridDim]) > 0:
        dim = G.gridDim
        assert 1 <= dim <= 3, "Wrong grid dimension"

        cellno = utils.rldecode(np.arange(G.cells.num), np.diff(G.cells.facePos, axis=0))
        cvec = G.faces.centroids[G.cells.faces[:,0],:] - G.cells.centroids[cellno,:]
        ff = omega[cellno] * np.dot(cvec, g_vec[:dim].reshape([3,1]))
    else:
        ff = np.zeros([G.cells.faces.shape[0], 1])
    return ff
Example #5
0
def computeGeometry(G, findNeighbors=False, hingenodes=None):
    """
    Compute and add geometry attributes to grid object.

    Synopsis:
        G = computeGeometry(G)

    Arguments:
        G (Grid): Grid object. Input argument is mutated.

        findNeighbors (Optional[bool]):
            Force finding the neighbors array even if it exists. Defaults to
            False.


    Returns:
        G - Grid object with added attributes:
            - cells
                - volumes   -- An array with size G.cells.num of cell volumes.

                - centroids -- An array with shape (G.cells.num, G.gridDim)
                               of approximate cell centroids.

            - faces
                - areas     -- An array with size G.faces.num of face areas.

                - normals   -- An array with shape (G.faces.num, G.gridDim)
                               of face normals

                - centroids -- An array with shape (G.faces.num, G.griddim) of
                               approximate face centroids.

    Individual face normals have length (i.e. Euclidean norm) equal to the
    corresponding face areas. In other words, subject to numerical round-off,
    the identity

        np.linalg.norm(G.faces.normals[i,:], 2) == G.faces.areas[i]

    holds for all faces i in range(0, G.faces.num) .

    In three space dimensions, i.e. when G.gridDim == 3, the function
    `computeGeometry` assumes that the nodes on a given face `f` are ordered
    such that the face normal on `f` is directed from cell
    `G.faces.neighbors[f,0]` to cell `G.faces.neighbors[f,1]`.

    """

    ## Setup
    assert hingenodes is None or G.gridDim == 3,\
           "Hinge nodes are only supported for 3D grids"

    numCells = G.cells.num
    numFaces = G.faces.num

    ## Possibly find neighbors
    if findNeighbors:
        G.faces.neighbors = _findNeighbors(G)
        G = _findNormalDirections(G)
    else:
        if not hasattr(G.faces, "neighbors"):
            import prst
            prst.log.warn("No field faces.neighbors found. "
                   + "Adding plausible values... proceed with caution!")
            G.faces.neighbors = _findNeighbors(G)
            G = _findNormalDirections(G)

    ## Main part
    if G.gridDim == 3:
        ## 3D grid
        assert G.nodes.coords.shape[1] == 3
        faceNumbers = utils.rldecode(np.arange(G.faces.num), np.diff(G.faces.nodePos,axis=0))
        nodePos = G.faces.nodePos;
        nextNode = np.arange(1, G.faces.nodes.size+1)
        nextNode[nodePos[1:,0]-1] = nodePos[:-1,0]

        # Divide each face into sub-triangles all having one node as pCenter =
        # sum(nodes) / numNodes. Compute area-weighted normals, and add to
        # obtain approx face-normals. Compute resulting areas and centroids.
        import prst
        prst.log.info("Computing normals, areas and centroids...")
        # Construct a sparse matrix with zeros and ones.
        localEdge2Face = scipy.sparse.csc_matrix((
                np.ones(G.faces.nodes.size),
                (np.arange(G.faces.nodes.size), faceNumbers)
            ))
        # Divide each row in the matrix product by the numbers in the final
        # array elementwise
        pCenters = (localEdge2Face.transpose().dot(
                G.nodes.coords[G.faces.nodes[:,0]]
            )[None,:] / np.diff(G.faces.nodePos,axis=0))[0]
        pCenters = localEdge2Face.dot(pCenters)

        if hingenodes:
            raise NotImplementedError("hingenodes are not yet supported in PRST")

        subNormals = np.cross(
                  G.nodes.coords[G.faces.nodes[nextNode,0],:]
              - G.nodes.coords[G.faces.nodes[:,0],:],
                  pCenters - G.nodes.coords[G.faces.nodes[:,0],:]) / 2

        subAreas = np.linalg.norm(subNormals, axis=1)

        subCentroids = (G.nodes.coords[G.faces.nodes[:,0],:]
            + G.nodes.coords[G.faces.nodes[nextNode,0],:] + pCenters) / 3

        faceNormals = localEdge2Face.transpose().dot(subNormals)

        faceAreas = localEdge2Face.transpose().dot(subAreas)

        subNormalSigns = np.sign(np.sum(
            subNormals * (localEdge2Face * faceNormals), axis=1))

        faceCentroids = localEdge2Face.transpose().dot(
            subAreas[:,np.newaxis] * subCentroids
        ) / faceAreas[:,np.newaxis]

        # Computation above does not make sense for faces with zero area
        zeroAreaIndices = np.where(faceAreas <= 0)
        if np.any(zeroAreaIndices):
            import prst
            prst.log.warning("Faces with zero area detected. Such faces should be"
                      + "removed before calling computeGeometry")

        # Divide each cell into sub-tetrahedra according to sub-triangles above,
        # all having one node as cCenter = sum(faceCentroids) / #faceCentroids
        import prst
        prst.log.info("Computing cell volumes and centroids")
        cellVolumes = np.zeros(numCells)
        cellCentroids = np.zeros([numCells, 3])

        lastIndex = 0
        for cell in range(numCells):
            # Number of faces for the current cell
            cellNumFaces = G.cells.facePos[cell+1] - G.cells.facePos[cell]
            indexes = np.arange(cellNumFaces) + lastIndex

            # The indices to the faces for the current cell
            cellFaces = G.cells.faces[indexes,0]
            # triE are the row indices of the non-zero elements in the matrix, while
            # triF are the col indices of the same elements.
            # Original code based on MRST:
            #
            #   tmp = localEdge2Face[:,cellFaces]
            #   triEa, triFa = tmp.nonzero()
            #
            # Was very slow, so a custom function for extracting columns for a
            # csc_matrix was created. It is not fully tested, but is 4x faster.
            triF, triE = _csc_columns_nonzero(localEdge2Face.indptr,
                                              localEdge2Face.indices, cellFaces)


            cellFaceCentroids = faceCentroids[cellFaces,:]
            cellCenter = np.sum(cellFaceCentroids, axis=0) / cellNumFaces
            relSubC = subCentroids[triE,:] - cellCenter[np.newaxis,:]

            # The normal of a face f is directed from cell G.faces.neighbors[f,0]
            # to cell G.faces.neighbors[f,1].  If cell c is in the second column
            # for face f, then the normal must be multiplied by -1 to be an outer
            # normal.
            orientation = 2 * (G.faces.neighbors[G.cells.faces[indexes,0], 0] == cell) - 1

            outNormals = subNormals[triE,:] * (
                (subNormalSigns[triE] * orientation[triF])[:,np.newaxis]
            )
            tetraVolumes = (1/3) * np.sum(relSubC * outNormals, axis=1)
            tetraCentroids = (3/4) * relSubC;

            cellVolume = np.sum(tetraVolumes)
            # Warning: This expression can be very close to zero, and often differs from
            # the same calculation in MATLAB.
            relCentroid = (tetraVolumes.dot(tetraCentroids)) / cellVolume
            cellCentroid = relCentroid + cellCenter
            cellVolumes[cell] = cellVolume
            cellCentroids[cell,:] = cellCentroid

            lastIndex += cellNumFaces

    elif G.gridDim == 2 and G.nodes.coords.shape[1] == 2:
        # Sometimes G.cells.faces has a second column with face directions.
        # So we retrieve the index column only.
        cellFaces = G.cells.faces[:,0]
        ## 2D grid in 2D space
        import prst
        prst.log.info("Computing normals, areas and centroids")
        edges = G.faces.nodes.reshape([-1,2], order="C")
        # Distance between edge nodes as a vector. "Length" is misleading.
        edgeLength =   G.nodes.coords[edges[:,1],:] \
                     - G.nodes.coords[edges[:,0],:]

        # Since this is 2D, these are actually lengths
        faceAreas = np.linalg.norm(edgeLength, axis=1)
        faceCentroids = np.average(G.nodes.coords[edges], axis=1)
        faceNormals = np.column_stack([edgeLength[:,1], -edgeLength[:,0]])
        import prst
        prst.log.info("Computing cell volumes and centroids")
        numFaces = np.diff(G.cells.facePos, axis=0)[:,0]
        cellNumbers = prst.utils.rldecode(np.arange(G.cells.num), numFaces)
        cellEdges = edges[cellFaces,:]
        r = G.faces.neighbors[cellFaces, 1] == cellNumbers
        # swap the two columns
        cellEdges[r, 0], cellEdges[r, 1] = cellEdges[r, 1], cellEdges[r, 0]

        cCenter = np.zeros([G.cells.num, 2])

        # npg.aggregate is similar to accumarray in MATLAB
        cCenter[:,0] = aggregate(cellNumbers,
                faceCentroids[cellFaces, 0]) / numFaces

        cCenter[:,1] = aggregate(cellNumbers,
                faceCentroids[cellFaces, 1]) / numFaces

        a = G.nodes.coords[cellEdges[:,0],:] - cCenter[cellNumbers,:]
        b = G.nodes.coords[cellEdges[:,1],:] - cCenter[cellNumbers,:]
        subArea = 0.5 * (a[:,0]*b[:,1] - a[:,1] * b[:,0])

        subCentroid = (  cCenter[cellNumbers,:]
                       + 2*faceCentroids[cellFaces,:])/3
        cellVolumes = aggregate(cellNumbers, subArea)

        cellCentroids = np.zeros([G.cells.num, 2], dtype=np.float64)

        cellCentroids[:,0] = aggregate(
                cellNumbers, subArea * subCentroid[:,0]) / cellVolumes

        cellCentroids[:,1] = aggregate(
                cellNumbers, subArea * subCentroid[:,1]) / cellVolumes

    elif G.gridDim == 2 and G.nodes.coords.shape[1] == 3:
        ## 2D grid in 3D space
        raise NotImplementedError(
                "computeGeometry not yet implemented for surface grids")
    else:
        raise ValueError("gridDim or nodes.coords have invalid values")

    ## Update grid
    G.faces.areas = faceAreas[:,np.newaxis]
    G.faces.normals = faceNormals
    G.faces.centroids = faceCentroids
    G.cells.volumes = cellVolumes[:,np.newaxis]
    G.cells.centroids = cellCentroids

    if not hasattr(G, "gridType"):
        import prst
        prst.log.warning("Input grid has no type")
        G.gridType = []

    G.gridType.append("computeGeometry")

    return G
Example #6
0
def computeTrans(G, rock, K_system="xyz", cellCenters=None, cellFaceCenters=None,
        verbose=False):
    """
    Compute transmissibilities for a grid.

    Synopsis:
        T = computeTrans(G, rock)
        T = computeTrans(G, rock, **kwargs)

    Arguments:
        G (Grid):
            prst.gridprocessing.Grid instance.

        rock (Rock):
            prst.params.rock.Rock instance with `perm` attribute. The
            permeability is assumed to be in units of metres squared (m^2).
            Use constant `darcy` from prst.utils.units to convert to m^2, e.g.,

                from prst.utils.units import *
                perm = convert(perm, from_=milli*darcy, to=meter**2)

            if the permeability is provided in units of millidarcies.

            The field rock.perm may have ONE column for a scalar permeability in each cell,
            TWO/THREE columns for a diagonal permeability in each cell (in 2/D
            D) and THREE/SIX columns for a symmetric full tensor permeability.
            In the latter case, each cell gets the permability tensor.

                K_i = [ k1  k2 ]      in two space dimensions
                      [ k2  k3 ]

                K_i = [ k1  k2  k3 ]  in three space dimensions
                      [ k2  k4  k5 ]
                      [ k3  k5  k6 ]

        K_system (Optional[str]):
            The system permeability. Valid values are "xyz" and "loc_xyz".

        cellCenters (Optional[ndarray]):
            Compute transmissibilities based on supplied cellCenters rather
            than default G.cells.centroids. Must have shape (n,2) for 2D and
            (n,3) for 3D.

        cellFaceCenters (Optional[ndarray]):
            Compute transmissibilities based on supplied cellFaceCenters rather
            than default `G.faces.centroids[G.cells.faces[:,0], :]`.

    Returns:
        T: Half-transmissibilities for each local face of each grid cell in the
        grid. The number of half-transmissibilities equals the number of rows
        in `G.cells.faces`. 2D column array.

    Comments:
        PLEASE NOTE: Face normals are assumed to have length equal to the
        corresponding face areas. This property is guaranteed by function
        `computeGeometry`.

    See also:
        computeGeometry, computeMimeticIP (MRST), darcy, permTensor, Rock
    """
    if K_system not in ["xyz", "loc_xyz"]:
        raise TypeError(
            "Specified permeability coordinate system must be a 'xyz' or 'loc_xyz'")

    if verbose:
        print("Computing one-sided transmissibilites.")

    # Vectors from cell centroids to face centroids
    assert G.cells.facePos.ndim == 2, "facePos has wrong dimensions"
    cellNo = rldecode(np.arange(G.cells.num), np.diff(G.cells.facePos, axis=0))
    if cellCenters is None:
        C = G.cells.centroids
    else:
        C = cellCenters
    if cellFaceCenters is None:
        C = G.faces.centroids[G.cells.faces[:,0],:] - C[cellNo,:]
    else:
        C = cellFaceCenters - C[cellNo,:]

    # Normal vectors
    sgn = 2*(cellNo == G.faces.neighbors[G.cells.faces[:,0], 0]) - 1
    N = sgn[:,np.newaxis] * G.faces.normals[G.cells.faces[:,0],:]

    if K_system == "xyz":
        K, i, j = permTensor(rock, G.gridDim, rowcols=True)
        assert K.shape[0] == G.cells.num, \
            "Permeability must be defined in active cells only.\n"+\
            "Got {} tensors, expected {} (== num cells)".format(K.shape[0], G.cells.num)

        # Compute T = C'*K*N / C'*C. Loop based to limit memory use.
        T = np.zeros(cellNo.size)
        for k in range(i.size):
            tmp = C[:,i[k]] * K[cellNo,k] * N[:,j[k]]
            # Handle both 1d and 2d array.
            if tmp.ndim == 1:
                T += tmp
            else:
                T += np.sum(tmp, axis=1)
        T = T / np.sum(C*C, axis=1)

    elif K_system == "loc_xyz":
        if rock.perm.shape[1] == 1:
            rock.perm = np.tile(rock.perm, (1, G.gridDim))
        if rock.perm.shape[1] != G.cartDims.size:
            raise ValueError(
                "Permeability coordinate system `loc_xyz` is only "+\
                "valid for diagonal tensor.")
        assert rock.perm.shape[0] == G.cells.num,\
            "Permeability must be defined in active cells only. "+\
            "Got {} tensors, expected {} == (num cells)".format(rock.perm.shape[0], G.cells.num)

        dim = np.ceil(G.cells.faces[:,1] / 2)
        raise NotImplementedError("Function not finished for K_system='loc_xyz'")
        # See MRST, solvers/computeTrans.m

    else:
        raise ValueError("Unknown permeability coordinate system {}.".format(K_system))

    is_neg = T < 0
    if np.any(is_neg):
        if verbose:
            prst.log.warn("Warning: {} negative transmissibilities. ".format(np.sum(is_neg))+
                          "Replaced by absolute values...")
            T[is_neg] = -T[is_neg]

    return np.atleast_2d(T).transpose()
Example #7
0
def computeTrans(G,
                 rock,
                 K_system="xyz",
                 cellCenters=None,
                 cellFaceCenters=None,
                 verbose=False):
    """
    Compute transmissibilities for a grid.

    Synopsis:
        T = computeTrans(G, rock)
        T = computeTrans(G, rock, **kwargs)

    Arguments:
        G (Grid):
            prst.gridprocessing.Grid instance.

        rock (Rock):
            prst.params.rock.Rock instance with `perm` attribute. The
            permeability is assumed to be in units of metres squared (m^2).
            Use constant `darcy` from prst.utils.units to convert to m^2, e.g.,

                from prst.utils.units import *
                perm = convert(perm, from_=milli*darcy, to=meter**2)

            if the permeability is provided in units of millidarcies.

            The field rock.perm may have ONE column for a scalar permeability in each cell,
            TWO/THREE columns for a diagonal permeability in each cell (in 2/D
            D) and THREE/SIX columns for a symmetric full tensor permeability.
            In the latter case, each cell gets the permability tensor.

                K_i = [ k1  k2 ]      in two space dimensions
                      [ k2  k3 ]

                K_i = [ k1  k2  k3 ]  in three space dimensions
                      [ k2  k4  k5 ]
                      [ k3  k5  k6 ]

        K_system (Optional[str]):
            The system permeability. Valid values are "xyz" and "loc_xyz".

        cellCenters (Optional[ndarray]):
            Compute transmissibilities based on supplied cellCenters rather
            than default G.cells.centroids. Must have shape (n,2) for 2D and
            (n,3) for 3D.

        cellFaceCenters (Optional[ndarray]):
            Compute transmissibilities based on supplied cellFaceCenters rather
            than default `G.faces.centroids[G.cells.faces[:,0], :]`.

    Returns:
        T: Half-transmissibilities for each local face of each grid cell in the
        grid. The number of half-transmissibilities equals the number of rows
        in `G.cells.faces`. 2D column array.

    Comments:
        PLEASE NOTE: Face normals are assumed to have length equal to the
        corresponding face areas. This property is guaranteed by function
        `computeGeometry`.

    See also:
        computeGeometry, computeMimeticIP (MRST), darcy, permTensor, Rock
    """
    if K_system not in ["xyz", "loc_xyz"]:
        raise TypeError(
            "Specified permeability coordinate system must be a 'xyz' or 'loc_xyz'"
        )

    if verbose:
        print("Computing one-sided transmissibilites.")

    # Vectors from cell centroids to face centroids
    assert G.cells.facePos.ndim == 2, "facePos has wrong dimensions"
    cellNo = rldecode(np.arange(G.cells.num), np.diff(G.cells.facePos, axis=0))
    if cellCenters is None:
        C = G.cells.centroids
    else:
        C = cellCenters
    if cellFaceCenters is None:
        C = G.faces.centroids[G.cells.faces[:, 0], :] - C[cellNo, :]
    else:
        C = cellFaceCenters - C[cellNo, :]

    # Normal vectors
    sgn = 2 * (cellNo == G.faces.neighbors[G.cells.faces[:, 0], 0]) - 1
    N = sgn[:, np.newaxis] * G.faces.normals[G.cells.faces[:, 0], :]

    if K_system == "xyz":
        K, i, j = permTensor(rock, G.gridDim, rowcols=True)
        assert K.shape[0] == G.cells.num, \
            "Permeability must be defined in active cells only.\n"+\
            "Got {} tensors, expected {} (== num cells)".format(K.shape[0], G.cells.num)

        # Compute T = C'*K*N / C'*C. Loop based to limit memory use.
        T = np.zeros(cellNo.size)
        for k in range(i.size):
            tmp = C[:, i[k]] * K[cellNo, k] * N[:, j[k]]
            # Handle both 1d and 2d array.
            if tmp.ndim == 1:
                T += tmp
            else:
                T += np.sum(tmp, axis=1)
        T = T / np.sum(C * C, axis=1)

    elif K_system == "loc_xyz":
        if rock.perm.shape[1] == 1:
            rock.perm = np.tile(rock.perm, (1, G.gridDim))
        if rock.perm.shape[1] != G.cartDims.size:
            raise ValueError(
                "Permeability coordinate system `loc_xyz` is only "+\
                "valid for diagonal tensor.")
        assert rock.perm.shape[0] == G.cells.num,\
            "Permeability must be defined in active cells only. "+\
            "Got {} tensors, expected {} == (num cells)".format(rock.perm.shape[0], G.cells.num)

        dim = np.ceil(G.cells.faces[:, 1] / 2)
        raise NotImplementedError(
            "Function not finished for K_system='loc_xyz'")
        # See MRST, solvers/computeTrans.m

    else:
        raise ValueError(
            "Unknown permeability coordinate system {}.".format(K_system))

    is_neg = T < 0
    if np.any(is_neg):
        if verbose:
            prst.log.warn("Warning: {} negative transmissibilities. ".format(
                np.sum(is_neg)) + "Replaced by absolute values...")
            T[is_neg] = -T[is_neg]

    return np.atleast_2d(T).transpose()