def setup_discr_tpfa(gb, key="flow"): """ Setup the discretization Tpfa. """ discr = pp.Tpfa(key) p_trace = pp.CellDofFaceDofMap(key) interface = pp.FluxPressureContinuity(key, discr, p_trace) for g, d in gb: if g.dim == gb.dim_max(): d[pp.PRIMARY_VARIABLES] = {key: {"cells": 1}} d[pp.DISCRETIZATION] = {key: {"flux": discr}} else: d[pp.PRIMARY_VARIABLES] = {key: {"cells": 1}} d[pp.DISCRETIZATION] = {key: {"flux": p_trace}} for e, d in gb.edges(): g_slave, g_master = gb.nodes_of_edge(e) d[pp.PRIMARY_VARIABLES] = {key: {"cells": 1}} d[pp.COUPLING_DISCRETIZATION] = { "flux": { g_slave: (key, "flux"), g_master: (key, "flux"), e: (key, interface), } } return pp.Assembler(gb), (discr, p_trace)
def test_two_cart_grids(self): """ We set up the test case -----|--------- | | | | g1 | g2 | | | | -----|--------- with a linear pressure increase from left to right """ n = 2 xmax = 3 ymax = 1 split = 2 gb = self.generate_grids(n, xmax, ymax, split) tol = 1e-6 for g, d in gb: left = g.face_centers[0] < tol right = g.face_centers[0] > xmax - tol dir_bc = left + right bound = pp.BoundaryCondition(g, dir_bc, "dir") bc_val = np.zeros(g.num_faces) bc_val[left] = xmax bc_val[right] = 0 specified_parameters = {"bc": bound, "bc_values": bc_val} pp.initialize_default_data(g, d, "flow", specified_parameters) for e, d in gb.edges(): mg = d["mortar_grid"] d[pp.PARAMETERS] = pp.Parameters(mg, ["flow"], [{}]) pp.params.data.add_discretization_matrix_keyword(d, "flow") # assign discretization data_key = "flow" tpfa = pp.Tpfa(data_key) coupler = pp.FluxPressureContinuity(data_key, tpfa) assembler = test_utils.setup_flow_assembler(gb, tpfa, data_key, coupler=coupler) test_utils.solve_and_distribute_pressure(gb, assembler) # test pressure for g, d in gb: self.assertTrue( np.allclose(d[pp.STATE]["pressure"], xmax - g.cell_centers[0])) # test mortar solution for e, d_e in gb.edges(): mg = d_e["mortar_grid"] g2, g1 = gb.nodes_of_edge(e) master_to_m = mg.master_to_mortar_avg() slave_to_m = mg.slave_to_mortar_avg() master_area = master_to_m * g1.face_areas slave_area = slave_to_m * g2.face_areas self.assertTrue( np.allclose(d_e[pp.STATE]["mortar_flux"] / master_area, 1)) self.assertTrue( np.allclose(d_e[pp.STATE]["mortar_flux"] / slave_area, 1))
def solve(self, gb, analytic_p): # Parameter-discretization keyword: kw = "flow" # Terms key_flux = "flux" key_src = "src" # Primary variables key_p = "pressure" # pressure name key_m = "mortar" # mortar name tpfa = pp.Tpfa(kw) src = pp.ScalarSource(kw) for g, d in gb: d[pp.DISCRETIZATION] = {key_p: {key_src: src, key_flux: tpfa}} d[pp.PRIMARY_VARIABLES] = {key_p: {"cells": 1}} for e, d in gb.edges(): g1, g2 = gb.nodes_of_edge(e) d[pp.PRIMARY_VARIABLES] = {key_m: {"cells": 1}} if g1.dim == g2.dim: mortar_disc = pp.FluxPressureContinuity(kw, tpfa) else: mortar_disc = pp.RobinCoupling(kw, tpfa) d[pp.COUPLING_DISCRETIZATION] = { key_flux: { g1: (key_p, key_flux), g2: (key_p, key_flux), e: (key_m, mortar_disc), } } assembler = pp.Assembler(gb) assembler.discretize() A, b = assembler.assemble_matrix_rhs() x = sps.linalg.spsolve(A, b) assembler.distribute_variable(x) # test pressure for g, d in gb: ap, _, _ = analytic_p(g.cell_centers) self.assertTrue(np.max(np.abs(d[pp.STATE][key_p] - ap)) < 5e-2) # test mortar solution for e, d_e in gb.edges(): mg = d_e["mortar_grid"] g1, g2 = gb.nodes_of_edge(e) if g1 == g2: left_to_m = mg.master_to_mortar_avg() right_to_m = mg.slave_to_mortar_avg() else: continue d1 = gb.node_props(g1) d2 = gb.node_props(g2) _, analytic_flux, _ = analytic_p(g1.face_centers) # the aperture is assumed constant left_flux = np.sum(analytic_flux * g1.face_normals[:2], 0) left_flux = left_to_m * ( d1[pp.DISCRETIZATION_MATRICES][kw]["bound_flux"] * left_flux ) # right flux is negative lambda right_flux = np.sum(analytic_flux * g2.face_normals[:2], 0) right_flux = -right_to_m * ( d2[pp.DISCRETIZATION_MATRICES][kw]["bound_flux"] * right_flux ) self.assertTrue(np.max(np.abs(d_e[pp.STATE][key_m] - left_flux)) < 5e-2) self.assertTrue(np.max(np.abs(d_e[pp.STATE][key_m] - right_flux)) < 5e-2)
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 advdiff(gb, discr, param, bc_flag): model = "transport" model_data_adv, model_data_diff, model_data_src = data_advdiff( gb, model, param, bc_flag ) # discretization operator names adv_id = "advection" diff_id = "diffusion" src_id = "source" # variable names variable = "scalar" mortar_adv = "lambda_" + variable + "_" + adv_id mortar_diff = "lambda_" + variable + "_" + diff_id # save variable name for the post-process param["scalar"] = variable discr_adv = pp.Upwind(model_data_adv) discr_adv_interface = pp.CellDofFaceDofMap(model_data_adv) discr_diff = pp.Tpfa(model_data_diff) discr_diff_interface = pp.CellDofFaceDofMap(model_data_diff) coupling_adv = pp.UpwindCoupling(model_data_adv) coupling_diff = pp.FluxPressureContinuity( model_data_diff, discr_diff, discr_diff_interface ) # mass term mass_id = "mass" discr_mass = pp.MassMatrix(model_data_adv) discr_mass_interface = pp.CellDofFaceDofMap(model_data_adv) discr_src = pp.ScalarSource(model_data_src) for g, d in gb: d[pp.PRIMARY_VARIABLES] = {variable: {"cells": 1}} if g.dim == gb.dim_max(): d[pp.DISCRETIZATION] = { variable: {adv_id: discr_adv, diff_id: discr_diff, mass_id: discr_mass, src_id: discr_src} } else: d[pp.DISCRETIZATION] = { variable: {adv_id: discr_adv_interface, diff_id: discr_diff_interface, mass_id: discr_mass_interface} } for e, d in gb.edges(): g_slave, g_master = gb.nodes_of_edge(e) d[pp.PRIMARY_VARIABLES] = {mortar_adv: {"cells": 1}, mortar_diff: {"cells": 1}} d[pp.COUPLING_DISCRETIZATION] = { adv_id: { g_slave: (variable, adv_id), g_master: (variable, adv_id), e: (mortar_adv, coupling_adv), }, diff_id: { g_slave: (variable, diff_id), g_master: (variable, diff_id), e: (mortar_diff, coupling_diff), }, } # setup the advection-diffusion problem assembler = pp.Assembler(gb, active_variables=[variable, mortar_diff, mortar_adv]) logger.info("Assemble the advective and diffusive terms of the transport problem") block_A, block_b = assembler.assemble_matrix_rhs(add_matrices=False) logger.info("done") # unpack the matrices just computed diff_name = diff_id + "_" + variable adv_name = adv_id + "_" + variable mass_name = mass_id + "_" + variable source_name = src_id + "_" + variable diff_coupling_name = diff_id + "_" + mortar_diff + "_" + variable + "_" + variable adv_coupling_name = adv_id + "_" + mortar_adv + "_" + variable + "_" + variable # need a sign for the convention of the conservation equation M = block_A[mass_name] A = block_A[diff_name] + block_A[diff_coupling_name] + \ block_A[adv_name] + block_A[adv_coupling_name] b = block_b[diff_name] + block_b[diff_coupling_name] + \ block_b[adv_name] + block_b[adv_coupling_name] + \ block_b[source_name] M_t = M.copy() / param["time_step"] * param.get("mass_weight", 1) M_r = M.copy() * param.get("reaction", 0) # Perform an LU factorization to speedup the solver IE_solver = sps.linalg.factorized((M_t + A + M_r).tocsc()) # time loop logger.info("Prepare the exporting") save = pp.Exporter(gb, "solution", folder=param["folder"]) logger.info("done") variables = [variable, param["pressure"], "frac_num", "cell_volumes"] if discr["scheme"] is pp.MVEM or discr["scheme"] is pp.RT0: variables.append(param["P0_flux"]) # assign the initial condition x = np.zeros(A.shape[0]) assembler.distribute_variable(x) for g, d in gb: if g.dim == gb.dim_max(): d[pp.STATE][variable] = param.get("init_trans", 0) * np.ones(g.num_cells) x = assembler.merge_variable(variable) outflow = np.zeros(param["n_steps"]) logger.info("Start the time loop with " + str(param["n_steps"]) + " steps") for i in np.arange(param["n_steps"]): logger.info("Solve the linear system for time step " + str(i)) x = IE_solver(b + M_t.dot(x)) logger.info("done") logger.info("Variable post-process") assembler.distribute_variable(x) logger.info("done") logger.info("Export variable") save.write_vtk(variables, time_step=i) logger.info("done") logger.info("Compute the production") outflow[i] = compute_outflow(gb, param) logger.info("done") time = np.arange(param["n_steps"]) * param["time_step"] save.write_pvd(time) logger.info("Save outflow on file") file_out = param["folder"] + "/outflow.csv" data = np.vstack((time, outflow)).T np.savetxt(file_out, data, delimiter=",") logger.info("done") logger.info("Save dof on file") file_out = param["folder"] + "/dof_transport.csv" np.savetxt(file_out, get_dof(assembler), delimiter=",", fmt="%d") logger.info("done")
def flow(gb, discr, param, bc_flag): model = "flow" model_data = data_flow(gb, discr, model, param, bc_flag) # discretization operator name flux_id = "flux" # master variable name variable = "flow_variable" mortar = "lambda_" + variable # post process variables pressure = "pressure" flux = "darcy_flux" # it has to be this one # save variable name for the advection-diffusion problem param["pressure"] = pressure param["flux"] = flux param["mortar_flux"] = mortar discr_scheme = discr["scheme"](model_data) discr_interface = pp.CellDofFaceDofMap(model_data) coupling = pp.FluxPressureContinuity(model_data, discr_scheme, discr_interface) # define the dof and discretization for the grids for g, d in gb: if g.dim == gb.dim_max(): d[pp.PRIMARY_VARIABLES] = {variable: discr["dof"]} d[pp.DISCRETIZATION] = {variable: {flux_id: discr_scheme}} else: d[pp.PRIMARY_VARIABLES] = {variable: {"cells": 1}} d[pp.DISCRETIZATION] = {variable: {flux_id: discr_interface}} # define the interface terms to couple the grids for e, d in gb.edges(): g_slave, g_master = gb.nodes_of_edge(e) d[pp.PRIMARY_VARIABLES] = {mortar: {"cells": 1}} d[pp.COUPLING_DISCRETIZATION] = { flux: { g_slave: (variable, flux_id), g_master: (variable, flux_id), e: (mortar, coupling), } } # solution of the darcy problem assembler = pp.Assembler(gb) logger.info("Assemble the flow problem") A, b = assembler.assemble_matrix_rhs() logger.info("done") logger.info("Solve the linear system") x = sps.linalg.spsolve(A, b) logger.info("done") logger.info("Variable post-process") assembler.distribute_variable(x) # extract the pressure from the solution for g, d in gb: if g.dim == 2: d[pp.STATE][pressure] = discr_scheme.extract_pressure(g, d[pp.STATE][variable], d) d[pp.STATE][flux] = discr_scheme.extract_flux(g, d[pp.STATE][variable], d) else: d[pp.STATE][pressure] = np.zeros(g.num_cells) d[pp.STATE][flux] = np.zeros(g.num_faces) # export the P0 flux reconstruction only for some scheme if discr["scheme"] is pp.MVEM or discr["scheme"] is pp.RT0: P0_flux = "P0_flux" param["P0_flux"] = P0_flux pp.project_flux(gb, discr_scheme, flux, P0_flux, mortar) logger.info("done") logger.info("Save dof on file") file_out = param["folder"] + "/dof_flow.csv" np.savetxt(file_out, get_dof(assembler), delimiter=",", fmt="%d") logger.info("done")