def test_numpy_reduce_nd_md():
    array = np.ones((2, 12))
    by = np.array([labels] * 2)

    expected = aggregate(by.ravel(), array.ravel(), func="sum")
    result, groups = groupby_reduce(array, by, func="sum", fill_value=123)
    actual = reindex_(result, groups, np.unique(by), axis=0, fill_value=0)
    np.testing.assert_equal(expected, actual)

    array = np.ones((4, 2, 12))
    by = np.array([labels] * 2)

    expected = aggregate(by.ravel(),
                         array.reshape(4, 24),
                         func="sum",
                         axis=-1,
                         fill_value=0)
    result, groups = groupby_reduce(array, by, func="sum")
    actual = reindex_(result, groups, np.unique(by), axis=-1, fill_value=0)
    assert_equal(expected, actual)

    array = np.ones((4, 2, 12))
    by = np.broadcast_to(np.array([labels] * 2), array.shape)
    expected = aggregate(by.ravel(), array.ravel(), func="sum", axis=-1)
    result, groups = groupby_reduce(array, by, func="sum")
    actual = reindex_(result, groups, np.unique(by), axis=-1, fill_value=0)
    assert_equal(expected, actual)
def test_bad_npg_behaviour():
    labels = np.array([0, 0, 2, 2, 2, 1, 1, 2, 2, 1, 1, 0], dtype=int)
    # fmt: off
    array = np.array([[1] * 12, [1] * 12])
    # fmt: on
    assert_equal(aggregate(labels, array, axis=-1, func="argmax"),
                 np.array([[0, 5, 2], [0, 5, 2]]))

    assert (aggregate(np.array([0, 1, 2, 0, 1, 2]),
                      np.array([-np.inf, 0, 0, -np.inf, 0, 0]),
                      func="max")[0] == -np.inf)
예제 #3
0
파일: __init__.py 프로젝트: zwgzwz/PRST
def _compute_trans(G, T, cellNo, cellFaces, neighborship, totmob, use_trans):
    niface = neighborship.shape[0]
    if use_trans:
        neighborcount = np.sum(neighborship != -1, axis=1, keepdims=True)
        assert T.shape[0] == niface, \
            "Expected one transmissibility for each interface " + \
            "(={}) but got {}".format(niface, T.shape[0])
        raise NotImplementedError("Function not yet implemented for use_trans=True. See source code.")
        # Matlab code for rest of function, from mrst-2015b\modules\incomp\incompTPFA.m
        #fmob = accumarray(cellFaces, totmob(cellNo), ...
        #                    [niface, 1]);
        #
        #fmob = fmob ./ neighborcount;
        #ft   = T .* fmob;
        #
        #% Synthetic one-sided transmissibilities.
        #th = ft .* neighborcount;
        #T  = th(cellFaces(:,1));
    else:
        # Define face transmissibility as harmonic average of mobility 
        # weighted one-sided transmissibilities.
        assert T.shape[0] == cellNo.shape[0], \
            "Expected one one-sided transmissibility for each " +\
            "half face (={}), but got {}.".format(cellNo.shape[0], T.shape[0])

        T = T * totmob[cellNo[:,0],:]
        from numpy_groupies.aggregate_numpy import aggregate
        ft = 1/aggregate(cellFaces[:,0], 1/T[:,0], size=niface)
    return T, ft
예제 #4
0
def _findNormalDirections(G):
    """
    Detects neighborship based on normal directions.
    """
    if G.gridDim != 3 or G.nodes.coords.shape[1] != 3:
        raise ValueError("Detecting neighborship based on normal directions "
                        +"is only supported for 3D grids.")

    # Assume convex faces. Compute average of node coordinates.
    faceCenters = _averageCoordinates(np.diff(G.faces.nodePos, axis=0),
                G.nodes.coords[G.faces.nodes[:,0],:])[0]

    cellCenters, cellNumbers = _averageCoordinates(
        np.diff(G.cells.facePos, axis=0), faceCenters[G.cells.faces[:,0], :])

    # Compute triple product v1 x v2 . v3 of vectors
    # v1 = faceCenters - cellCenters
    # v2 = n1 - fc
    # v3 = n2 - n1
    # n1 and n2 being the first and second nodes of the face. Triple product
    # should be positive for half-faces with positive sign.

    nodes1 = G.nodes.coords[G.faces.nodes[:,0][G.faces.nodePos[:-1,0]    ], :]
    nodes2 = G.nodes.coords[G.faces.nodes[:,0][G.faces.nodePos[:-1,0] + 1], :]

    v1 = faceCenters[G.cells.faces[:,0], :] - cellCenters[cellNumbers]
    v2 = nodes1[G.cells.faces[:,0], :] - faceCenters[G.cells.faces[:,0], :]
    v3 = nodes2[G.cells.faces[:,0], :] - nodes1[G.cells.faces[:,0], :]

    a = np.sum(np.cross(v1, v2) * v3, axis=1)
    sgn = 2 * (G.faces.neighbors[G.cells.faces[:,0], 0] == cellNumbers) - 1

    i = aggregate(G.cells.faces[:,0], a * sgn) < 0
    G.faces.neighbors[i,0], G.faces.neighbors[i,1] = \
            G.faces.neighbors[i,1], G.faces.neighbors[i,0]

    return G
예제 #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
예제 #6
0
파일: __init__.py 프로젝트: zwgzwz/PRST
def computePressureRHS(G, omega, bc=None, src=None):
    """
    %Compute right-hand side contributions to pressure linear system.

    TODO: Fix documentation for PRST.
    %
    % SYNOPSIS:
    %   [f, g, h, grav, dF, dC] = computePressureRHS(G, omega, bc, src)
    %
    % DESCRIPTION:
    %   The contributions to the right-hand side for mimetic, two-point and
    %   multi-point discretisations of the equations for pressure and total
    %   velocity
    %
    %     v + lam K·grad (p - g·z omega) = 0
    %     div v  = q
    %
    %   with
    %             __
    %             \    kr_i/
    %     lam   = /       / mu_i
    %             -- i
    %             __
    %             \
    %     omega = /    f_i·rho_i
    %             -- i
    %
    % PARAMETERS:
    %   G     - Grid data structure.
    %
    %   omega - Accumulated phase densities \rho_i weighted by fractional flow
    %           functions f_i -- i.e., omega = \sum_i \rho_i f_i.  One scalar
    %           value for each cell in the discretised reservoir model, G.
    %
    %   bc    - Boundary condition structure as defined by function 'addBC'.
    %           This structure accounts for all external boundary conditions to
    %           the reservoir flow.  May be empty (i.e., bc = struct([])) which
    %           is interpreted as all external no-flow (homogeneous Neumann)
    %           conditions.
    %
    %   src   - Explicit source contributions as defined by function
    %           'addSource'.  May be empty (i.e., src = struct([])) which is
    %           interpreted as a reservoir model without explicit sources.
    %
    % RETURNS:
    %   f, g, h - Pressure (f), source/sink (g), and flux (h) external
    %             conditions.  In a problem without effects of gravity, these
    %             values may be passed directly on to linear system solvers
    %             such as 'schurComplementSymm'.
    %
    %   grav    - Pressure contributions from gravity,
    %
    %                grav = omega·g·(x_face - x_cell)
    %
    %             where
    %
    %                omega = \sum_i f_i\rho_i,
    %
    %             thus grav is a vector with one scalar value for each
    %             half-face in the model (size(G.cells.faces,1)).
    %
    %   dF, dC  - Dirichlet/pressure condition structure.  Logical array 'dF'
    %             is true for those faces that have prescribed pressures, while
    %             the corresponding prescribed pressure values are listed in
    %             'dC'.  The number of elements in 'dC' is SUM(DOUBLE(dF)).
    %
    %             This structure may be used to eliminate known face pressures
    %             from the linear system before calling a system solver (e.g.,
    %             'schurComplementSymm').
    %
    % SEE ALSO:
    %   addBC, addSource, computeMimeticIP, schurComplementSymm.
    """
    if hasattr(G, "grav_pressure"):
        gp = G.grav_pressure(G, omega)
    else:
        gp = _grav_pressure(G, omega)

    ff = np.zeros(gp.shape)
    gg = np.zeros((G.cells.num, 1))
    hh = np.zeros((G.faces.num, 1))

    # Source terms
    if not src is None:
        prst.warning("computePressureRHS is untested for src != None")
        # Compatability check of cell numbers for source terms
        assert np.max(src.cell) < G.cells.num and np.min(src.cell) >= 0, \
            "Source terms refer to cell not existant in grid."

        # Sum source terms inside each cell and add to rhs
        ss = aggregate(src.cell, src.rate)
        ii = aggregate(src.cell, 1) > 0
        gg[ii] += ss[ii]

    dF = np.zeros((G.faces.num, 1), dtype=bool)
    dC = None

    if not bc is None:
        # Check that bc and G are compatible
        assert np.max(bc.face) < G.faces.num and np.min(bc.face) >= 0, \
            "Boundary condition refers to face not existant in grid."
        assert np.all(aggregate(bc.face, 1)) <= 1, \
            "There are repeated faces in boundary condition."

        # Pressure (Dirichlet) boundary conditions.
        # 1) Extract the faces marked as defining pressure conditions.
        #    Define a local numbering (map) of the face indices to the
        #    pressure condition values.
        is_press = bc.type == "pressure"
        face = bc.face[is_press]
        dC = bc.value[is_press]
        map = scipy.sparse.csc_matrix( (np.arange(face.size),
                                     (face.ravel(), np.zeros(face.size)))  )

        # 2) For purpose of (mimetic) pressure solvers, mark the "face"s as
        #    having pressure boundary conditions. This information will be used
        #    to eliminate known pressures from the resulting system of linear
        #    equations. See e.g. `solveIncompFlow` in MRST.
        dF[face] = True

        # 3) Enter Dirichlet conditions into system right hand side.
        #    Relies implicitly on boundary faces being mentioned exactly once
        #    in G.cells.faces[:,0].
        i = dF[G.cells.faces[:,0],:]
        ff[i] = -dC[map[ G.cells.faces[i[:,0],0],0].toarray().ravel()]

        # 4) Reorder Dirichlet conditions according to sort(face).
        #    This allows the caller to issue statements such as 
        #    `X[dF] = dC` even when dF is boolean.
        dC = dC[map[dF[:,0],0].toarray().ravel()]

        # Flux (Neumann) boundary conditions.
        # Note negative sign due to bc.value representing INJECTION flux.
        is_flux = bc.type == "flux"
        hh[bc.face[is_flux],0] = -bc.value[is_flux]

    if not dC is None:
        assert not np.any(dC < 0)

    return ff, gg, hh, gp, dF, dC
예제 #7
0
파일: __init__.py 프로젝트: zwgzwz/PRST
def incompTPFA(state, G, T, fluid, wells=None, bc=None, bcp=None, src=None,
               LinSolve=None, MatrixOutput=False, verbose=None,
               condition_number=False, gravity=None,
               pc_form="nonwetting", use_trans=False):
    """
    Solve incompressible flow problem (fluxes/pressure) using TPFA method.

    Synopsis:
        state = incompTPFA(state, G, T, fluid)
        state = incompTPFA(state, G, T, fluid, **kwargs)

    Description:
        This function assembles and solves a (block) system of linear equations
        defining interface fluxes and cell pressures at the next time step in a
        sequential splitting scheme for the reservoir simulation problem
        defined by Darcy's law and a given set of external influences (wells,
        sources, and boundary conditions).

        This function uses a two-point flux approximation (TPFA) method with
        minimal memory consumption within the constraints of operating on a
        fully unstructured polyhedral grid structure.

    Arguments:
        state (Struct):
            Reservoir and well solution structure either properly initialized
            from functions `prst.solvers.initResSol` and
            `prst.solvers.initWellSol` respectively, or the results from a
            previous call to function `incompTPFA` and, possibly, a transport
            solver such as function `prst.incomp.transport.implicitTransport`.

        G (Grid):
            prst.gridprocessing.Grid object.

        T (ndarray):
            Half-transmissibilities as computed by function `computeTrans`.
            Column array.

        fluid (Fluid):
            prst.incomp.fluid.SimpleFluid object.

        wells (Optional[Struct]):
            Well structure as defined by function `addWell. May be None, which
            is interpreted as a model without any wells.

        bc (Optional[BoundaryCondition]):
            prst.params.wells_and_bc.BoundaryCondition object. This structure
            accounts for all external boundary conditions to the reservoir
            flow. May be None, which is interpreted as all external no-flow
            (homogeneous Neumann) conditions.

        src (Optional[object]):
            Explicit source contributions as defined by function
            `prst.params.wells_and_bc.addSource`. May be None, which is
            interpreted as a reservoir model without explicit sources.

        LinSolve (Optional[function]):
            Handle to linear system solver function to which the fully
            assembled system of linear equations will be passed. Assumed to
            support the syntax

                x = LinSolve(A, b)

            in order to solve a system Ax=b of linear equations.
            Default: TODO scipy.sparse.solver used?

        MatrixOutput (Optional[bool]):
            Whether or not to return the final system matrix `A` to the caller
            of function `incompTPFA`. Boolean. Default: False.

        verbose (Optional[bool]):
            Enable output. Default value dependent upon global verbose settings
            of `prst.utils.prstVerbose`.

        condition_number (Optional[bool]):
            Display estimated condition number of linear system. Default: False.

        gravity (Optional[ndarray]):
            The current gravity in vector form.
            Default: prst.gravity (=np.array([0, 0, 9.8066]))

    Returns:
        Updates and returns `state` argument with new values for the fields:
            state.pressure: Pressure values for all cells in the discretised
            reservoir model, `G`.

            state.facePressure: Pressure values for all interfaces in the
            discretised reservoir model, `G`.

            state.flux: Flux across global interfaces corresponding to the rows
            of `G.faces.neighbors`.

            state.A: System matrix. Only returned if specifically requested by
            argument `MatrixOutput`.

            state.wellSol: List of well solution Structs. One for each well in
            the model, with new values for the fields:

                - flux: Perforation fluxes through all perforations for
                  corresponding well. The fluxes are interpreted as injection
                  fluxes, meaning positive values correspond to injection into
                  reservoir while negative values mean production/extraction
                  out of reservoir.

                - pressure: Well bottom-hole pressure.

    Note:
        If there are no external influences, i.e., if all of the structures
        `W`, `bc` and `src` are empty and there are no effects of gravity, then
        the input values `xr` and `xw` are returned unchanged and a warning is
        printed in the command window.

    MRST Example: (TODO: Rewrite in PRST)

        G   = computeGeometry(cartGrid([3, 3, 5]));

        f   = initSingleFluid('mu' , 1*centi*poise, ...
                              'rho', 1000*kilogram/meter^3);
        rock.perm = rand(G.cells.num, 1)*darcy()/100;

        bc  = pside([], G, 'LEFT', 2*barsa);
        src = addSource([], 1, 1);
        W   = verticalWell([], G, rock, 1, G.cartDims(2), []   , ...
                           'Type', 'rate', 'Val', 1*meter^3/day, ...
                           'InnerProduct', 'ip_tpf');
        W   = verticalWell(W, G, rock, G.cartDims(1), G.cartDims(2), [], ...
                           'Type', 'bhp', 'Val', 1*barsa, ...
                           'InnerProduct', 'ip_tpf');

        T   = computeTrans(G, rock);

        state         = initResSol (G, 10);
        state.wellSol = initWellSol(G, 10);

        state = incompTPFA(state, G, T, f, 'bc', bc, 'src', src, ...
                           'wells', W, 'MatrixOutput', true);

        plotCellData(G, state.pressure)

    See also:
        computeTrans, addBC, addSource, addWell, initSingleFluid, initResSol,
        initWellSol.
    """
    if wells is None:
        wells = []
    if LinSolve is None:
        LinSolve = scipy.sparse.linalg.spsolve
    if verbose is None:
        verbose = prst.verbosity
    if gravity is None:
        gravity = prst.gravity


    g_vec = gravity
    # If gravity is overridden, we cannot say anything about the effects of gravity on rhs.
    grav = np.linalg.norm(g_vec[0:G.gridDim]) > 0 or hasattr(G, 'grav_pressure')

    if all([not MatrixOutput, 
            bc is None,
            src is None,
            bcp is None,
            wells is None,
            not grav]):
        prst.log.warn("No external driving forces present in model, state remains unchanged.")

    if verbose:
        print("Setting up linear system...")
        t0 = time.time()

    # Preliminaries
    neighborship, n_isnnc = utils.gridtools.getNeighborship(G, "Topological", True, nargout=2)
    cellNo, cf, cn_isnnc = utils.gridtools.getCellNoFaces(G) # TODO
    nif = neighborship.shape[0]
    ncf = cf.shape[0]
    nc = G.cells.num
    nw = len(wells)
    n = nc + nw

    mob, omega, rho = _dynamic_quantities(state, fluid)
    totmob = np.sum(mob, axis=1, keepdims=True)

    # Compute effective (mobility-weighted) transmissibilities
    T, ft = _compute_trans(G, T, cellNo, cf, neighborship, totmob, use_trans=False)

    # Identify internal faces
    i = np.all(neighborship != -1, axis=1)

    # Boundary conditions and source terms
    hh = np.zeros((nif, 1))
    dF = np.zeros((nif, 1), dtype=bool)
    grav, ff = np.zeros((ncf, 1)), np.zeros((ncf, 1))
    cn_notnnc = np.logical_not(cn_isnnc)[:,0]
    n_notnnc = np.logical_not(n_isnnc)[:,0]
    ff[cn_notnnc,:], gg, hh[n_notnnc,:], grav[cn_notnnc,:], dF[n_notnnc,:], dC = computePressureRHS(G, omega, bc, src)

    # Made to add capillary pressure
    if hasattr(fluid, "pc"):
        prst.warning("incompTPFA for fluid with capillary pressure is UNTESTED. Compare with MRST.")
        pc = fluid.pc(state)
        gpc = np.zeros(totmob.shape)

        if hasattr(fluid, "gpc") and pc_form == "global":
            cc = capPressureRHS(G, mob, pc, gpc, pc_form)
        else:
            cc = capPressureRHS(G, mob, pc, pc_form)
        grav += cc

    sgn = 2*(neighborship[cf,0] == cellNo).astype(np.int)-1
    j = np.logical_or( i[cf[:,0]],  dF[cf[:,0],0] )
    fg = aggregate(cf[j,0], grav[j,0] * sgn[j,0], size=nif)[:,np.newaxis]

    if not bcp is None:
        prst.warning("Code not tested in PRST, cross check with MRST.")
        fg[bcp.face[:,0],0] = fg[bcp.face,0] + bcp.value
        prst.warning("Face pressures are not well defined for periodic boundary faces.")
        if np.any(G.faces.neighbors[:,0] == G.faces.neighbors[:,1]):
            raise ValueError("Periodic boundary: This code does not work of a face is in and outflo.")

    rhs = aggregate(cellNo[:,0], -ft[cf[:,0]] * (sgn[:,0]*fg[cf[:,0],0]+ff[:,0]), size=n) + \
            np.r_[gg[:,0], np.zeros(nw)] + \
          aggregate(cellNo[:,0], -hh[cf[:,0],0], size=n)
    rhs = rhs[:,np.newaxis] # as column vector

    d = np.zeros((G.cells.num, 1))


    if nw:
        raise NotImplementedError("Not yet working with wells. See MRST.")

    """
       # Missing code, from MRST
       % Wells --------------------------
       C    = cell (nw, 1);
       D    = zeros(nw, 1);
       W    = opt.wells;

       for k = 1 : nw,
          wc       = W(k).cells;
          nwc      = numel(wc);
          w        = k + nc;

          wi       = W(k).WI .* totmob(wc);

          dp       = norm(gravity()) * W(k).dZ*sum(rho .* W(k).compi, 2);
          d   (wc) = d   (wc) + wi;

          if     strcmpi(W(k).type, 'bhp'),
             ww=max(wi);
             %ww=1.0;
             rhs (w)  = rhs (w)  + ww*W(k).val;
             rhs (wc) = rhs (wc) + wi.*(W(k).val + dp);
             C{k}     = -sparse(1, nc);
             D(k)     = ww;

          elseif strcmpi(W(k).type, 'rate'),
             rhs (w)  = rhs (w)  + W(k).val;
             rhs (wc) = rhs (wc) + wi.*dp;

             C{k}     =-sparse(ones(nwc, 1), wc, wi, 1, nc);
             D(k)     = sum(wi);

             rhs (w)  = rhs (w) - wi.'*dp;

          else
             error('Unsupported well type.');
          end
       end

       C = vertcat(C{:});
       D = spdiags(D, 0, nw, nw);
    """

    # Add up internal face transmissibilities plus Dirichlet pressure faces for each cell.
    d[:,0] += aggregate(cellNo[dF[cf[:,0],0],0], T[dF[cf,0]], size=nc) + \
         aggregate(neighborship[i,:].ravel(order="F"), np.tile(ft[i], (2,)), size=nc)

    # Assemble coefficient matrix for internal faces. 
    # Boundary conditions may introduce additional diagonal entries. 
    # Also, wells introduce additional equations and unknowns.
    I = np.r_[neighborship[i,0], neighborship[i,1], np.arange(nc)]
    J = np.r_[neighborship[i,1], neighborship[i,0], np.arange(nc)]
    V = np.r_[-ft[i], -ft[i], d[:,0]][:,np.newaxis]
    A = scipy.sparse.csc_matrix((V[:,0], (I, J)), shape=(nc,nc))

    if nw:
        raise NotImplementedError("Wells not implemented yet in PRST.")
    else:
        pass

    if prst.verbosity:
        print("Solving linear system...")
        t0 = time.time()

    if (not np.any(dF)) and (W is None or not np.any(W.type == "bhp")):
        if A[0,0] > 0:
            A[0,0] *= 2
        else:
            j = np.argmax(A.diagonal())
            A[j,j] *= 2

    if condition_number:
        print("*"*60)
        prst.warning("Warning: Condition number estimation method may be slow.")
        print("Condition number is... ", end="")
        import pyamg.util.linalg
        print(pyamg.util.linalg.condest(A))

    if MatrixOutput:
        state.A = A
        state.rhs = rhs

    p = LinSolve(A, rhs)[:,np.newaxis]

    if prst.verbosity:
        print("{} seconds to solve".format(time.time() - t0))

        print("Computing fluxes, face pressures etc...")
        t0 = time.time()

    # Reconstruct face pressures and fluxes
    fpress = (aggregate(cf[:,0], (p[cellNo[:,0],0]+grav[:,0])*T[:,0], size=nif)
              /aggregate(cf[:,0], T[:,0], size=nif))[:,np.newaxis]

    # Neumann faces
    b = np.any(G.faces.neighbors==-1, axis=1)[:,np.newaxis]
    fpress[b[:,0],0] -= hh[b] / ft[b[:,0]]

    # Dirichlet faces
    fpress[dF] = dC

    # Sign for boundary faces
    noti = np.logical_not(i)
    sgn = 2*(G.faces.neighbors[noti,1]==-1)-1
    ni = neighborship[i]
    # Because of floating point loss of precision due to subtraction of similarly sized numbers,
    # this result can be slightly different from MATLAB for very low flux.
    flux = -aggregate(np.where(i)[0], ft[i] * (p[ni[:,1],0]-p[ni[:,0],0]-fg[i,0]), size=nif)[:,np.newaxis]
    c = np.max(G.faces.neighbors[noti,:], axis=1)[:,np.newaxis]
    fg = aggregate(cf[:,0], grav[:,0], size=nif)
    flux[noti,0] = -sgn*ft[noti] * ( fpress[noti,0] - p[c[:,0],0] - fg[noti] )
    state.pressure[0:nc] = p[0:nc]
    state.flux = flux
    state.facePressure = fpress

    if nw:
        raise NotImplementedError("Wells not yet in PRST, only MRST.")
    """
     for k = 1 : nw,
          wc       = W(k).cells;
          dp       = norm(gravity()) * W(k).dZ*sum(rho .* W(k).compi, 2);
          state.wellSol(k).flux     = W(k).WI.*totmob(wc).*(p(nc+k) + dp - p(wc));
          state.wellSol(k).pressure = p(nc + k);
       end
    """
    if prst.verbosity:
        print("{} seconds to compute fluxes, face pressures, etc...".format(time.time()-t0))

    return state
예제 #8
0
def aggregate_loom(ds: loompy.LoomConnection,
                   out_file: str,
                   select: np.ndarray,
                   group_by: str,
                   aggr_by: str,
                   aggr_ca_by: Dict[str, str],
                   return_matrix: bool = False) -> np.ndarray:
    """
	Aggregate a Loom file by applying aggregation functions to the main matrix as well as to the column attributes

	Args:
		ds			The Loom file
		out_file	The name of the output Loom file (will be appended to if it exists)
		select		Bool array giving the columns to include (or None, to include all)
		group_by	The column attribute to group by
		aggr_by 	The aggregation function for the main matrix
		aggr_ca_by	The aggregation functions for the column attributes (or None to skip)

	Remarks:
		aggr_by gives the aggregation function for the main matrix
		aggr_ca_by is a dictionary with column attributes as keys and aggregation functionas as values
		
		Aggregation functions can be any valid aggregation function from here: https://github.com/ml31415/numpy-groupies

		In addition, you can specify:
			"tally" to count the number of occurences of each value of a categorical attribute
	"""
    ca = {}  # type: Dict[str, np.ndarray]
    if select is not None:
        raise ValueError("The 'select' argument is deprecated")
    labels = (ds.ca[group_by]).astype('int')
    _, zero_strt_sort_noholes_lbls = np.unique(labels, return_inverse=True)
    n_groups = len(set(labels))
    if aggr_ca_by is not None:
        for key in ds.col_attrs.keys():
            if key not in aggr_ca_by:
                continue
            func = aggr_ca_by[key]
            if func == "tally":
                for val in set(ds.col_attrs[key]):
                    ca[key + "_" + val] = npg.aggregate(
                        zero_strt_sort_noholes_lbls,
                        (ds.col_attrs[key] == val).astype('int'),
                        func="sum",
                        fill_value=0)
            elif func == "mode":

                def mode(x):
                    return scipy.stats.mode(x)[0][0]

                ca[key] = npg.aggregate(zero_strt_sort_noholes_lbls,
                                        ds.col_attrs[key],
                                        func=mode,
                                        fill_value=0).astype('str')
            elif func == "mean":
                ca[key] = npg.aggregate(zero_strt_sort_noholes_lbls,
                                        ds.col_attrs[key],
                                        func=func,
                                        fill_value=0)
            elif func == "first":
                ca[key] = npg.aggregate(zero_strt_sort_noholes_lbls,
                                        ds.col_attrs[key],
                                        func=func,
                                        fill_value=ds.col_attrs[key][0])

    m = np.empty((ds.shape[0], n_groups))
    for (_, selection, view) in ds.scan(axis=0):
        vals_aggr = npg.aggregate(zero_strt_sort_noholes_lbls,
                                  view[:, :],
                                  func=aggr_by,
                                  axis=1,
                                  fill_value=0)
        m[selection, :] = vals_aggr

    if return_matrix:
        return m

    loompy.create_append(out_file, m, ds.ra, ca, fill_values="auto")