def __init__(self, physics='flow'): self.physics = physics self.discr = tpfa.Tpfa(self.physics) self.discr_ndof = self.discr.ndof self.coupling_conditions = tpfa.TpfaCoupling(self.discr) tp = tpfa.Tpfa(self.physics) mp = mpfa.Mpfa(self.physics) def discr_fct(g, d): return mp.matrix_rhs(g, d) if g.dim < 3 \ else tp.matrix_rhs(g, d) kwargs = {'discr_fct': discr_fct} self.solver = Coupler(self.discr, self.coupling_conditions, **kwargs)
def test_1d_elimination_3d_2d_1d(self): """ 3d case with a single 1d grid. """ f1 = np.array([[0, 1, 1, 0], [0, 0, 1, 1], [.5, .5, .5, .5]]) f2 = np.array([[.5, .5, .5, .5], [0, 1, 1, 0], [0, 0, 1, 1]]) gb = meshing.cart_grid([f1, f2], [2, 2, 2], **{'physdims': [1, 1, 1]}) gb.compute_geometry() gb.assign_node_ordering() tol = 1e-3 solver = tpfa.Tpfa() gb.add_node_props(['param']) a = 1e-2 for g, d in gb: param = Parameters(g) aperture = np.ones(g.num_cells) * np.power(a, gb.dim_max() - g.dim) param.set_aperture(aperture) p = tensor.SecondOrder( 3, np.ones(g.num_cells) * np.power(1e3, g.dim < gb.dim_max())) param.set_tensor('flow', p) bound_faces = g.get_boundary_faces() bound_face_centers = g.face_centers[:, bound_faces] left = bound_face_centers[0, :] > 1 - tol right = bound_face_centers[0, :] < tol labels = np.array(['neu'] * bound_faces.size) labels[np.logical_or(left, right)] = ['dir'] bc_val = np.zeros(g.num_faces) bc_dir = bound_faces[np.logical_or(left, right)] bc_val[bc_dir] = g.face_centers[0, bc_dir] param.set_bc(solver, bc.BoundaryCondition(g, bound_faces, labels)) param.set_bc_val(solver, bc_val) d['param'] = param coupling_conditions = tpfa.TpfaCoupling(solver) solver_coupler = coupler.Coupler(solver, coupling_conditions) A, rhs = solver_coupler.matrix_rhs(gb) p = sps.linalg.spsolve(A, rhs) p_cond, _, _, _ = condensation.solve_static_condensation(\ A, rhs, gb, dim=1) solver_coupler.split(gb, "pressure", p) solver_coupler.split(gb, "p_cond", p_cond) tol = 1e-5 assert ((np.amax(np.absolute(p - p_cond))) < tol) assert (np.sum( error.error_L2(g, d['pressure'], d['p_cond']) for g, d in gb) < tol)
def tpfa_matrix(g, perm=None): """ Compute a two-point flux approximation matrix useful related to a call of create_partition. Parameters ---------- g: the grid perm: (optional) permeability, the it is not given unitary tensor is assumed Returns ------- out: sparse matrix Two-point flux approximation matrix """ if isinstance(g, grid_bucket.GridBucket): g = g.get_grids(lambda g_: g_.dim == g.dim_max())[0] if perm is None: perm = tensor.SecondOrderTensor(g.dim, np.ones(g.num_cells)) solver = tpfa.Tpfa() param = Parameters(g) param.set_tensor(solver, perm) param.set_bc(solver, BoundaryCondition(g, np.empty(0), "")) return solver.matrix_rhs(g, {"param": param})[0]
def diffusive_disc(self): 'Discretization of term \nabla K \nabla T' if self.is_GridBucket: diffusive_discr = tpfa.TpfaMixedDim(physics=self.physics) else: diffusive_discr = tpfa.Tpfa(physics=self.physics) return diffusive_discr
def test_tpfa_coupling_2d_1d_bottom_top_dir(self): """ Grid: 2 x 2 matrix + 2 x 1 fracture from left to right. Dirichlet + no-flow, blocking fracture. """ f = np.array([[0, 1], [.5, .5]]) gb = meshing.cart_grid([f], [1, 2], **{'physdims': [1, 1]}) gb.compute_geometry() gb.assign_node_ordering() tol = 1e-3 solver = tpfa.Tpfa(physics='flow') #solver = tpfa.Tpfa(physics='flow') gb.add_node_props(['param']) a = 1e-2 for g, d in gb: param = Parameters(g) aperture = np.ones(g.num_cells) * np.power(a, gb.dim_max() - g.dim) param.set_aperture(aperture) p = tensor.SecondOrderTensor( 3, np.ones(g.num_cells) * np.power(1e-3, g.dim < gb.dim_max())) param.set_tensor('flow', p) bound_faces = g.tags['domain_boundary_faces'].nonzero()[0] bound_face_centers = g.face_centers[:, bound_faces] top = bound_face_centers[1, :] > 1 - tol bottom = bound_face_centers[1, :] < tol labels = np.array(['neu'] * bound_faces.size) labels[np.logical_or(top, bottom)] = ['dir'] bc_val = np.zeros(g.num_faces) bc_dir = bound_faces[np.logical_or(top, bottom)] bc_val[bc_dir] = g.face_centers[1, bc_dir] param.set_bc(solver, bc.BoundaryCondition(g, bound_faces, labels)) param.set_bc_val(solver, bc_val) d['param'] = param coupling_conditions = tpfa.TpfaCoupling(solver) solver_coupler = coupler.Coupler(solver, coupling_conditions) A, rhs = solver_coupler.matrix_rhs(gb) A_known = np.array([[4.19047619, 0., -0.19047619], [0., 4.19047619, -0.19047619], [-0.19047619, -0.19047619, 0.38095238]]) rhs_known = np.array([0., 4., 0.]) rtol = 1e-6 atol = rtol assert np.allclose(A.todense(), A_known, rtol, atol) assert np.allclose(rhs, rhs_known, rtol, atol)
def test_tpfa_cart_2d(): """ Apply TPFA on Cartesian grid, should obtain Laplacian stencil. """ # Set up 3 X 3 Cartesian grid nx = np.array([3, 3]) g = structured.CartGrid(nx) g.compute_geometry() kxx = np.ones(g.num_cells) perm = tensor.SecondOrder(g.dim, kxx) bound_faces = np.array([0, 3, 12]) bound = bc.BoundaryCondition(g, bound_faces, ['dir'] * bound_faces.size) discr = tpfa.Tpfa() d = _assign_params(g, perm, bound) discr.discretize(g, d) trm, bound_flux = d['flux'], d['bound_flux'] div = g.cell_faces.T a = div * trm b = -(div * bound_flux).A # Checks on interior cell mid = 4 assert a[mid, mid] == 4 assert a[mid - 1, mid] == -1 assert a[mid + 1, mid] == -1 assert a[mid - 3, mid] == -1 assert a[mid + 3, mid] == -1 assert np.all(b[mid, :] == 0) # The first cell should have two Dirichlet bnds assert a[0, 0] == 6 assert a[0, 1] == -1 assert a[0, 3] == -1 assert b[0, 0] == 2 assert b[0, 12] == 2 # Cell 3 has one Dirichlet, one Neumann face print(a) assert a[2, 2] == 4 assert a[2, 1] == -1 assert a[2, 5] == -1 assert b[2, 3] == 2 assert b[2, 14] == -1 # Cell 2 has one Neumann face assert a[1, 1] == 3 assert a[1, 0] == -1 assert a[1, 2] == -1 assert a[1, 4] == -1 assert b[1, 13] == -1 return a
def test_uniform_flow_cart_2d(): nx = np.array([13, 13]) g = structured.CartGrid(nx) g.compute_geometry() kxx = np.ones(g.num_cells) perm = tensor.SecondOrder(g.dim, kxx) bound_faces = np.argwhere( np.abs(g.cell_faces).sum(axis=1).A.ravel('F') == 1) bound = bc.BoundaryCondition(g, bound_faces, ['dir'] * bound_faces.size) discr = tpfa.Tpfa() d = _assign_params(g, perm, bound) discr.discretize(g, d) flux, bound_flux = d['flux'], d['bound_flux']
def test_tpfa_coupling_2d_1d_left_right_dir_neu(self): """ Grid: 2 x 2 cells in matrix + 2 cells in the fracture from left to right. Dirichlet + inflow + no-flow, conductive fracture. Tests pressure solution as well as matrix and rhs. """ f = np.array([[0, 1], [.5, .5]]) gb = meshing.cart_grid( [f], [2, 2], **{'physdims': [1, 1]}) gb.compute_geometry() gb.assign_node_ordering() tol = 1e-3 solver = tpfa.Tpfa(physics='flow') gb.add_node_props(['param']) a = 1e-2 for g, d in gb: param = Parameters(g) a_dim = np.power(a, gb.dim_max() - g.dim) aperture = np.ones(g.num_cells)*a_dim param.set_aperture(aperture) p = tensor.SecondOrder(3,np.ones(g.num_cells)* np.power(1e3, g.dim<gb.dim_max())) param.set_tensor('flow', p) bound_faces = g.get_boundary_faces() bound_face_centers = g.face_centers[:, bound_faces] right = bound_face_centers[0, :] > 1 - tol left = bound_face_centers[0, :] < tol labels = np.array(['neu'] * bound_faces.size) labels[right] = ['dir'] bc_val = np.zeros(g.num_faces) bc_dir = bound_faces[right] bc_neu = bound_faces[left] bc_val[bc_dir] = g.face_centers[0,bc_dir] bc_val[bc_neu] = -g.face_areas[bc_neu]*a_dim param.set_bc(solver, bc.BoundaryCondition(g, bound_faces, labels)) param.set_bc_val(solver, bc_val) d['param'] = param coupling_conditions = tpfa.TpfaCoupling(solver) solver_coupler = coupler.Coupler(solver, coupling_conditions) A, rhs = solver_coupler.matrix_rhs(gb) A_known = np.array(\ [[ 2.99996, -1. , 0. , 0. , 0. , -1.99996], [ -1. , 4.99996, 0. , 0. , -1.99996, 0. ], [ 0. , 0. , 2.99996, -1. , 0. , -1.99996], [ 0. , 0. , -1. , 4.99996, -1.99996, 0. ], [ 0. , -1.99996, 0. , -1.99996, 63.99992, -20. ], [ -1.99996, 0. , -1.99996, 0. , -20. , 23.99992]] ) rhs_known = np.array([ 5.00000000e-01, 2.00000000e+00, 5.00000000e-01, 2.00000000e+00, 4.00000000e+01, 1.00000000e-02]) p_known = np.array([ 1.21984244, 1.05198918, 1.21984244, 1.05198918, 1.02005108, 1.05376576]) p = sps.linalg.spsolve(A, rhs) rtol = 1e-6 atol = rtol assert np.allclose(A.todense(), A_known, rtol, atol) assert np.allclose(rhs, rhs_known, rtol, atol) assert np.allclose(p, p_known, rtol, atol)
def test_tpfa_coupling_2d_1d_left_right_dir(self): """ Grid: 2 x 2 cells in matrix + 2 cells in the fracture from left to right. Dirichlet + no-flow, conductive fracture. """ f = np.array([[0, 1], [.5, .5]]) gb = meshing.cart_grid( [f], [2, 2], **{'physdims': [1, 1]}) gb.compute_geometry() gb.assign_node_ordering() tol = 1e-3 solver = tpfa.Tpfa(physics='flow') gb.add_node_props(['param']) a = 1e-2 for g, d in gb: param = Parameters(g) aperture = np.ones(g.num_cells)*np.power(a, gb.dim_max() - g.dim) param.set_aperture(aperture) p = tensor.SecondOrder(3,np.ones(g.num_cells)* np.power(1e3, g.dim<gb.dim_max())) param.set_tensor('flow', p) bound_faces = g.get_boundary_faces() bound_face_centers = g.face_centers[:, bound_faces] left = bound_face_centers[0, :] > 1 - tol right = bound_face_centers[0, :] < tol labels = np.array(['neu'] * bound_faces.size) labels[np.logical_or(left, right)] = ['dir'] bc_val = np.zeros(g.num_faces) bc_dir = bound_faces[np.logical_or(left, right)] bc_val[bc_dir] = g.face_centers[0,bc_dir] param.set_bc(solver, bc.BoundaryCondition(g, bound_faces, labels)) param.set_bc_val(solver, bc_val) d['param'] = param coupling_conditions = tpfa.TpfaCoupling(solver) solver_coupler = coupler.Coupler(solver, coupling_conditions) A, rhs = solver_coupler.matrix_rhs(gb) A_known = np.array( [[ 4.99996, -1. , 0. , 0. , 0. , -1.99996], [ -1. , 4.99996, 0. , 0. , -1.99996, 0. ], [ 0. , 0. , 4.99996, -1. , 0. , -1.99996], [ 0. , 0. , -1. , 4.99996, -1.99996, 0. ], [ 0. , -1.99996, 0. , -1.99996, 63.99992, -20. ], [ -1.99996, 0. , -1.99996, 0. , -20. , 63.99992]]) rhs_known = np.array([ 0., 2., 0., 2., 40., 0.]) rtol = 1e-6 atol = rtol assert np.allclose(A.todense(), A_known, rtol, atol) assert np.allclose(rhs, rhs_known, rtol, atol)
def atest_0d_elimination_two_0d_grids(self): """ 2d case involving two 0d grids. """ f1 = np.array([[0, 1], [.5, .5]]) f2 = np.array([[.5, .5], [0, 1]]) f3 = np.array([[.25, .25], [0, 1]]) gb = meshing.cart_grid([f1, f2, f3], [4, 2], **{"physdims": [1, 1]}) gb.compute_geometry() gb.assign_node_ordering() tol = 1e-3 solver = tpfa.Tpfa() gb.add_node_props(["param"]) a = 1e-2 for g, d in gb: param = Parameters(g) a_dim = np.power(a, gb.dim_max() - g.dim) aperture = np.ones(g.num_cells) * a_dim param.set_aperture(aperture) kxx = np.ones(g.num_cells) * np.power(1e3, g.dim < gb.dim_max()) p = tensor.SecondOrderTensor(3, kxx, kyy=kxx, kzz=kxx) param.set_tensor("flow", p) bound_faces = g.tags["domain_boundary_faces"].nonzero()[0] if bound_faces.size != 0: bound_face_centers = g.face_centers[:, bound_faces] right = bound_face_centers[0, :] > 1 - tol left = bound_face_centers[0, :] < tol labels = np.array(["neu"] * bound_faces.size) labels[right] = ["dir"] bc_val = np.zeros(g.num_faces) bc_dir = bound_faces[right] bc_neu = bound_faces[left] bc_val[bc_dir] = g.face_centers[0, bc_dir] bc_val[bc_neu] = -g.face_areas[bc_neu] * a_dim param.set_bc(solver, bc.BoundaryCondition(g, bound_faces, labels)) param.set_bc_val(solver, bc_val) else: param.set_bc("flow", bc.BoundaryCondition(g, np.empty(0), np.empty(0))) d["param"] = param coupling_conditions = tpfa.TpfaCoupling(solver) solver_coupler = coupler.Coupler(solver, coupling_conditions) A, rhs = solver_coupler.matrix_rhs(gb) p = sps.linalg.spsolve(A, rhs) p_cond, _, _, _ = condensation.solve_static_condensation(A, rhs, gb, dim=0) solver_coupler.split(gb, "pressure", p) solver_coupler.split(gb, "p_cond", p_cond) tol = 1e-10 self.assertTrue((np.amax(np.absolute(p - p_cond))) < tol) self.assertTrue( np.sum(error.error_L2(g, d["pressure"], d["p_cond"]) for g, d in gb) < tol )
def _mpfa_local(g, k, bnd, eta=None, inverter='numba', apertures=None): """ Actual implementation of the MPFA O-method. To calculate MPFA on a grid directly, either call this method, or, to respect the privacy of this method, the main mpfa method with no memory constraints. Method properties and implementation details. The pressure is discretized as a linear function on sub-cells (see reference paper). In this implementation, the pressure is represented by its cell center value and the sub-cell gradients (this is in contrast to most papers, which use auxiliary pressures on the faces; the current formulation is equivalent, but somewhat easier to implement). The method will give continuous fluxes over the faces, and pressure continuity for certain points (controlled by the parameter eta). This can be expressed as a linear system on the form (i) A * grad_p = 0 (ii) B * grad_p + C * p_cc = 0 (iii) 0 D * p_cc = I Here, the first equation represents flux continuity, and involves only the pressure gradients (grad_p). The second equation gives pressure continuity over cell faces, thus B will contain distances between cell centers and the face continuity points, while C consists of +- 1 (depending on which side the cell is relative to the face normal vector). The third equation enforces the pressure to be unity in one cell at a time. Thus (i)-(iii) can be inverted to express the pressure gradients as in terms of the cell center variables, that is, we can compute the basis functions on the sub-cells. Because of the method construction (again see reference paper), the basis function of a cell c will be non-zero on all sub-cells sharing a vertex with c. Finally, the fluxes as functions of cell center values are computed by insertion into Darcy's law (which is essentially half of A from (i), that is, only consider contribution from one side of the face. Boundary values can be incorporated with appropriate modifications - Neumann conditions will have a non-zero right hand side for (i), while Dirichlet gives a right hand side for (ii). """ if eta is None: eta = fvutils.determine_eta(g) # The method reduces to the more efficient TPFA in one dimension, so that # method may be called. In 0D, there is no internal discretization to be # done. if g.dim == 1: discr = tpfa.Tpfa() params = data.Parameters(g) params.set_bc('flow', bnd) params.set_aperture(apertures) params.set_tensor('flow', k) d = {'param': params} discr.discretize(g, d) return d['flux'], d['bound_flux'] elif g.dim == 0: return sps.csr_matrix([0]), 0 # The grid coordinates are always three-dimensional, even if the grid is # really 2D. This means that there is not a 1-1 relation between the number # of coordinates of a point / vector and the real dimension. This again # violates some assumptions tacitly made in the discretization (in # particular that the number of faces of a cell that meets in a vertex # equals the grid dimension, and that this can be used to construct an # index of local variables in the discretization). These issues should be # possible to overcome, but for the moment, we simply force 2D grids to be # proper 2D. if g.dim == 2: # Rotate the grid into the xy plane and delete third dimension. First # make a copy to avoid alterations to the input grid g = g.copy() cell_centers, face_normals, face_centers, R, _, nodes = cg.map_grid(g) g.cell_centers = cell_centers g.face_normals = face_normals g.face_centers = face_centers g.nodes = nodes # Rotate the permeability tensor and delete last dimension k = k.copy() k.perm = np.tensordot(R.T, np.tensordot(R, k.perm, (1, 0)), (0, 1)) k.perm = np.delete(k.perm, (2), axis=0) k.perm = np.delete(k.perm, (2), axis=1) # Define subcell topology, that is, the local numbering of faces, subfaces, # sub-cells and nodes. This numbering is used throughout the # discretization. subcell_topology = fvutils.SubcellTopology(g) # Obtain normal_vector * k, pairings of cells and nodes (which together # uniquely define sub-cells, and thus index for gradients. nk_grad, cell_node_blocks, \ sub_cell_index = _tensor_vector_prod(g, k, subcell_topology, apertures) # Distance from cell centers to face centers, this will be the # contribution from gradient unknown to equations for pressure continuity pr_cont_grad = fvutils.compute_dist_face_cell(g, subcell_topology, eta) # Darcy's law darcy = -nk_grad[subcell_topology.unique_subfno] # Pair fluxes over subfaces, that is, enforce conservation nk_grad = subcell_topology.pair_over_subfaces(nk_grad) # Contribution from cell center potentials to local systems # For pressure continuity, +-1 (Depending on whether the cell is on the # positive or negative side of the face. # The .A suffix is necessary to get a numpy array, instead of a scipy # matrix. sgn = g.cell_faces[subcell_topology.fno, subcell_topology.cno].A pr_cont_cell = sps.coo_matrix( (sgn[0], (subcell_topology.subfno, subcell_topology.cno))).tocsr() # The cell centers give zero contribution to flux continuity nk_cell = sps.coo_matrix( (np.zeros(1), (np.zeros(1), np.zeros(1))), shape=(subcell_topology.num_subfno, subcell_topology.num_cno)).tocsr() del sgn # Mapping from sub-faces to faces hf2f = sps.coo_matrix( (np.ones(subcell_topology.unique_subfno.size), (subcell_topology.fno_unique, subcell_topology.subfno_unique))) # Update signs sgn_unique = g.cell_faces[subcell_topology.fno_unique, subcell_topology.cno_unique].A.ravel('F') # The boundary faces will have either a Dirichlet or Neumann condition, but # not both (Robin is not implemented). # Obtain mappings to exclude boundary faces. bound_exclusion = fvutils.ExcludeBoundaries(subcell_topology, bnd, g.dim) # No flux conditions for Dirichlet boundary faces nk_grad = bound_exclusion.exclude_dirichlet(nk_grad) nk_cell = bound_exclusion.exclude_dirichlet(nk_cell) # No pressure condition for Neumann boundary faces pr_cont_grad = bound_exclusion.exclude_neumann(pr_cont_grad) pr_cont_cell = bound_exclusion.exclude_neumann(pr_cont_cell) # So far, the local numbering has been based on the numbering scheme # implemented in SubcellTopology (which treats one cell at a time). For # efficient inversion (below), it is desirable to get the system over to a # block-diagonal structure, with one block centered around each vertex. # Obtain the necessary mappings. rows2blk_diag, cols2blk_diag, size_of_blocks = _block_diagonal_structure( sub_cell_index, cell_node_blocks, subcell_topology.nno_unique, bound_exclusion) del cell_node_blocks, sub_cell_index # System of equations for the subcell gradient variables. On block diagonal # form. grad_eqs = sps.vstack([nk_grad, pr_cont_grad]) num_nk_cell = nk_cell.shape[0] num_pr_cont_grad = pr_cont_grad.shape[0] del nk_grad, pr_cont_grad grad = rows2blk_diag * grad_eqs * cols2blk_diag del grad_eqs darcy_igrad = darcy * cols2blk_diag * fvutils.invert_diagonal_blocks(grad, size_of_blocks, method=inverter) \ * rows2blk_diag del grad, cols2blk_diag, rows2blk_diag, darcy flux = hf2f * darcy_igrad * (-sps.vstack([nk_cell, pr_cont_cell])) del nk_cell, pr_cont_cell #### # Boundary conditions rhs_bound = _create_bound_rhs(bnd, bound_exclusion, subcell_topology, sgn_unique, g, num_nk_cell, num_pr_cont_grad) # Discretization of boundary values bound_flux = hf2f * darcy_igrad * rhs_bound return flux, bound_flux
def atest_tpfa_coupling_2d_1d_bottom_top_dir(self): """ Grid: 2 x 2 matrix + 2 x 1 fracture from left to right. Dirichlet + no-flow, blocking fracture. """ f = np.array([[0, 1], [.5, .5]]) gb = meshing.cart_grid([f], [1, 2], **{"physdims": [1, 1]}) gb.compute_geometry() gb.assign_node_ordering() tol = 1e-3 solver = tpfa.Tpfa(physics="flow") # solver = tpfa.Tpfa(physics='flow') gb.add_node_props(["param"]) gb.add_edge_props(["kn"]) a = 1e-2 for g, d in gb: param = Parameters(g) aperture = np.ones(g.num_cells) * np.power(a, gb.dim_max() - g.dim) param.set_aperture(aperture) p = tensor.SecondOrderTensor( 3, np.ones(g.num_cells) * np.power(1e-3, g.dim < gb.dim_max())) param.set_tensor("flow", p) bound_faces = g.tags["domain_boundary_faces"].nonzero()[0] bound_face_centers = g.face_centers[:, bound_faces] top = bound_face_centers[1, :] > 1 - tol bottom = bound_face_centers[1, :] < tol labels = np.array(["neu"] * bound_faces.size) labels[np.logical_or(top, bottom)] = ["dir"] bc_val = np.zeros(g.num_faces) bc_dir = bound_faces[np.logical_or(top, bottom)] bc_val[bc_dir] = g.face_centers[1, bc_dir] param.set_bc(solver, bc.BoundaryCondition(g, bound_faces, labels)) param.set_bc_val(solver, bc_val) d["param"] = param for e, d in gb.edges(): d["kn"] = 1e-5 coupling_conditions = tpfa.TpfaCoupling(solver) solver_coupler = coupler.Coupler(solver, coupling_conditions) A, rhs = solver_coupler.matrix_rhs(gb) A_known = np.array([ [4., 0., -0, 0., 1], [0., 4., -0., 1, 0], [0, 0, 0, -1, -1], [0, 1, -1, -5.000025e4, 0], [1, 0, -1, 0, -5.000025e4], ]) rhs_known = np.array([0., 4., 0., 0, 0]) rtol = 1e-6 atol = rtol self.assertTrue(np.allclose(A.todense(), A_known, rtol, atol)) self.assertTrue(np.allclose(rhs, rhs_known, rtol, atol))
def test_0d_elimination_two_0d_grids(self): """ 2d case involving two 0d grids. """ f1 = np.array([[0, 1], [.5, .5]]) f2 = np.array([[.5, .5], [0, 1]]) f3 = np.array([[.25, .25], [0, 1]]) gb = meshing.cart_grid([f1, f2, f3], [4, 2], **{'physdims': [1, 1]}) gb.compute_geometry() gb.assign_node_ordering() tol = 1e-3 solver = tpfa.Tpfa() gb.add_node_props(['param']) a = 1e-2 for g, d in gb: param = Parameters(g) a_dim = np.power(a, gb.dim_max() - g.dim) aperture = np.ones(g.num_cells) * a_dim param.set_aperture(aperture) kxx = np.ones(g.num_cells) * np.power(1e3, g.dim < gb.dim_max()) #print(kxx, 'dim', g.dim) p = tensor.SecondOrder(3, kxx, kyy=kxx, kzz=kxx) # print(p.perm) param.set_tensor('flow', p) bound_faces = g.get_boundary_faces() bound_face_centers = g.face_centers[:, bound_faces] right = bound_face_centers[0, :] > 1 - tol left = bound_face_centers[0, :] < tol labels = np.array(['neu'] * bound_faces.size) labels[right] = ['dir'] bc_val = np.zeros(g.num_faces) bc_dir = bound_faces[right] bc_neu = bound_faces[left] bc_val[bc_dir] = g.face_centers[0, bc_dir] bc_val[bc_neu] = -g.face_areas[bc_neu] * a_dim param.set_bc(solver, bc.BoundaryCondition(g, bound_faces, labels)) param.set_bc_val(solver, bc_val) d['param'] = param coupling_conditions = tpfa.TpfaCoupling(solver) solver_coupler = coupler.Coupler(solver, coupling_conditions) A, rhs = solver_coupler.matrix_rhs(gb) p = sps.linalg.spsolve(A, rhs) p_cond, _, _, _ = condensation.solve_static_condensation(A, rhs, gb, dim=0) solver_coupler.split(gb, 'pressure', p) solver_coupler.split(gb, "p_cond", p_cond) tol = 1e-10 assert ((np.amax(np.absolute(p - p_cond))) < tol) assert (np.sum( error.error_L2(g, d['pressure'], d['p_cond']) for g, d in gb) < tol)
def test_0d_elimination_3d_2d_1d_0d(self): """ 3d case with a single 0d grid. """ f1 = np.array([[0, 1, 1, 0], [0, 0, 1, 1], [.5, .5, .5, .5]]) f2 = np.array([[.5, .5, .5, .5], [0, 1, 1, 0], [0, 0, 1, 1]]) f3 = np.array([[0, 1, 1, 0], [.5, .5, .5, .5], [0, 0, 1, 1]]) gb = meshing.cart_grid([f1, f2, f3], [2, 2, 2], **{'physdims': [1, 1, 1]}) gb.compute_geometry() gb.assign_node_ordering() cell_centers1 = np.array([[0.25, 0.75, 0.25, 0.75], [0.25, 0.25, 0.75, 0.75], [0.5, 0.5, 0.5, 0.5]]) cell_centers2 = np.array([[0.5, 0.5, 0.5, 0.5], [0.25, 0.25, 0.75, 0.75], [0.75, 0.25, 0.75, 0.25]]) cell_centers3 = np.array([[0.25, 0.75, 0.25, 0.75], [0.5, 0.5, 0.5, 0.5], [0.25, 0.25, 0.75, 0.75]]) cell_centers4 = np.array([[0.5], [0.25], [0.5]]) cell_centers5 = np.array([[0.5], [0.75], [0.5]]) cell_centers6 = np.array([[0.75], [0.5], [0.5]]) cell_centers7 = np.array([[0.25], [0.5], [0.5]]) cell_centers8 = np.array([[0.5], [0.5], [0.25]]) cell_centers9 = np.array([[0.5], [0.5], [0.75]]) for g, d in gb: if np.allclose(g.cell_centers[:, 0], cell_centers1[:, 0]): d['node_number'] = 1 elif np.allclose(g.cell_centers[:, 0], cell_centers2[:, 0]): d['node_number'] = 2 elif np.allclose(g.cell_centers[:, 0], cell_centers3[:, 0]): d['node_number'] = 3 elif np.allclose(g.cell_centers[:, 0], cell_centers4[:, 0]): d['node_number'] = 4 elif np.allclose(g.cell_centers[:, 0], cell_centers5[:, 0]): d['node_number'] = 5 elif np.allclose(g.cell_centers[:, 0], cell_centers6[:, 0]): d['node_number'] = 6 elif np.allclose(g.cell_centers[:, 0], cell_centers7[:, 0]): d['node_number'] = 7 elif np.allclose(g.cell_centers[:, 0], cell_centers8[:, 0]): d['node_number'] = 8 elif np.allclose(g.cell_centers[:, 0], cell_centers9[:, 0]): d['node_number'] = 9 else: pass tol = 1e-3 solver = tpfa.Tpfa() gb.add_node_props(['param']) a = 1e-2 for g, d in gb: param = Parameters(g) aperture = np.ones(g.num_cells) * np.power(a, gb.dim_max() - g.dim) param.set_aperture(aperture) p = tensor.SecondOrderTensor( 3, np.ones(g.num_cells) * np.power(1e3, g.dim < gb.dim_max())) param.set_tensor('flow', p) bound_faces = g.tags['domain_boundary_faces'].nonzero()[0] if bound_faces.size != 0: bound_face_centers = g.face_centers[:, bound_faces] left = bound_face_centers[0, :] > 1 - tol right = bound_face_centers[0, :] < tol labels = np.array(['neu'] * bound_faces.size) labels[np.logical_or(left, right)] = ['dir'] bc_val = np.zeros(g.num_faces) bc_dir = bound_faces[np.logical_or(left, right)] bc_val[bc_dir] = g.face_centers[0, bc_dir] param.set_bc(solver, bc.BoundaryCondition(g, bound_faces, labels)) param.set_bc_val(solver, bc_val) else: param.set_bc("flow", bc.BoundaryCondition(g, np.empty(0), np.empty(0))) d['param'] = param coupling_conditions = tpfa.TpfaCoupling(solver) solver_coupler = coupler.Coupler(solver, coupling_conditions) A, rhs = solver_coupler.matrix_rhs(gb) p = sps.linalg.spsolve(A, rhs) p_cond, _, _, _ = condensation.solve_static_condensation(A, rhs, gb, dim=0) solver_coupler.split(gb, 'pressure', p) solver_coupler.split(gb, "p_cond", p_cond) tol = 1e-10 assert ((np.amax(np.absolute(p - p_cond))) < tol) assert (np.sum( error.error_L2(g, d['pressure'], d['p_cond']) for g, d in gb) < tol)
def test_tpfa_coupling_2d_1d_left_right_cross_dir_neu(self): f1 = np.array([[0, 2], [.5, .5]]) f2 = np.array([[.5, .5], [0, 2]]) gb = meshing.cart_grid( [f1, f2], [2, 2], **{'physdims': [1, 1]}) gb.compute_geometry() gb.assign_node_ordering() # Enforce node orderning because of Python 3.5 and 2.7. # Don't do it in general. cell_centers_1 = np.array([[ 7.50000000e-01, 2.500000000e-01], [ 5.00000000e-01, 5.00000000e-01], [ -5.55111512e-17, 5.55111512e-17]]) cell_centers_2 = np.array([[ 5.00000000e-01, 5.00000000e-01], [ 7.50000000e-01, 2.500000000e-01], [ -5.55111512e-17, 5.55111512e-17]]) for g, d in gb: if g.dim == 1: if np.allclose(g.cell_centers, cell_centers_1): d['node_number'] = 1 elif np.allclose(g.cell_centers, cell_centers_2): d['node_number'] = 2 else: raise ValueError('Grid not found') tol = 1e-3 solver = tpfa.Tpfa() gb.add_node_props(['param']) a = 1e-2 for g, d in gb: param = Parameters(g) a_dim = np.power(a, gb.dim_max() - g.dim) aperture = np.ones(g.num_cells)*a_dim param.set_aperture(aperture) kxx = np.ones(g.num_cells) * np.power(1e3, g.dim<gb.dim_max()) #print(kxx, 'dim', g.dim) p = tensor.SecondOrder(3,kxx,kyy=kxx,kzz=kxx) #print(p.perm) param.set_tensor('flow', p) bound_faces = g.get_boundary_faces() bound_face_centers = g.face_centers[:, bound_faces] right = bound_face_centers[0, :] > 1 - tol left = bound_face_centers[0, :] < tol labels = np.array(['neu'] * bound_faces.size) labels[right] = ['dir'] bc_val = np.zeros(g.num_faces) bc_dir = bound_faces[right] bc_neu = bound_faces[left] bc_val[bc_dir] = g.face_centers[0,bc_dir] bc_val[bc_neu] = -g.face_areas[bc_neu]*a_dim param.set_bc(solver, bc.BoundaryCondition(g, bound_faces, labels)) param.set_bc_val(solver, bc_val) d['param'] = param coupling_conditions = tpfa.TpfaCoupling(solver) solver_coupler = coupler.Coupler(solver, coupling_conditions) A, rhs = solver_coupler.matrix_rhs(gb) A_known, rhs_known = matrix_rhs_for_2d_1d_cross() rtol = 1e-6 atol = rtol assert np.allclose(A.todense(), A_known, rtol, atol) assert np.allclose(rhs, rhs_known, rtol, atol)
def atest_tpfa_coupling_2d_1d_bottom_top_dir_neu(self): """ Grid: 1 x 2 cells in matrix + 1 cell in the fracture from left to right. Dirichlet + inflow + no-flow, blocking fracture. """ f = np.array([[0, 1], [.5, .5]]) gb = meshing.cart_grid([f], [1, 2], **{"physdims": [1, 1]}) gb.compute_geometry() gb.assign_node_ordering() tol = 1e-3 solver = tpfa.Tpfa(physics="flow") solver = tpfa.Tpfa(physics="flow") gb.add_node_props(["param"]) a = 1e-2 for g, d in gb: param = Parameters(g) a_dim = np.power(a, gb.dim_max() - g.dim) aperture = np.ones(g.num_cells) * a_dim param.set_aperture(aperture) p = tensor.SecondOrderTensor( 3, np.ones(g.num_cells) * np.power(1e-3, g.dim < gb.dim_max())) param.set_tensor("flow", p) bound_faces = g.tags["domain_boundary_faces"].nonzero()[0] bound_face_centers = g.face_centers[:, bound_faces] top = bound_face_centers[1, :] > 1 - tol bottom = bound_face_centers[1, :] < tol labels = np.array(["neu"] * bound_faces.size) labels[bottom] = ["dir"] bc_val = np.zeros(g.num_faces) bc_dir = bound_faces[bottom] bc_neu = bound_faces[top] bc_val[bc_dir] = g.face_centers[1, bc_dir] bc_val[bc_neu] = -g.face_areas[bc_neu] * a_dim param.set_bc(solver, bc.BoundaryCondition(g, bound_faces, labels)) param.set_bc_val(solver, bc_val) d["param"] = param coupling_conditions = tpfa.TpfaCoupling(solver) solver_coupler = coupler.Coupler(solver, coupling_conditions) A, rhs = solver_coupler.matrix_rhs(gb) A_known = np.array([ [4.19047619, 0., -0.19047619], [0., 0.19047619, -0.19047619], [-0.19047619, -0.19047619, 0.38095238], ]) rhs_known = np.array([0, 1, 0]) rtol = 1e-6 atol = rtol self.assertTrue(np.allclose(A.todense(), A_known, rtol, atol)) self.assertTrue(np.allclose(rhs, rhs_known, rtol, atol))
def flux_disc(self): if self.is_GridBucket: return tpfa.TpfaMixedDim(physics=self.physics) else: return tpfa.Tpfa(physics=self.physics)
def test_tpfa_coupling_3d_2d_1d_0d_dir(self): f1 = np.array([[ 0, 1, 1, 0], [ 0, 0, 1, 1], [.5, .5, .5, .5]]) f2 = np.array([[.5, .5, .5, .5], [ 0, 1, 1, 0], [ 0, 0, 1, 1]]) f3 = np.array([[ 0, 1, 1, 0], [.5, .5, .5, .5], [ 0, 0, 1, 1]]) gb = meshing.cart_grid([f1, f2, f3], [2, 2, 2], **{'physdims': [1, 1, 1]}) gb.compute_geometry() gb.assign_node_ordering() # Remove flag for dual cell_centers1 = np.array([[ 0.25 , 0.75 , 0.25 , 0.75], [ 0.25 , 0.25 , 0.75 , 0.75], [ 0.5 , 0.5 , 0.5 , 0.5 ]]) cell_centers2 = np.array([[ 0.5 , 0.5 , 0.5 , 0.5 ], [ 0.25 , 0.25 , 0.75 , 0.75], [ 0.75 , 0.25 , 0.75 , 0.25]]) cell_centers3 = np.array([[ 0.25 , 0.75 , 0.25 , 0.75], [ 0.5 , 0.5 , 0.5 , 0.5 ], [ 0.25 , 0.25 , 0.75 , 0.75]]) cell_centers4 = np.array([[ 0.5 ], [ 0.25], [ 0.5 ]]) cell_centers5 = np.array([[ 0.5 ], [ 0.75], [ 0.5 ]]) cell_centers6 = np.array([[ 0.75], [ 0.5 ], [ 0.5 ]]) cell_centers7 = np.array([[ 0.25], [ 0.5 ], [ 0.5 ]]) cell_centers8 = np.array([[ 0.5 ], [ 0.5 ], [ 0.25]]) cell_centers9 = np.array([[ 0.5 ], [ 0.5 ], [ 0.75]]) for g, d in gb: if np.allclose(g.cell_centers[:, 0], cell_centers1[:, 0]): d['node_number'] = 1 elif np.allclose(g.cell_centers[:, 0], cell_centers2[:, 0]): d['node_number'] = 2 elif np.allclose(g.cell_centers[:, 0], cell_centers3[:, 0]): d['node_number'] = 3 elif np.allclose(g.cell_centers[:, 0], cell_centers4[:, 0]): d['node_number'] = 4 elif np.allclose(g.cell_centers[:, 0], cell_centers5[:, 0]): d['node_number'] = 5 elif np.allclose(g.cell_centers[:, 0], cell_centers6[:, 0]): d['node_number'] = 6 elif np.allclose(g.cell_centers[:, 0], cell_centers7[:, 0]): d['node_number'] = 7 elif np.allclose(g.cell_centers[:, 0], cell_centers8[:, 0]): d['node_number'] = 8 elif np.allclose(g.cell_centers[:, 0], cell_centers9[:, 0]): d['node_number'] = 9 else: pass tol = 1e-3 solver = tpfa.Tpfa() gb.add_node_props(['param']) a = 1e-2 for g, d in gb: param = Parameters(g) aperture = np.ones(g.num_cells)*np.power(a, gb.dim_max() - g.dim) param.set_aperture(aperture) p = tensor.SecondOrder(3,np.ones(g.num_cells)* np.power(1e3, g.dim<gb.dim_max())) param.set_tensor('flow', p) bound_faces = g.get_boundary_faces() bound_face_centers = g.face_centers[:, bound_faces] left = bound_face_centers[0, :] > 1 - tol right = bound_face_centers[0, :] < tol labels = np.array(['neu'] * bound_faces.size) labels[np.logical_or(left, right)] = ['dir'] bc_val = np.zeros(g.num_faces) bc_dir = bound_faces[np.logical_or(left, right)] bc_val[bc_dir] = g.face_centers[0,bc_dir] param.set_bc(solver, bc.BoundaryCondition(g, bound_faces, labels)) param.set_bc_val(solver, bc_val) d['param'] = param coupling_conditions = tpfa.TpfaCoupling(solver) solver_coupler = coupler.Coupler(solver, coupling_conditions) A, rhs = solver_coupler.matrix_rhs(gb) A_known, rhs_known, p_known = \ matrix_rhs_pressure_for_test_tpfa_coupling_3d_2d_1d_0d() p = sps.linalg.spsolve(A, rhs) rtol = 1e-6 atol = rtol assert np.allclose(A.todense(), A_known, rtol, atol) assert np.allclose(rhs, rhs_known, rtol, atol) assert np.allclose(p, p_known, rtol, atol)
def _mpfa_local(g, k, bnd, eta=None, inverter="numba", apertures=None, robin_weight=None): """ Actual implementation of the MPFA O-method. To calculate MPFA on a grid directly, either call this method, or, to respect the privacy of this method, the main mpfa method with no memory constraints. Method properties and implementation details. The pressure is discretized as a linear function on sub-cells (see reference paper). In this implementation, the pressure is represented by its cell center value and the sub-cell gradients (this is in contrast to most papers, which use auxiliary pressures on the faces; the current formulation is equivalent, but somewhat easier to implement). The method will give continuous fluxes over the faces, and pressure continuity for certain points (controlled by the parameter eta). This can be expressed as a linear system on the form (i) A * grad_p = 0 (ii) Ar * grad_p + Cr * P_cc = 0 (iii) B * grad_p + C * p_cc = 0 (iv) 0 D * p_cc = I Here, the first equation represents flux continuity, and involves only the pressure gradients (grad_p). The second equation gives the Robin conditions, relating flux to the pressure. The third equation gives pressure continuity over cell faces, thus B will contain distances between cell centers and the face continuity points, while C consists of +- 1 (depending on which side the cell is relative to the face normal vector). The fourth equation enforces the pressure to be unity in one cell at a time. Thus (i)-(iv) can be inverted to express the pressure gradients as in terms of the cell center variables, that is, we can compute the basis functions on the sub-cells. Because of the method construction (again see reference paper), the basis function of a cell c will be non-zero on all sub-cells sharing a vertex with c. Finally, the fluxes as functions of cell center values are computed by insertion into Darcy's law (which is essentially half of A from (i), that is, only consider contribution from one side of the face. Boundary values can be incorporated with appropriate modifications - Neumann conditions will have a non-zero right hand side for (i), while Dirichlet gives a right hand side for (iii). """ if eta is None: eta = fvutils.determine_eta(g) # The method reduces to the more efficient TPFA in one dimension, so that # method may be called. In 0D, there is no internal discretization to be # done. if g.dim == 1: discr = tpfa.Tpfa() params = data.Parameters(g) params.set_bc("flow", bnd) params.set_aperture(apertures) params.set_tensor("flow", k) d = {"param": params} discr.discretize(g, d) return ( d["flux"], d["bound_flux"], d["bound_pressure_cell"], d["bound_pressure_face"], ) elif g.dim == 0: return sps.csr_matrix([0]), 0, 0, 0 if robin_weight is None: if np.sum(bnd.is_rob) != 0: raise ValueError( "If applying Robin conditions you must supply an robin_weight") else: robin_weight = 1 # The grid coordinates are always three-dimensional, even if the grid is # really 2D. This means that there is not a 1-1 relation between the number # of coordinates of a point / vector and the real dimension. This again # violates some assumptions tacitly made in the discretization (in # particular that the number of faces of a cell that meets in a vertex # equals the grid dimension, and that this can be used to construct an # index of local variables in the discretization). These issues should be # possible to overcome, but for the moment, we simply force 2D grids to be # proper 2D. if g.dim == 2: # Rotate the grid into the xy plane and delete third dimension. First # make a copy to avoid alterations to the input grid g = g.copy() cell_centers, face_normals, face_centers, R, _, nodes = cg.map_grid(g) g.cell_centers = cell_centers g.face_normals = face_normals g.face_centers = face_centers g.nodes = nodes # Rotate the permeability tensor and delete last dimension k = k.copy() k.perm = np.tensordot(R.T, np.tensordot(R, k.perm, (1, 0)), (0, 1)) k.perm = np.delete(k.perm, (2), axis=0) k.perm = np.delete(k.perm, (2), axis=1) # Define subcell topology, that is, the local numbering of faces, subfaces, # sub-cells and nodes. This numbering is used throughout the # discretization. subcell_topology = fvutils.SubcellTopology(g) # Obtain normal_vector * k, pairings of cells and nodes (which together # uniquely define sub-cells, and thus index for gradients. See comment # below for the ordering of elements in the subcell gradient. nk_grad_all, cell_node_blocks, sub_cell_index = _tensor_vector_prod( g, k, subcell_topology, apertures) # Distance from cell centers to face centers, this will be the # contribution from gradient unknown to equations for pressure continuity pr_cont_grad_all = fvutils.compute_dist_face_cell(g, subcell_topology, eta) # Darcy's law darcy = -nk_grad_all[subcell_topology.unique_subfno] # Pair fluxes over subfaces, that is, enforce conservation nk_grad_all = subcell_topology.pair_over_subfaces(nk_grad_all) # Contribution from cell center potentials to local systems # For pressure continuity, +-1 (Depending on whether the cell is on the # positive or negative side of the face. # The .A suffix is necessary to get a numpy array, instead of a scipy # matrix. sgn = g.cell_faces[subcell_topology.fno, subcell_topology.cno].A pr_cont_cell_all = sps.coo_matrix( (sgn[0], (subcell_topology.subfno, subcell_topology.cno))).tocsr() # The cell centers give zero contribution to flux continuity nk_cell = sps.coo_matrix( (np.zeros(1), (np.zeros(1), np.zeros(1))), shape=(subcell_topology.num_subfno, subcell_topology.num_cno), ).tocsr() # For the Robin condition the distance from the cell centers to face centers # will be the contribution from the gradients. We integrate over the subface # and multiply by the area num_nodes = np.diff(g.face_nodes.indptr) sgn = g.cell_faces[subcell_topology.fno_unique, subcell_topology.cno_unique].A scaled_sgn = (robin_weight * sgn[0] * g.face_areas[subcell_topology.fno_unique] / num_nodes[subcell_topology.fno_unique]) # pair_over_subfaces flips the sign so we flip it back pr_trace_grad_all = sps.diags(scaled_sgn) * pr_cont_grad_all pr_trace_cell_all = sps.coo_matrix(( robin_weight * g.face_areas[subcell_topology.fno] / num_nodes[subcell_topology.fno], (subcell_topology.subfno, subcell_topology.cno), )).tocsr() del sgn, scaled_sgn # Mapping from sub-faces to faces hf2f = sps.coo_matrix(( np.ones(subcell_topology.unique_subfno.size), (subcell_topology.fno_unique, subcell_topology.subfno_unique), )) # Update signs sgn_unique = g.cell_faces[subcell_topology.fno_unique, subcell_topology.cno_unique].A.ravel("F") # The boundary faces will have either a Dirichlet or Neumann condition, but # not both (Robin is not implemented). # Obtain mappings to exclude boundary faces. bound_exclusion = fvutils.ExcludeBoundaries(subcell_topology, bnd, g.dim) # No flux conditions for Dirichlet boundary faces nk_grad_n = bound_exclusion.exclude_robin_dirichlet(nk_grad_all) nk_cell = bound_exclusion.exclude_robin_dirichlet(nk_cell) # Robin condition is only applied to Robin boundary faces nk_grad_r = bound_exclusion.keep_robin(nk_grad_all) pr_trace_grad = bound_exclusion.keep_robin(pr_trace_grad_all) pr_trace_cell = bound_exclusion.keep_robin(pr_trace_cell_all) del nk_grad_all # No pressure condition for Neumann or Robin boundary faces pr_cont_grad = bound_exclusion.exclude_neumann_robin(pr_cont_grad_all) pr_cont_cell = bound_exclusion.exclude_neumann_robin(pr_cont_cell_all) # So far, the local numbering has been based on the numbering scheme # implemented in SubcellTopology (which treats one cell at a time). For # efficient inversion (below), it is desirable to get the system over to a # block-diagonal structure, with one block centered around each vertex. # Obtain the necessary mappings. rows2blk_diag, cols2blk_diag, size_of_blocks = _block_diagonal_structure( sub_cell_index, cell_node_blocks, subcell_topology.nno_unique, bound_exclusion) del cell_node_blocks, sub_cell_index # System of equations for the subcell gradient variables. On block diagonal # form. # NOTE: I think in the discretization for sub_cells a flow out of the cell is # negative. This is a contradiction to what is done for the boundary conditions # where we want to set dot(n, flux) where n is the normal pointing outwards. # thats why we need +nk_grad_r - pr_trace_grad -pr_trace_cell instead of = rhs # instead of how we would expect: -nk_grad_r + pr_trace_grad +pr_trace_cell= rhs. # This is also why we multiply with -1 in scaled_sgn in _create_bound_rhs grad_eqs = sps.vstack([nk_grad_n, nk_grad_r - pr_trace_grad, pr_cont_grad]) num_nk_cell = nk_cell.shape[0] num_nk_rob = nk_grad_r.shape[0] num_pr_cont_grad = pr_cont_grad.shape[0] del nk_grad_n, nk_grad_r, pr_trace_grad grad = rows2blk_diag * grad_eqs * cols2blk_diag del grad_eqs igrad = (cols2blk_diag * fvutils.invert_diagonal_blocks( grad, size_of_blocks, method=inverter) * rows2blk_diag) del grad, cols2blk_diag, rows2blk_diag # Technical note: The elements in igrad are organized as follows: # The fields subcell_topology.cno and .nno will together identify Nd # placements in igrad that are associated with the same cell and the same # node, that is, they belong to the same subcell. These placements are used # to store the discrete gradient of that cell, with the first item # representing the x-component etc. # As an example, to find the gradient in the subcell of cell ci, associated # with node ni, first find the indexes of subcell_topology.cno and .nno # that contain ci and ni, respectively. The first of these indexes give the # row of the x-component of the gradient, the second the y-component etc. # # The columns of igrad corresponds to the ordering of the equations in # grad; as recovered in _block_diagonal_structure. In practice, the first # columns correspond to unit pressures assigned to faces (as used for # boundary conditions or to discretize discontinuities over internal faces, # say, to represent heterogeneous gravity), while the latter group # gives gradients induced by cell center pressures. # # Note tacit assumptions: 1) Each cell has exactly Nd faces meeting in a # vertex; or else, there would not be an exact match between the # number of equal (nno-cno) pairs and the number of components in the # gradient. This assumption is always okay in 2d, in 3d it rules out cells # shaped as pyramids, in which case mpfa is not defined without making # further specifications of the method. # 2) The number of components in the gradient is equal to the spatial # dimension of the grid, as defined in g.dim. Thus 2d grids embedded in 3d # will run into trouble, unless the grid is first projected down to its # natural plane. This can be fixed by a more general implementation, but # it would require quite deep changes to the code. # Flux discretization: # The negative in front of pr_trace_cell comes from the grad_egs flux = hf2f * darcy * igrad * ( -sps.vstack([nk_cell, -pr_trace_cell, pr_cont_cell])) #### # Boundary conditions rhs_bound = _create_bound_rhs( bnd, bound_exclusion, subcell_topology, sgn_unique, g, num_nk_cell, num_nk_rob, num_pr_cont_grad, ) # Discretization of boundary values bound_flux = hf2f * darcy * igrad * rhs_bound # Below here, fields necessary for reconstruction of boundary pressures # Diagonal matrix that divides by number of sub-faces per face half_face_per_face = sps.diags(1. / (hf2f * np.ones(hf2f.shape[1]))) # Contribution to face pressure from sub-cell gradients, calculated as # gradient times distance. Then further map to faces, and divide by number # of contributions per face dp = (half_face_per_face * hf2f * pr_cont_grad_all * igrad * (-sps.vstack([nk_cell, pr_trace_cell, pr_cont_cell]))) # Internal faces, and boundary faces with a Dirichle condition do not need # information on the gradient. # Implementation note: This can be expanded to pressure recovery also # on internal faces by including them here, and below. remove_not_neumann = sps.diags(bnd.is_neu.astype(np.int)) dp = remove_not_neumann * dp # We also need pressure in the cell next to the boundary face. bound_faces = g.get_all_boundary_faces() # A trick to get the boundary face: We know that one element is -1 (e.g. # outside the domain). Add 1, sum cell indices (will only contain the # internal cell; the one outside is now zero), and then subtract 1 again. bound_cells = np.sum(g.cell_face_as_dense()[:, bound_faces] + 1, axis=0) - 1 cell_contrib = sps.coo_matrix( (np.ones_like(bound_faces), (bound_faces, bound_cells)), shape=(g.num_faces, g.num_cells), ) cell_contrib = remove_not_neumann * cell_contrib bound_pressure_cell = dp + cell_contrib sgn_arr = np.zeros(g.num_faces) sgn_arr[bound_faces] = g.cell_faces[bound_faces].sum(axis=1).A.ravel() sgn_mat = sps.diags(sgn_arr) bound_pressure_face_neu = (sgn_mat * half_face_per_face * hf2f * pr_cont_grad_all * igrad * rhs_bound) # For Dirichlet faces, simply recover the boundary condition bound_pressure_face_dir = sps.diags(bnd.is_dir.astype(np.int)) bound_pressure_face = (bound_pressure_face_dir + remove_not_neumann * bound_pressure_face_neu) return flux, bound_flux, bound_pressure_cell, bound_pressure_face
def test_0d_elimination_2d_1d_cross(self): """ Simplest case possible: 2d case with two fractures intersecting in a single 0d grid at the center of the domain. """ f1 = np.array([[0, 1], [.5, .5]]) f2 = np.array([[.5, .5], [0, 1]]) gb = meshing.cart_grid([f1, f2], [2, 2], **{'physdims': [1, 1]}) gb.compute_geometry() gb.assign_node_ordering() tol = 1e-3 solver = tpfa.Tpfa() gb.add_node_props(['param']) a = 1e-2 for g, d in gb: param = Parameters(g) a_dim = np.power(a, gb.dim_max() - g.dim) aperture = np.ones(g.num_cells) * a_dim param.set_aperture(aperture) kxx = np.ones(g.num_cells) * np.power(1e3, g.dim < gb.dim_max()) p = tensor.SecondOrderTensor(3, kxx, kyy=kxx, kzz=kxx) param.set_tensor('flow', p) bound_faces = g.tags['domain_boundary_faces'].nonzero()[0] if bound_faces.size != 0: bound_face_centers = g.face_centers[:, bound_faces] right = bound_face_centers[0, :] > 1 - tol left = bound_face_centers[0, :] < tol labels = np.array(['neu'] * bound_faces.size) labels[right] = ['dir'] bc_val = np.zeros(g.num_faces) bc_dir = bound_faces[right] bc_neu = bound_faces[left] bc_val[bc_dir] = g.face_centers[0, bc_dir] bc_val[bc_neu] = -g.face_areas[bc_neu] * a_dim param.set_bc(solver, bc.BoundaryCondition(g, bound_faces, labels)) param.set_bc_val(solver, bc_val) else: param.set_bc("flow", bc.BoundaryCondition(g, np.empty(0), np.empty(0))) d['param'] = param coupling_conditions = tpfa.TpfaCoupling(solver) solver_coupler = coupler.Coupler(solver, coupling_conditions) A, rhs = solver_coupler.matrix_rhs(gb) p = sps.linalg.spsolve(A, rhs) p_cond, _, _, _ = condensation.solve_static_condensation(A, rhs, gb, dim=0) solver_coupler.split(gb, 'pressure', p) solver_coupler.split(gb, "p_cond", p_cond) tol = 1e-10 assert ((np.amax(np.absolute(p - p_cond))) < tol) assert (np.sum( error.error_L2(g, d['pressure'], d['p_cond']) for g, d in gb) < tol)
diffusion = second_order_tensor.SecondOrderTensorTensor(g.dim, kxx) f = np.ones(g.num_cells) * g.cell_volumes data = {'beta_n': beta_n, 'bc': bnd, 'bc_val': bnd_val, 'k': diffusion, 'f': f} return data #------------------------------------------------------------------------------# # the f is considered twice, we guess that non-homogeneous Neumann as well. Nx = Ny = 20 g = structured.CartGrid([Nx, Ny], [1, 1]) g.compute_geometry() advection = upwind.Upwind() diffusion = tpfa.Tpfa() # Assign parameters data = add_data(g, advection) U, rhs_u = advection.matrix_rhs(g, data) D, rhs_d = diffusion.matrix_rhs(g, data) theta = sps.linalg.spsolve(D + U, rhs_u + rhs_d) exporter.export_vtk(g, "advection_diffusion", {"theta": theta})