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)
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
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
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
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
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
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")