[np.array([49]), np.array([55])]] return gb, split_scheme # The main test function @pytest.mark.parametrize( "geometry", [ _two_fractures_overlapping_regions, _two_fractures_non_overlapping_regions, _two_fractures_regions_become_overlapping, ], ) @pytest.mark.parametrize( "method", [pp.Mpfa("flow"), pp.Mpsa("mechanics"), pp.Biot("mechanics", "flow")]) def test_propagation(geometry, method): # Method to test partial discretization (aimed at finite volume methods) under # fracture propagation. The test is based on first discretizing, and then do one # or several fracture propagation steps. after each step, we do a partial # update of the discretization scheme, and compare with a full discretization on # the newly split grid. The test fails unless all discretization matrices generated # are identical. # # NOTE: Only the highest-dimensional grid in the GridBucket is used. # Get GridBucket and splitting schedule gb, faces_to_split = geometry()
def set_variables_discretizations_cell_basis(self, gb): """ Assign variables, and set discretizations for the micro gb. NOTE: keywords and variable names are hardcoded here. This should be centralized. @Eirik we are keeping the same nomenclature, since the gb are different maybe we can change it a bit Args: gb (TYPE): the micro gb. Returns: None. """ # Use mpfa for the fine-scale problem for now. We may generalize this at some # point, but that should be a technical detail. fine_scale_dicsr = pp.Mpfa(self.keyword) # In 1d, mpfa will end up calling tpfa, so use this directly, in a hope that # the reduced amount of boilerplate will save some time. fine_scale_dicsr_1d = pp.Tpfa(self.keyword) void_discr = pp.EllipticDiscretizationZeroPermeability(self.keyword) for g, d in gb: d[pp.PRIMARY_VARIABLES] = { self.cell_variable: { "cells": 1, "faces": 0 } } if g.dim > 1: d[pp.DISCRETIZATION] = { self.cell_variable: { self.cell_discr: fine_scale_dicsr } } else: d[pp.DISCRETIZATION] = { self.cell_variable: { self.cell_discr: fine_scale_dicsr_1d } } d[pp.DISCRETIZATION_MATRICES] = {self.keyword: {}} # Loop over the edges in the GridBucket, define primary variables and discretizations # NOTE: No need to differ between Mpfa and Tpfa here; their treatment of interface # discretizations are the same. for e, d in gb.edges(): g1, g2 = gb.nodes_of_edge(e) # Set the mortar variable. # NOTE: This is overriden in the case where the higher-dimensional grid is # auxiliary, see below. d[pp.PRIMARY_VARIABLES] = {self.mortar_variable: {"cells": 1}} # The type of lower-dimensional discretization depends on whether this is a # (part of a) fracture, or a transition between two line or surface grids. if g1.dim == 2 and g2.dim == 2: mortar_discr = pp.FluxPressureContinuity( self.keyword, fine_scale_dicsr, fine_scale_dicsr) elif hasattr(g1, "is_auxiliary") and g1.is_auxiliary: if g2.dim > 1: # This is a connection between a surface and an auxiliary line. # Impose continuity conditions over the line. assert g1.dim == 1 # Cannot imagine this is not True mortar_discr = pp.FluxPressureContinuity( self.keyword, fine_scale_dicsr, void_discr) else: # Connection between a 1d line (an interaction region edge) and a # point along that line (could be a coner along the edge. mortar_discr = pp.FluxPressureContinuity( self.keyword, fine_scale_dicsr_1d, void_discr) elif hasattr(g2, "is_auxiliary") and g2.is_auxiliary: # This is an auxiliary line being cut by a point, which should then # be a fracture point. Impose no condition here. assert g2.dim == 1 # No variable for this edge - we have no equation for it. d[pp.PRIMARY_VARIABLES] = {} continue else: # Standard coupling, with resistance, for the final case. if g1.dim > 1: mortar_discr = pp.RobinCoupling(self.keyword, fine_scale_dicsr, fine_scale_dicsr) elif g1.dim == 1: mortar_discr = pp.RobinCoupling(self.keyword, fine_scale_dicsr, fine_scale_dicsr_1d) else: mortar_discr = pp.RobinCoupling(self.keyword, fine_scale_dicsr_1d, fine_scale_dicsr_1d) d[pp.COUPLING_DISCRETIZATION] = { self.mortar_discr: { g1: (self.cell_variable, self.cell_discr), g2: (self.cell_variable, self.cell_discr), e: (self.mortar_variable, mortar_discr), } } d[pp.DISCRETIZATION_MATRICES] = {self.keyword: {}}
def run_mpfa(self, gb): key = "flow" method = pp.Mpfa(key) self._solve(gb, method, key)
def test_assemble_biot(self): """ Test the assembly of the Biot problem using the assembler. The test checks whether the discretization matches that of the Biot class. """ gb = pp.meshing.cart_grid([], [2, 1]) g = gb.grids_of_dimension(2)[0] d = gb.node_props(g) # Parameters identified by two keywords kw_m = "mechanics" kw_f = "flow" variable_m = "displacement" variable_f = "pressure" bound_mech, bound_flow = self.make_boundary_conditions(g) initial_disp, initial_pressure, initial_state = self.make_initial_conditions( g, x0=0, y0=0, p0=0 ) state = { variable_f: initial_pressure, variable_m: initial_disp, kw_m: {"bc_values": np.zeros(g.num_faces * g.dim)}, } parameters_m = {"bc": bound_mech, "biot_alpha": 1} parameters_f = {"bc": bound_flow, "biot_alpha": 1} pp.initialize_default_data(g, d, kw_m, parameters_m) pp.initialize_default_data(g, d, kw_f, parameters_f) pp.set_state(d, state) # Discretize the mechanics related terms using the Biot class biot_discretizer = pp.Biot() biot_discretizer._discretize_mech(g, d) # Set up the structure for the assembler. First define variables and equation # term names. v_0 = variable_m v_1 = variable_f term_00 = "stress_divergence" term_01 = "pressure_gradient" term_10 = "displacement_divergence" term_11_0 = "fluid_mass" term_11_1 = "fluid_flux" term_11_2 = "stabilization" d[pp.PRIMARY_VARIABLES] = {v_0: {"cells": g.dim}, v_1: {"cells": 1}} d[pp.DISCRETIZATION] = { v_0: {term_00: pp.Mpsa(kw_m)}, v_1: { term_11_0: pp.MassMatrix(kw_f), term_11_1: pp.Mpfa(kw_f), term_11_2: pp.BiotStabilization(kw_f), }, v_0 + "_" + v_1: {term_01: pp.GradP(kw_m)}, v_1 + "_" + v_0: {term_10: pp.DivU(kw_m)}, } # Assemble. Also discretizes the flow terms (fluid_mass and fluid_flux) general_assembler = pp.Assembler(gb) general_assembler.discretize(term_filter=["fluid_mass", "fluid_flux"]) A, b = general_assembler.assemble_matrix_rhs() # Re-discretize and assemble using the Biot class A_class, b_class = biot_discretizer.assemble_matrix_rhs(g, d) # Make sure the variable ordering of the matrix assembled by the assembler # matches that of the Biot class. grids = [g, g] variables = [v_0, v_1] A, b = permute_matrix_vector( A, b, general_assembler.block_dof, general_assembler.full_dof, grids, variables, ) # Compare the matrices and rhs vectors self.assertTrue(np.all(np.isclose(A.A, A_class.A))) self.assertTrue(np.all(np.isclose(b, b_class)))
def test_fv_cart_2d(method): """ Apply TPFA and MPFA on Cartesian grid, should obtain Laplacian stencil.""" # Set up 3 X 3 Cartesian grid nx = np.array([3, 3]) g = pp.CartGrid(nx) g.compute_geometry() kxx = np.ones(g.num_cells) perm = pp.SecondOrderTensor(kxx) bound_faces = np.array([0, 3, 12]) bound = pp.BoundaryCondition(g, bound_faces, ["dir"] * bound_faces.size) key = "flow" d = pp.initialize_default_data( g, {}, key, {"second_order_tensor": perm, "bc": bound} ) if method == "tpfa": discr = pp.Tpfa(key) elif method == "mpfa": discr = pp.Mpfa(key) else: assert False discr.discretize(g, d) matrix_dictionary = d[pp.DISCRETIZATION_MATRICES][key] trm, bound_flux = matrix_dictionary["flux"], matrix_dictionary["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 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_hydrostatic_pressure(self): # Test mpfa_gravity in 2D Cartesian # and triangular grids # Should be exact for hydrostatic pressure # with stepwise gravity variation grids = ["cart", "triangular"] x, y = sympy.symbols("x y") g1 = 10 g2 = 1 p0 = 1 # reference pressure p = p0 + sympy.Piecewise(((1 - y) * g1, y >= 0.5), (0.5 * g1 + (0.5 - y) * g2, y < 0.5)) an_sol = _SolutionHomogeneousDomainFlowWithGravity(p, x, y) for gr in grids: domain = np.array([1, 1]) basedim = np.array([4, 4]) pert = 0.5 g = make_grid(gr, basedim, domain) g.compute_geometry() dx = np.max(domain / basedim) g = perturb(g, pert, dx) g.compute_geometry() xc = g.cell_centers xf = g.face_centers k = pp.SecondOrderTensor(np.ones(g.num_cells)) # Gravity gforce = np.zeros((2, g.num_cells)) gforce[0, :] = an_sol.gx_f(xc[0], xc[1]) gforce[1, :] = an_sol.gy_f(xc[0], xc[1]) gforce = gforce.ravel("F") # Set type of boundary conditions p_bound = np.zeros(g.num_faces) left_faces = np.ravel(np.argwhere(g.face_centers[0] < 1e-10)) right_faces = np.ravel( np.argwhere(g.face_centers[0] > domain[0] - 1e-10)) dir_faces = np.concatenate((left_faces, right_faces)) bound_cond = pp.BoundaryCondition(g, dir_faces, ["dir"] * dir_faces.size) # set value of boundary condition p_bound[dir_faces] = an_sol.p_f(xf[0, dir_faces], xf[1, dir_faces]) # GCMPFA discretization, and system matrix flux, bound_flux, _, _, div_g = pp.Mpfa("flow").mpfa( g, k, bound_cond, vector_source=True, inverter="python") div = pp.fvutils.scalar_divergence(g) a = div * flux flux_g = div_g * gforce b = -div * bound_flux * p_bound - div * flux_g p = scipy.sparse.linalg.spsolve(a, b) q = flux * p + bound_flux * p_bound + flux_g p_ex = an_sol.p_f(xc[0], xc[1]) q_ex = np.zeros(g.num_faces) self.assertTrue(np.allclose(p, p_ex)) self.assertTrue(np.allclose(q, q_ex))