def __init__(self, gb, flow, model="flow"): self.model = model self.gb = gb self.data = None self.assembler = None # discretization operator name self.discr_name = self.model + "_flux" self.discr = pp.Tpfa(self.model) self.coupling_name = self.discr_name + "_coupling" self.coupling = pp.RobinCoupling(self.model, self.discr) self.source_name = self.model + "_source" self.source = pp.ScalarSource(self.model) # master variable name self.variable = self.model + "_variable" self.mortar = self.model + "_lambda" # post process variables self.pressure = "pressure" self.flux = "darcy_flux" # it has to be this one self.P0_flux = "P0_darcy_flux"
def run_flow(gb, partition, folder): grid_variable = "pressure" mortar_variable = "mortar_flux" # Identifier of the discretization operator on each grid diffusion_term = "diffusion" # Identifier of the discretization operator between grids coupling_operator_keyword = "coupling_operator" # Loop over the nodes in the GridBucket, define primary variables and discretization schemes for g, d in gb: # retrieve the scheme discr = d["discr"] # Assign primary variables on this grid. It has one degree of freedom per cell. d[pp.PRIMARY_VARIABLES] = {grid_variable: discr["dof"]} # Assign discretization operator for the variable. d[pp.DISCRETIZATION] = { grid_variable: { diffusion_term: discr["scheme"] } } # Loop over the edges in the GridBucket, define primary variables and discretizations for e, d in gb.edges(): # The mortar variable has one degree of freedom per cell in the mortar grid d[pp.PRIMARY_VARIABLES] = {mortar_variable: {"cells": 1}} # edge discretization discr1 = gb.node_props( e[0], pp.DISCRETIZATION)[grid_variable][diffusion_term] discr2 = gb.node_props( e[1], pp.DISCRETIZATION)[grid_variable][diffusion_term] edge_discretization = pp.RobinCoupling("flow", discr1, discr2) # The coupling discretization links an edge discretization with variables # and discretization operators on each neighboring grid d[pp.COUPLING_DISCRETIZATION] = { coupling_operator_keyword: { e[0]: (grid_variable, diffusion_term), e[1]: (grid_variable, diffusion_term), e: (mortar_variable, edge_discretization), } } assembler = pp.Assembler(gb, active_variables=[grid_variable, mortar_variable]) assembler.discretize() # Assemble the linear system, using the information stored in the GridBucket A, b = assembler.assemble_matrix_rhs() x = sps.linalg.spsolve(A, b) assembler.distribute_variable(x) for g, d in gb: discr = d[pp.DISCRETIZATION][grid_variable][diffusion_term] d[pp.STATE]["pressure"] = discr.extract_pressure( g, d[pp.STATE][grid_variable], d) _export_flow(gb, partition, folder)
def __init__(self, gb, folder, tol): self.model = "flow" self.gb = gb self.data = None self.assembler = None # discretization operator name self.discr_name = "flux" self.discr = pp.RT0(self.model) self.mass_name = "mass" self.mass = pp.MixedMassMatrix(self.model) self.coupling_name = self.discr_name + "_coupling" self.coupling = pp.RobinCoupling(self.model, self.discr) self.source_name = "source" self.source = pp.DualScalarSource(self.model) # master variable name self.variable = "flow_variable" self.mortar = "lambda_" + self.variable # post process variables self.pressure = "pressure" self.flux = "darcy_flux" # it has to be this one self.P0_flux = "P0_darcy_flux" # tolerance self.tol = tol # exporter self.save = pp.Exporter(self.gb, "solution", folder=folder)
def set_param_flow(self, gb, no_flow=False, kn=1e3, method="mpfa"): # Set up flow field with uniform flow in y-direction kw = "flow" for g, d in gb: parameter_dictionary = {} perm = pp.SecondOrderTensor(kxx=np.ones(g.num_cells)) parameter_dictionary["second_order_tensor"] = perm b_val = np.zeros(g.num_faces) if g.dim == 2: bound_faces = pp.face_on_side(g, ["ymin", "ymax"]) if no_flow: b_val[bound_faces[0]] = 1 b_val[bound_faces[1]] = 1 bound_faces = np.hstack((bound_faces[0], bound_faces[1])) labels = np.array(["dir"] * bound_faces.size) parameter_dictionary["bc"] = pp.BoundaryCondition( g, bound_faces, labels) y_max_faces = pp.face_on_side(g, "ymax")[0] b_val[y_max_faces] = 1 else: parameter_dictionary["bc"] = pp.BoundaryCondition(g) parameter_dictionary["bc_values"] = b_val parameter_dictionary["mpfa_inverter"] = "python" d[pp.PARAMETERS] = pp.Parameters(g, [kw], [parameter_dictionary]) d[pp.DISCRETIZATION_MATRICES] = {"flow": {}} gb.add_edge_props("kn") for e, d in gb.edges(): mg = d["mortar_grid"] flow_dictionary = { "normal_diffusivity": 2 * kn * np.ones(mg.num_cells) } d[pp.PARAMETERS] = pp.Parameters(keywords=["flow"], dictionaries=[flow_dictionary]) d[pp.DISCRETIZATION_MATRICES] = {"flow": {}} discretization_key = kw + "_" + pp.DISCRETIZATION for g, d in gb: # Choose discretization and define the solver if method == "mpfa": discr = pp.Mpfa(kw) elif method == "mvem": discr = pp.MVEM(kw) else: discr = pp.Tpfa(kw) d[discretization_key] = discr for _, d in gb.edges(): d[discretization_key] = pp.RobinCoupling(kw, discr)
def __init__(self, keyword, edges): if isinstance(edges, list): self._edges = edges else: self._edges = [edges] self._discretization = pp.RobinCoupling(keyword, primary_keyword=keyword) self._name = "Robin interface coupling" self.keyword = keyword self.mortar_scaling: _MergedOperator self.mortar_discr: _MergedOperator _wrap_discretization(self, self._discretization, edges)
def setup_flow_assembler(gb, method, data_key=None, coupler=None): """ Setup a standard assembler for the flow problem for a given grid bucket. The assembler will be set up with primary variable name 'pressure' on the GridBucket nodes, and mortar_flux for the mortar variables. Parameters: gb: GridBucket. method (EllipticDiscretization). data_key (str, optional): Keyword used to identify data dictionary for node and edge discretization. Coupler (EllipticInterfaceLaw): Defaults to RobinCoulping. Returns: Assembler, ready to discretize and assemble problem. """ if data_key is None: data_key = "flow" if coupler is None: coupler = pp.RobinCoupling(data_key, method) if isinstance(method, pp.MVEM) or isinstance(method, pp.RT0): mixed_form = True else: mixed_form = False for g, d in gb: if mixed_form: d[pp.PRIMARY_VARIABLES] = {"pressure": {"cells": 1, "faces": 1}} else: d[pp.PRIMARY_VARIABLES] = {"pressure": {"cells": 1}} d[pp.DISCRETIZATION] = {"pressure": {"diffusive": method}} for e, d in gb.edges(): g1, g2 = gb.nodes_of_edge(e) d[pp.PRIMARY_VARIABLES] = {"mortar_flux": {"cells": 1}} d[pp.COUPLING_DISCRETIZATION] = { "lambda": { g1: ("pressure", "diffusive"), g2: ("pressure", "diffusive"), e: ("mortar_flux", coupler), } } d[pp.DISCRETIZATION_MATRICES] = {"flow": {}} assembler = pp.Assembler(gb) return assembler
def assign_scalar_discretizations(self) -> None: """ Assign discretizations to the nodes and edges of the grid bucket. Note the attribute subtract_fracture_pressure: Indicates whether or not to subtract the fracture pressure contribution for the contact traction. This should not be done if the scalar variable is temperature. """ gb = self.gb # Shorthand key_s = self.scalar_parameter_key var_s = self.scalar_variable discr_key, coupling_discr_key = pp.DISCRETIZATION, pp.COUPLING_DISCRETIZATION # Scalar discretizations (all dimensions) diff_disc_s = implicit_euler.ImplicitMpfa(key_s) mass_disc_s = implicit_euler.ImplicitMassMatrix(key_s, var_s) source_disc_s = pp.ScalarSource(key_s) # Assign node discretizations for _, d in gb: add_nonpresent_dictionary(d, discr_key) d[discr_key].update({ var_s: { "diffusion": diff_disc_s, "mass": mass_disc_s, "source": source_disc_s, }, }) # Assign edge discretizations for e, d in gb.edges(): g_l, g_h = gb.nodes_of_edge(e) add_nonpresent_dictionary(d, coupling_discr_key) d[coupling_discr_key].update({ self.scalar_coupling_term: { g_h: (var_s, "diffusion"), g_l: (var_s, "diffusion"), e: ( self.mortar_scalar_variable, pp.RobinCoupling(key_s, diff_disc_s), ), }, })
def assign_discretizations(self) -> None: """ Assign discretizations to the nodes and edges of the grid bucket. Note the attribute subtract_fracture_pressure: Indicates whether or not to subtract the fracture pressure contribution for the contact traction. This should not be done if the scalar variable is temperature. """ # from porepy.utils.derived_discretizations import ( # implicit_euler as IE_discretizations, # ) # Shorthand key_s = self.scalar_parameter_key var_s = self.scalar_variable # Scalar discretizations (all dimensions) diff_disc_s = pp.Mpfa(key_s) # mass_disc_s = pp.MassMatrix(key_s) # diff_disc_s = IE_discretizations.ImplicitMpfa(key_s) # mass_disc_s = IE_discretizations.ImplicitMassMatrix(key_s, var_s) source_disc_s = pp.ScalarSource(key_s) # Assign node discretizations for g, d in self.gb: d[pp.DISCRETIZATION] = { var_s: { "diffusion": diff_disc_s, # "mass": mass_disc_s, "source": source_disc_s, }, } for e, d in self.gb.edges(): g_l, g_h = self.gb.nodes_of_edge(e) d[pp.COUPLING_DISCRETIZATION] = { self.scalar_coupling_term: { g_h: (var_s, "diffusion"), g_l: (var_s, "diffusion"), e: ( self.mortar_scalar_variable, pp.RobinCoupling(key_s, diff_disc_s), ), }, }
def solve(self, kf, description, is_coarse=False): gb, domain = pp.grid_buckets_2d.benchmark_regular( {"mesh_size_frac": 0.045}, is_coarse) # Assign parameters setup.add_data(gb, domain, kf) key = "flow" method = pp.MVEM(key) coupler = pp.RobinCoupling(key, method) for g, d in gb: d[pp.PRIMARY_VARIABLES] = {"pressure": {"cells": 1, "faces": 1}} d[pp.DISCRETIZATION] = {"pressure": {"diffusive": method}} for e, d in gb.edges(): g1, g2 = gb.nodes_of_edge(e) d[pp.PRIMARY_VARIABLES] = {"mortar_solution": {"cells": 1}} d[pp.COUPLING_DISCRETIZATION] = { "lambda": { g1: ("pressure", "diffusive"), g2: ("pressure", "diffusive"), e: ("mortar_solution", coupler), } } d[pp.DISCRETIZATION_MATRICES] = {"flow": {}} assembler = pp.Assembler(gb) # Discretize assembler.discretize() A, b = assembler.assemble_matrix_rhs() p = sps.linalg.spsolve(A, b) assembler.distribute_variable(p) for g, d in gb: d[pp.STATE]["darcy_flux"] = d[pp.STATE]["pressure"][:g.num_faces] d[pp.STATE]["pressure"] = d[pp.STATE]["pressure"][g.num_faces:]
def solve(self, kf, description, multi_point): mesh_size = 0.045 gb, domain = setup.make_grid_bucket(mesh_size) # Assign parameters setup.add_data(gb, domain, kf) key = "flow" if multi_point: method = pp.Mpfa(key) else: method = pp.Tpfa(key) coupler = pp.RobinCoupling(key, method) for g, d in gb: d[pp.PRIMARY_VARIABLES] = {"pressure": {"cells": 1}} d[pp.DISCRETIZATION] = {"pressure": {"diffusive": method}} for e, d in gb.edges(): g1, g2 = gb.nodes_of_edge(e) d[pp.PRIMARY_VARIABLES] = {"mortar_solution": {"cells": 1}} d[pp.COUPLING_DISCRETIZATION] = { "lambda": { g1: ("pressure", "diffusive"), g2: ("pressure", "diffusive"), e: ("mortar_solution", coupler), } } d[pp.DISCRETIZATION_MATRICES] = {"flow": {}} assembler = pp.Assembler(gb) # Discretize assembler.discretize() A, b = assembler.assemble_matrix_rhs() p = sps.linalg.spsolve(A, b) assembler.distribute_variable(p)
def assign_discretizations(self) -> None: """ Assign discretizations to the nodes and edges of the grid bucket. Note the attribute subtract_fracture_pressure: Indicates whether or not to subtract the fracture pressure contribution for the contact traction. This should not be done if the scalar variable is temperature. """ # Call parent class for disrcetizations for the poro-elastic system. super().assign_discretizations() # What remains is terms related to temperature # Shorthand for parameter keywords key_t = self.temperature_parameter_key key_m = self.mechanics_parameter_key # distinguish the coupling terms from temperature terms (grad_p, as opposed to grad_T) key_mt = self.mechanics_temperature_parameter_key var_s = self.scalar_variable var_t = self.temperature_variable var_d = self.displacement_variable # Define discretization # Scalar discretizations (will be assigned to all dimensions) diff_disc_t = IE_discretizations.ImplicitMpfa(key_t) adv_disc_t = IE_discretizations.ImplicitUpwind(key_t) mass_disc_t = IE_discretizations.ImplicitMassMatrix(key_t, var_t) source_disc_t = pp.ScalarSource(key_t) # Coupling discretizations # All dimensions div_u_disc_t = pp.DivU( key_m, key_t, variable=var_d, mortar_variable=self.mortar_displacement_variable, ) # Nd grad_t_disc = pp.GradP( key_mt ) # pp.GradP(key_m) # Kanskje denne (og andre) b;r erstattes av spesiallagde # varianter som henter ut s-varianten og ganger med alpha/beta? stabilization_disc_t = pp.BiotStabilization(key_t, var_t) s2t_disc = IE_discretizations.ImplicitMassMatrix( keyword=self.s2t_parameter_key, variable=var_s) t2s_disc = IE_discretizations.ImplicitMassMatrix( keyword=self.t2s_parameter_key, variable=var_t) # Assign node discretizations for g, d in self.gb: if g.dim == self.Nd: d[pp.DISCRETIZATION].update( { # advection-diffusion equation for temperature var_t: { "diffusion": diff_disc_t, self.advection_term: adv_disc_t, "mass": mass_disc_t, # Also the stabilization term from Biot "stabilization": stabilization_disc_t, "source": source_disc_t, }, # grad T term in the momentuum equation var_d + "_" + var_t: {"grad_p": grad_t_disc}, # div u term in the energy equation var_t + "_" + var_d: {"div_u": div_u_disc_t}, # Accumulation of pressure in energy equation var_t + "_" + var_s: {self.s2t_coupling_term: s2t_disc}, # Accumulation of temperature / energy in pressure equation var_s + "_" + var_t: {self.t2s_coupling_term: t2s_disc}, } ) else: # Inside fracture network d[pp.DISCRETIZATION].update( { # Advection-diffusion equation, no biot stabilization var_t: { "diffusion": diff_disc_t, self.advection_term: adv_disc_t, "mass": mass_disc_t, "source": source_disc_t, }, # Standard coupling terms var_t + "_" + var_s: {self.s2t_coupling_term: s2t_disc}, var_s + "_" + var_t: {self.t2s_coupling_term: t2s_disc}, } ) # Next, the edges in the gb. # Account for the mortar displacements effect on energy balance in the matrix, # as an internal boundary contribution div_u_coupling_t = pp.DivUCoupling(self.displacement_variable, div_u_disc_t, div_u_disc_t) # Account for the temperature contributions to the force balance on the fracture # (see contact_discr). # This discretization needs the keyword used to store the grad p discretization: grad_t_key = key_mt matrix_temperature_to_force_balance = pp.MatrixScalarToForceBalance( grad_t_key, mass_disc_t, mass_disc_t) # Coupling of advection and diffusion terms in the energy equation adv_coupling_t = IE_discretizations.ImplicitUpwindCoupling(key_t) diff_coupling_t = pp.RobinCoupling(key_t, diff_disc_t) for e, d in self.gb.edges(): g_l, g_h = self.gb.nodes_of_edge(e) d[pp.COUPLING_DISCRETIZATION].update({ self.advection_coupling_term: { g_h: (self.temperature_variable, self.advection_term), g_l: (self.temperature_variable, self.advection_term), e: (self.mortar_temperature_advection_variable, adv_coupling_t), }, self.temperature_coupling_term: { g_h: (var_t, "diffusion"), g_l: (var_t, "diffusion"), e: (self.mortar_temperature_variable, diff_coupling_t), }, }) if g_h.dim == self.Nd: d[pp.COUPLING_DISCRETIZATION].update({ "div_u_coupling_t": { g_h: ( var_t, "mass", ), # This is really the div_u, but this is not implemented g_l: (var_t, "mass"), e: (self.mortar_displacement_variable, div_u_coupling_t), }, "matrix_temperature_to_force_balance": { g_h: (var_t, "mass"), g_l: (var_t, "mass"), e: ( self.mortar_displacement_variable, matrix_temperature_to_force_balance, ), }, })
def flow(gb, param): model = "flow" model_data = data.flow(gb, model, param) # discretization operator name flux_id = "flux" # master variable name variable = "flux_pressure" mortar = "lambda_" + variable # post process variables pressure = "pressure" flux = "darcy_flux" # it has to be this one P0_flux = "P0_flux" # save variable name for the advection-diffusion problem param["pressure"] = pressure param["flux"] = flux param["P0_flux"] = P0_flux param["mortar_flux"] = mortar # discretization operators discr = pp.MVEM(model_data) coupling = pp.RobinCoupling(model_data, discr) # define the dof and discretization for the grids for g, d in gb: d[pp.PRIMARY_VARIABLES] = {variable: {"cells": 1, "faces": 1}} d[pp.DISCRETIZATION] = {variable: {flux_id: discr}} # 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] = { variable: { g_slave: (variable, flux_id), g_master: (variable, flux_id), e: (mortar, coupling) } } # solution of the darcy problem assembler = pp.Assembler() logger.info("Assemble the flow problem") A, b, block_dof, full_dof = assembler.assemble_matrix_rhs(gb) logger.info("done") logger.info("Solve the linear system") up = sps.linalg.spsolve(A, b) logger.info("done") logger.info("Variable post-process") assembler.distribute_variable(gb, up, block_dof, full_dof) for g, d in gb: d[pressure] = discr.extract_pressure(g, d[variable]) d[flux] = discr.extract_flux(g, d[variable]) pp.project_flux(gb, discr, flux, P0_flux, mortar) logger.info("done") return model_data
def advdiff(gb, param, model_flow): model = "transport" model_data_adv, model_data_diff = data.advdiff(gb, model, model_flow, param) # discretization operator names adv_id = "advection" diff_id = "diffusion" # variable names variable = "scalar" mortar_adv = "lambda_" + variable + "_" + adv_id mortar_diff = "lambda_" + variable + "_" + diff_id # discretization operatr discr_adv = pp.Upwind(model_data_adv) discr_diff = pp.Tpfa(model_data_diff) coupling_adv = pp.UpwindCoupling(model_data_adv) coupling_diff = pp.RobinCoupling(model_data_diff, discr_diff) for g, d in gb: d[pp.PRIMARY_VARIABLES] = {variable: {"cells": 1}} d[pp.DISCRETIZATION] = { variable: { adv_id: discr_adv, diff_id: discr_diff } } 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() logger.info( "Assemble the advective and diffusive terms of the transport problem") A, b, block_dof, full_dof = assembler.assemble_matrix_rhs(gb) logger.info("done") # mass term mass_id = "mass" discr_mass = pp.MassMatrix(model_data_adv) for g, d in gb: d[pp.PRIMARY_VARIABLES] = {variable: {"cells": 1}} d[pp.DISCRETIZATION] = {variable: {mass_id: discr_mass}} gb.remove_edge_props(pp.COUPLING_DISCRETIZATION) 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 } } logger.info("Assemble the mass term of the transport problem") M, _, _, _ = assembler.assemble_matrix_rhs(gb) logger.info("done") # Perform an LU factorization to speedup the solver #IE_solver = sps.linalg.factorized((M + A).tocsc()) # time loop logger.info("Prepare the exporting") save = pp.Exporter(gb, "solution", folder=param["folder"]) logger.info("done") variables = [variable, param["pressure"], param["P0_flux"]] x = np.ones(A.shape[0]) * param["initial_advdiff"] logger.info("Start the time loop with " + str(param["n_steps"]) + " steps") for i in np.arange(param["n_steps"]): #x = IE_solver(b + M.dot(x)) logger.info("Solve the linear system for time step " + str(i)) x = sps.linalg.spsolve(M + A, b + M.dot(x)) logger.info("done") logger.info("Variable post-process") assembler.distribute_variable(gb, x, block_dof, full_dof) logger.info("done") logger.info("Export variable") save.write_vtk(variables, time_step=i) logger.info("done") save.write_pvd(np.arange(param["n_steps"]) * param["time_step"])
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 test_md_flow(): # Three fractures, will create intersection lines and point frac_1 = np.array([[2, 4, 4, 2], [3, 3, 3, 3], [0, 0, 6, 6]]) frac_2 = np.array([[3, 3, 3, 3], [2, 4, 4, 2], [0, 0, 6, 6]]) frac_3 = np.array([[0, 6, 6, 0], [2, 2, 4, 4], [3, 3, 3, 3]]) gb = pp.meshing.cart_grid(fracs=[frac_1, frac_2, frac_3], nx=np.array([6, 6, 6])) gb.compute_geometry() pressure_variable = "pressure" flux_variable = "mortar_flux" keyword = "flow" discr = pp.Tpfa(keyword) source_discr = pp.ScalarSource(keyword) coupling_discr = pp.RobinCoupling(keyword, discr, discr) for g, d in gb: # Assign data if g.dim == gb.dim_max(): upper_left_ind = np.argmax(np.linalg.norm(g.face_centers, axis=0)) bc = pp.BoundaryCondition(g, np.array([0, upper_left_ind]), ["dir", "dir"]) bc_values = np.zeros(g.num_faces) bc_values[0] = 1 sources = np.random.rand(g.num_cells) * g.cell_volumes specified_parameters = { "bc": bc, "bc_values": bc_values, "source": sources } else: sources = np.random.rand(g.num_cells) * g.cell_volumes specified_parameters = {"source": sources} # Initialize data pp.initialize_default_data(g, d, keyword, specified_parameters) # Declare grid primary variable d[pp.PRIMARY_VARIABLES] = {pressure_variable: {"cells": 1}} # Assign discretization d[pp.DISCRETIZATION] = { pressure_variable: { "diff": discr, "source": source_discr } } # Initialize state d[pp.STATE] = { pressure_variable: np.zeros(g.num_cells), pp.ITERATE: { pressure_variable: np.zeros(g.num_cells) }, } for e, d in gb.edges(): mg = d["mortar_grid"] pp.initialize_data(mg, d, keyword, {"normal_diffusivity": 1}) d[pp.PRIMARY_VARIABLES] = {flux_variable: {"cells": 1}} d[pp.COUPLING_DISCRETIZATION] = {} d[pp.COUPLING_DISCRETIZATION]["coupling"] = { e[0]: (pressure_variable, "diff"), e[1]: (pressure_variable, "diff"), e: (flux_variable, coupling_discr), } d[pp.STATE] = { flux_variable: np.zeros(mg.num_cells), pp.ITERATE: { flux_variable: np.zeros(mg.num_cells) }, } dof_manager = pp.DofManager(gb) assembler = pp.Assembler(gb, dof_manager) assembler.discretize() # Reference discretization A_ref, b_ref = assembler.assemble_matrix_rhs() manager = pp.ad.EquationManager(gb, dof_manager) grid_list = [g for g, _ in gb] edge_list = [e for e, _ in gb.edges()] node_discr = pp.ad.MpfaAd(keyword, grid_list) edge_discr = pp.ad.RobinCouplingAd(keyword, edge_list) bc_val = pp.ad.BoundaryCondition(keyword, grid_list) source = pp.ad.ParameterArray(param_keyword=keyword, array_keyword='source', grids=grid_list) projections = pp.ad.MortarProjections(gb=gb) div = pp.ad.Divergence(grids=grid_list) p = manager.merge_variables([(g, pressure_variable) for g in grid_list]) lmbda = manager.merge_variables([(e, flux_variable) for e in edge_list]) flux = (node_discr.flux * p + node_discr.bound_flux * bc_val + node_discr.bound_flux * projections.mortar_to_primary_int * lmbda) flow_eq = div * flux - projections.mortar_to_secondary_int * lmbda - source interface_flux = edge_discr.mortar_scaling * ( projections.primary_to_mortar_avg * node_discr.bound_pressure_cell * p + projections.primary_to_mortar_avg * node_discr.bound_pressure_face * projections.mortar_to_primary_int * lmbda - projections.secondary_to_mortar_avg * p + edge_discr.mortar_discr * lmbda) flow_eq_ad = pp.ad.Expression(flow_eq, dof_manager, "flow on nodes") flow_eq_ad.discretize(gb) interface_eq_ad = pp.ad.Expression(interface_flux, dof_manager, "flow on interface") manager.equations += [flow_eq_ad, interface_eq_ad] state = np.zeros(gb.num_cells() + gb.num_mortar_cells()) A, b = manager.assemble_matrix_rhs(state=state) diff = A - A_ref if diff.data.size > 0: assert np.max(np.abs(diff.data)) < 1e-10 assert np.max(np.abs(b - b_ref)) < 1e-10
def flow(gb, param, bc_flag): model = "flow" model_data = data_flow(gb, 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 # define the dof and discretization for the grids for g, d in gb: d[pp.PRIMARY_VARIABLES] = {variable: {"cells": 1, "faces": 1}} if g.dim == gb.dim_max(): discr = pp.RT0(model_data) else: discr = RT0Multilayer(model_data) d[pp.DISCRETIZATION] = {variable: {flux_id: discr}} # 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}} # retrive the discretization of the master and slave grids discr_master = gb.node_props(g_master, pp.DISCRETIZATION)[variable][flux_id] discr_slave = gb.node_props(g_slave, pp.DISCRETIZATION)[variable][flux_id] if g_master.dim == gb.dim_max(): # classical 2d-1d/3d-2d coupling condition coupling = pp.RobinCoupling(model_data, discr_master, discr_slave) d[pp.COUPLING_DISCRETIZATION] = { flux: { g_slave: (variable, flux_id), g_master: (variable, flux_id), e: (mortar, coupling), } } elif g_master.dim < gb.dim_max(): # the multilayer coupling condition coupling = RobinCouplingMultiLayer(model_data, discr_master, discr_slave) 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() logger.info("Assemble the flow problem") A, b, block_dof, full_dof = assembler.assemble_matrix_rhs(gb) 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(gb, x, block_dof, full_dof) for g, d in gb: d[pressure] = discr.extract_pressure(g, d[variable], d) d[flux] = discr.extract_flux(g, d[variable], d) # export the P0 flux reconstruction P0_flux = "P0_flux" param["P0_flux"] = P0_flux pp.project_flux(gb, discr, flux, P0_flux, mortar) # identification of layer and fault for g, d in gb: # save the identification of the fault if "fault" in g.name: d["fault"] = np.ones(g.num_cells) d["layer"] = np.zeros(g.num_cells) # save the identification of the layer elif "layer" in g.name: d["fault"] = np.zeros(g.num_cells) half_cells = int(g.num_cells / 2) d["layer"] = np.hstack( (np.ones(half_cells), 2 * np.ones(half_cells))) # save zero for the other cases else: d["fault"] = np.zeros(g.num_cells) d["layer"] = np.zeros(g.num_cells) save = pp.Exporter(gb, "solution", folder=param["folder"]) save.write_vtk([pressure, P0_flux, "fault", "layer"]) logger.info("done")
def permeability_upscaling(network, data, mesh_args, directions, do_viz=True, tracer_transport=False): """ Compute bulk permeabilities for a 2d domain with a fracture network. The function sets up a flow field in the specified directions, and calculates an upscaled permeability of corresponding to the calculated flow configuration. Parameters: network (pp.FractureNetwork2d): Network, with domain. data (dictionary): Data to be specified. mesh_args (dictionray): Parameters for meshing. See FractureNetwork2d.mesh() for details. directions (np.array): Directions to upscale permeabilities. Indicated by 0 (x-direction) and or 1 (y-direction). Returns: np.array, dim directions.size: Upscaled permeability in each of the directions. sim_info: Various information on the time spent on this simulation. """ directions = np.asarray(directions) upscaled_perm = np.zeros(directions.size) sim_info = {} for di, direct in enumerate(directions): tic = time.time() filename = str(uuid.uuid4()) gb = network.mesh(tol=network.tol, mesh_args=mesh_args, file_name=filename) toc = time.time() if di == 0: sim_info["num_cells_2d"] = gb.grids_of_dimension(2)[0].num_cells sim_info["time_for_meshing"] = toc - tic gb = _setup_simulation_flow(gb, data, direct) pressure_kw = "flow" mpfa = pp.Mpfa(pressure_kw) for g, d in gb: d[pp.PRIMARY_VARIABLES] = {"pressure": {"cells": 1}} d[pp.DISCRETIZATION] = {"pressure": {"diffusive": mpfa}} coupler = pp.RobinCoupling(pressure_kw, mpfa) for e, d in gb.edges(): g1, g2 = gb.nodes_of_edge(e) d[pp.PRIMARY_VARIABLES] = {"mortar_darcy_flux": {"cells": 1}} d[pp.COUPLING_DISCRETIZATION] = { "lambda": { g1: ("pressure", "diffusive"), g2: ("pressure", "diffusive"), e: ("mortar_darcy_flux", coupler), } } assembler = pp.Assembler(gb) assembler.discretize() # Discretize tic = time.time() A, b = assembler.assemble_matrix_rhs() if di == 0: toc = time.time() sim_info["Time_for_assembly"] = toc - tic tic = time.time() p = spla.spsolve(A, b) if di == 0: toc = time.time() sim_info["Time_for_pressure_solve"] = toc - tic assembler.distribute_variable(p) # Post processing to recover flux tot_pressure = 0 tot_area = 0 tot_inlet_flux = 0 for g, d in gb: # Inlet faces of this grid inlet = d.get("inlet_faces", None) # If no inlet, no need to do anything if inlet is None or inlet.size == 0: continue # Compute flux field in the grid # Internal flux flux = (d[pp.DISCRETIZATION_MATRICES][pressure_kw]["flux"] * d[pp.STATE]["pressure"]) # Contribution from the boundary bound_flux_discr = d[ pp.DISCRETIZATION_MATRICES][pressure_kw]["bound_flux"] flux += bound_flux_discr * d["parameters"][pressure_kw]["bc_values"] # Add contribution from all neighboring lower-dimensional interfaces for e, d_e in gb.edges_of_node(g): mg = d_e["mortar_grid"] if mg.dim == g.dim - 1: flux += (bound_flux_discr * mg.mortar_to_master_int() * d_e[pp.STATE]["mortar_darcy_flux"]) # Add contribution to total inlet flux tot_inlet_flux += np.abs(flux[inlet]).sum() # Next, find the pressure at the inlet faces # The pressure is calculated as the values in the neighboring cells, # plus an offset from the incell-variations pressure_cell = (d[pp.DISCRETIZATION_MATRICES][pressure_kw] ["bound_pressure_cell"] * d[pp.STATE]["pressure"]) pressure_flux = (d[pp.DISCRETIZATION_MATRICES][pressure_kw] ["bound_pressure_face"] * d["parameters"][pressure_kw]["bc_values"]) inlet_pressure = pressure_cell[inlet] + pressure_flux[inlet] # Scale the pressure at the face with the face length. # Also include an aperture scaling here: For the highest dimensional grid, this # will be as scaling with 1. aperture = d[pp.PARAMETERS][pressure_kw]["aperture"][0] tot_pressure += np.sum(inlet_pressure * g.face_areas[inlet] * aperture) # Add to the toal outlet area tot_area += g.face_areas[inlet].sum() * aperture # Also compute the cross sectional area of the domain if g.dim == gb.dim_max(): # Extension of the domain in the active direction dx = g.nodes[direct].max() - g.nodes[direct].min() # Mean pressure at the inlet, which will also be mean pressure difference # over the domain mean_pressure = tot_pressure / tot_area # The upscaled permeability upscaled_perm[di] = (tot_inlet_flux / tot_area) * dx / mean_pressure # End of post processing for permeability upscaling if tracer_transport: _setup_simulation_tracer(gb, data, direct) tracer_variable = "temperature" temperature_kw = "transport" mortar_variable = "lambda_adv" # Identifier of the two terms of the equation adv = "advection" advection_term = "advection" mass_term = "mass" advection_coupling_term = "advection_coupling" adv_discr = pp.Upwind(temperature_kw) adv_coupling = pp.UpwindCoupling(temperature_kw) mass_discretization = pp.MassMatrix(temperature_kw) for g, d in gb: d[pp.PRIMARY_VARIABLES] = {tracer_variable: {"cells": 1}} d[pp.DISCRETIZATION] = { tracer_variable: { advection_term: adv_discr, mass_term: mass_discretization, } } for e, d in gb.edges(): g1, g2 = gb.nodes_of_edge(e) d[pp.PRIMARY_VARIABLES] = {mortar_variable: {"cells": 1}} d[pp.COUPLING_DISCRETIZATION] = { advection_coupling_term: { g1: (tracer_variable, adv), g2: (tracer_variable, adv), e: (mortar_variable, adv_coupling), } } pp.fvutils.compute_darcy_flux(gb, keyword_store=temperature_kw, lam_name="mortar_darcy_flux") A, b, block_dof, full_dof = assembler.assemble_matrix_rhs( gb, active_variables=[tracer_variable, mortar_variable], add_matrices=False, ) advection_coupling_term += ("_" + mortar_variable + "_" + tracer_variable + "_" + tracer_variable) mass_term += "_" + tracer_variable advection_term += "_" + tracer_variable lhs = A[mass_term] + data["time_step"] * ( A[advection_term] + A[advection_coupling_term]) rhs_source_adv = data["time_step"] * (b[advection_term] + b[advection_coupling_term]) IEsolver = spla.factorized(lhs) save_every = 10 n_steps = int(np.round(data["t_max"] / data["time_step"])) # Initial condition tracer = np.zeros(rhs_source_adv.size) assembler.distribute_variable( gb, tracer, block_dof, full_dof, variable_names=[tracer_variable, mortar_variable], ) # Exporter exporter = pp.Exporter(gb, name=tracer_variable, folder="viz_tmp") export_fields = [tracer_variable] for i in range(n_steps): if np.isclose(i % save_every, 0): # Export existing solution (final export is taken care of below) assembler.distribute_variable( gb, tracer, block_dof, full_dof, variable_names=[tracer_variable, mortar_variable], ) exporter.write_vtk(export_fields, time_step=int(i // save_every)) tracer = IEsolver(A[mass_term] * tracer + rhs_source_adv) exporter.write_vtk(export_fields, time_step=(n_steps // save_every)) time_steps = np.arange(0, data["t_max"] + data["time_step"], save_every * data["time_step"]) exporter.write_pvd(time_steps) return upscaled_perm, sim_info
def _assign_discretizations(self) -> None: """ Assign discretizations to the nodes and edges of the grid bucket. Note the attribute subtract_fracture_pressure: Indicates whether or not to subtract the fracture pressure contribution for the contact traction. This should not be done if the scalar variable is temperature. """ if not self._use_ad: # Shorthand key_s, key_m = self.scalar_parameter_key, self.mechanics_parameter_key var_s, var_d = self.scalar_variable, self.displacement_variable # Define discretization # For the Nd domain we solve linear elasticity with mpsa. mpsa = pp.Mpsa(key_m) empty_discr = pp.VoidDiscretization(key_m, ndof_cell=self._Nd) # Scalar discretizations (all dimensions) diff_disc_s = IE_discretizations.ImplicitMpfa(key_s) mass_disc_s = IE_discretizations.ImplicitMassMatrix(key_s, var_s) source_disc_s = pp.ScalarSource(key_s) # Coupling discretizations # All dimensions div_u_disc = pp.DivU( key_m, key_s, variable=var_d, mortar_variable=self.mortar_displacement_variable, ) # Nd grad_p_disc = pp.GradP(key_m) stabilization_disc_s = pp.BiotStabilization(key_s, var_s) # Assign node discretizations for g, d in self.gb: if g.dim == self._Nd: d[pp.DISCRETIZATION] = { var_d: {"mpsa": mpsa}, var_s: { "diffusion": diff_disc_s, "mass": mass_disc_s, "stabilization": stabilization_disc_s, "source": source_disc_s, }, var_d + "_" + var_s: {"grad_p": grad_p_disc}, var_s + "_" + var_d: {"div_u": div_u_disc}, } elif g.dim == self._Nd - 1: d[pp.DISCRETIZATION] = { self.contact_traction_variable: {"empty": empty_discr}, var_s: { "diffusion": diff_disc_s, "mass": mass_disc_s, "source": source_disc_s, }, } else: d[pp.DISCRETIZATION] = { var_s: { "diffusion": diff_disc_s, "mass": mass_disc_s, "source": source_disc_s, } } # Define edge discretizations for the mortar grid contact_law = pp.ColoumbContact( self.mechanics_parameter_key, self._Nd, mpsa ) contact_discr = pp.PrimalContactCoupling( self.mechanics_parameter_key, mpsa, contact_law ) # Account for the mortar displacements effect on scalar balance in the matrix, # as an internal boundary contribution, fracture, aperture changes appear as a # source contribution. div_u_coupling = pp.DivUCoupling( self.displacement_variable, div_u_disc, div_u_disc ) # Account for the pressure contributions to the force balance on the fracture # (see contact_discr). # This discretization needs the keyword used to store the grad p discretization: grad_p_key = key_m matrix_scalar_to_force_balance = pp.MatrixScalarToForceBalance( grad_p_key, mass_disc_s, mass_disc_s ) if self.subtract_fracture_pressure: fracture_scalar_to_force_balance = pp.FractureScalarToForceBalance( mass_disc_s, mass_disc_s ) for e, d in self.gb.edges(): g_l, g_h = self.gb.nodes_of_edge(e) if g_h.dim == self._Nd: d[pp.COUPLING_DISCRETIZATION] = { self.friction_coupling_term: { g_h: (var_d, "mpsa"), g_l: (self.contact_traction_variable, "empty"), (g_h, g_l): ( self.mortar_displacement_variable, contact_discr, ), }, self.scalar_coupling_term: { g_h: (var_s, "diffusion"), g_l: (var_s, "diffusion"), e: ( self.mortar_scalar_variable, pp.RobinCoupling(key_s, diff_disc_s), ), }, "div_u_coupling": { g_h: ( var_s, "mass", ), # This is really the div_u, but this is not implemented g_l: (var_s, "mass"), e: (self.mortar_displacement_variable, div_u_coupling), }, "matrix_scalar_to_force_balance": { g_h: (var_s, "mass"), g_l: (var_s, "mass"), e: ( self.mortar_displacement_variable, matrix_scalar_to_force_balance, ), }, } if self.subtract_fracture_pressure: d[pp.COUPLING_DISCRETIZATION].update( { "fracture_scalar_to_force_balance": { g_h: (var_s, "mass"), g_l: (var_s, "mass"), e: ( self.mortar_displacement_variable, fracture_scalar_to_force_balance, ), } } ) else: d[pp.COUPLING_DISCRETIZATION] = { self.scalar_coupling_term: { g_h: (var_s, "diffusion"), g_l: (var_s, "diffusion"), e: ( self.mortar_scalar_variable, pp.RobinCoupling(key_s, diff_disc_s), ), } } else: gb = self.gb Nd = self._Nd dof_manager = pp.DofManager(gb) eq_manager = pp.ad.EquationManager(gb, dof_manager) g_primary = gb.grids_of_dimension(Nd)[0] g_frac = gb.grids_of_dimension(Nd - 1).tolist() grid_list = [ g_primary, *g_frac, *gb.grids_of_dimension(Nd - 2), *gb.grids_of_dimension(Nd - 3), ] if len(gb.grids_of_dimension(Nd)) != 1: raise NotImplementedError("This will require further work") edge_list_highest = [(g_primary, g) for g in g_frac] edge_list = [e for e, _ in gb.edges()] mortar_proj_scalar = pp.ad.MortarProjections(edges=edge_list, gb=gb, nd=1) mortar_proj_vector = pp.ad.MortarProjections( edges=edge_list_highest, gb=gb, nd=self._Nd ) subdomain_proj_scalar = pp.ad.SubdomainProjections(gb=gb) subdomain_proj_vector = pp.ad.SubdomainProjections(gb=gb, nd=self._Nd) tangential_normal_proj_list = [] normal_proj_list = [] for gf in g_frac: proj = gb.node_props(gf, "tangential_normal_projection") tangential_normal_proj_list.append( proj.project_tangential_normal(gf.num_cells) ) normal_proj_list.append(proj.project_normal(gf.num_cells)) tangential_normal_proj = pp.ad.Matrix( sps.block_diag(tangential_normal_proj_list) ) normal_proj = pp.ad.Matrix(sps.block_diag(normal_proj_list)) # Ad representation of discretizations mpsa_ad = pp.ad.BiotAd(self.mechanics_parameter_key, g_primary) grad_p_ad = pp.ad.GradPAd(self.mechanics_parameter_key, g_primary) mpfa_ad = pp.ad.MpfaAd(self.scalar_parameter_key, grid_list) mass_ad = pp.ad.MassMatrixAd(self.scalar_parameter_key, grid_list) robin_ad = pp.ad.RobinCouplingAd(self.scalar_parameter_key, edge_list) div_u_ad = pp.ad.DivUAd( self.mechanics_parameter_key, grids=g_primary, mat_dict_keyword=self.scalar_parameter_key, ) stab_biot_ad = pp.ad.BiotStabilizationAd( self.scalar_parameter_key, g_primary ) coloumb_ad = pp.ad.ColoumbContactAd( self.mechanics_parameter_key, edge_list_highest ) bc_ad = pp.ad.BoundaryCondition( self.mechanics_parameter_key, grids=[g_primary] ) div_vector = pp.ad.Divergence(grids=[g_primary], dim=g_primary.dim) # Primary variables on Ad form u = eq_manager.variable(g_primary, self.displacement_variable) u_mortar = eq_manager.merge_variables( [(e, self.mortar_displacement_variable) for e in edge_list_highest] ) contact_force = eq_manager.merge_variables( [(g, self.contact_traction_variable) for g in g_frac] ) p = eq_manager.merge_variables( [(g, self.scalar_variable) for g in grid_list] ) mortar_flux = eq_manager.merge_variables( [(e, self.mortar_scalar_variable) for e in edge_list] ) u_prev = u.previous_timestep() u_mortar_prev = u_mortar.previous_timestep() p_prev = p.previous_timestep() # Reference pressure, corresponding to an initial stress free state p_reference = pp.ad.ParameterArray( param_keyword=self.mechanics_parameter_key, array_keyword="p_reference", grids=[g_primary], gb=gb, ) # Stress in g_h stress = ( mpsa_ad.stress * u + mpsa_ad.bound_stress * bc_ad + mpsa_ad.bound_stress * subdomain_proj_vector.face_restriction(g_primary) * mortar_proj_vector.mortar_to_primary_avg * u_mortar + grad_p_ad.grad_p * subdomain_proj_scalar.cell_restriction(g_primary) * p # The reference pressure is only defined on g_primary, thus there is no need # for a subdomain projection. - grad_p_ad.grad_p * p_reference ) momentum_eq = pp.ad.Expression( div_vector * stress, dof_manager, "momentuum", grid_order=[g_primary] ) jump = ( subdomain_proj_vector.cell_restriction(g_frac) * mortar_proj_vector.mortar_to_secondary_avg * mortar_proj_vector.sign_of_mortar_sides ) jump_rotate = tangential_normal_proj * jump # Contact conditions num_frac_cells = np.sum([g.num_cells for g in g_frac]) jump_discr = coloumb_ad.displacement * jump_rotate * u_mortar tmp = np.ones(num_frac_cells * self._Nd) tmp[self._Nd - 1 :: self._Nd] = 0 exclude_normal = pp.ad.Matrix( sps.dia_matrix((tmp, 0), shape=(tmp.size, tmp.size)) ) # Rhs of contact conditions rhs = ( coloumb_ad.rhs + exclude_normal * coloumb_ad.displacement * jump_rotate * u_mortar_prev ) contact_conditions = coloumb_ad.traction * contact_force + jump_discr - rhs contact_eq = pp.ad.Expression( contact_conditions, dof_manager, "contact", grid_order=g_frac ) # Force balance mat = None for _, d in gb.edges(): mg: pp.MortarGrid = d["mortar_grid"] if mg.dim < self._Nd - 1: continue faces_on_fracture_surface = mg.primary_to_mortar_int().tocsr().indices m = pp.grid_utils.switch_sign_if_inwards_normal( g_primary, self._Nd, faces_on_fracture_surface ) if mat is None: mat = m else: mat += m sign_switcher = pp.ad.Matrix(mat) # Contact from primary grid and mortar displacements (via primary grid) contact_from_primary_mortar = ( mortar_proj_vector.primary_to_mortar_int * subdomain_proj_vector.face_prolongation(g_primary) * sign_switcher * stress ) contact_from_secondary = ( mortar_proj_vector.sign_of_mortar_sides * mortar_proj_vector.secondary_to_mortar_int * subdomain_proj_vector.cell_prolongation(g_frac) * tangential_normal_proj.transpose() * contact_force ) if self.subtract_fracture_pressure: # This gives an error because of -= mat = [] for e in edge_list_highest: mg = gb.edge_props(e, "mortar_grid") faces_on_fracture_surface = ( mg.primary_to_mortar_int().tocsr().indices ) sgn, _ = g_primary.signs_and_cells_of_boundary_faces( faces_on_fracture_surface ) fracture_normals = g_primary.face_normals[ : self._Nd, faces_on_fracture_surface ] outwards_fracture_normals = sgn * fracture_normals data = outwards_fracture_normals.ravel("F") row = np.arange(g_primary.dim * mg.num_cells) col = np.tile(np.arange(mg.num_cells), (g_primary.dim, 1)).ravel( "F" ) n_dot_I = sps.csc_matrix((data, (row, col))) # i) The scalar contribution to the contact stress is mapped to # the mortar grid and multiplied by n \dot I, with n being the # outwards normals on the two sides. # Note that by using different normals for the two sides, we do not need to # adjust the secondary pressure with the corresponding signs by applying # sign_of_mortar_sides as done in PrimalContactCoupling. # iii) The contribution should be subtracted so that we balance the primary # forces by # T_contact - n dot I p, # hence the minus. mat.append(n_dot_I * mg.secondary_to_mortar_int(nd=1)) # May need to do this as for tangential projections, additive that is normal_matrix = pp.ad.Matrix(sps.block_diag(mat)) p_frac = subdomain_proj_scalar.cell_restriction(g_frac) * p contact_from_secondary2 = normal_matrix * p_frac force_balance_eq = pp.ad.Expression( contact_from_primary_mortar + contact_from_secondary + contact_from_secondary2, dof_manager, "force_balance", grid_order=edge_list_highest, ) bc_val_scalar = pp.ad.BoundaryCondition( self.scalar_parameter_key, grid_list ) div_scalar = pp.ad.Divergence(grids=grid_list) dt = self.time_step # FIXME: Need bc for div_u term, including previous time step accumulation_primary = ( div_u_ad.div_u * (u - u_prev) + stab_biot_ad.stabilization * subdomain_proj_scalar.cell_restriction(g_primary) * (p - p_prev) + div_u_ad.bound_div_u * subdomain_proj_vector.face_restriction(g_primary) * mortar_proj_vector.mortar_to_primary_int * (u_mortar - u_mortar_prev) ) # Accumulation term on the fractures. frac_vol = np.hstack([g.cell_volumes for g in g_frac]) vol_mat = pp.ad.Matrix( sps.dia_matrix((frac_vol, 0), shape=(num_frac_cells, num_frac_cells)) ) accumulation_fracs = ( vol_mat * normal_proj * jump * (u_mortar - u_mortar_prev) ) accumulation_all = mass_ad.mass * (p - p_prev) flux = ( mpfa_ad.flux * p + mpfa_ad.bound_flux * bc_val_scalar + mpfa_ad.bound_flux * mortar_proj_scalar.mortar_to_primary_int * mortar_flux ) flow_md = ( dt * ( # Time scaling of flux terms, both inter-dimensional and from # the higher dimension div_scalar * flux - mortar_proj_scalar.mortar_to_secondary_int * mortar_flux ) + accumulation_all + subdomain_proj_scalar.cell_prolongation(g_primary) * accumulation_primary + subdomain_proj_scalar.cell_prolongation(g_frac) * accumulation_fracs ) interface_flow_eq = robin_ad.mortar_scaling * ( mortar_proj_scalar.primary_to_mortar_avg * mpfa_ad.bound_pressure_cell * p + mortar_proj_scalar.primary_to_mortar_avg * mpfa_ad.bound_pressure_face * ( mortar_proj_scalar.mortar_to_primary_int * mortar_flux + bc_val_scalar ) - mortar_proj_scalar.secondary_to_mortar_avg * p + robin_ad.mortar_discr * mortar_flux ) flow_eq = pp.ad.Expression( flow_md, dof_manager, "flow on nodes", grid_order=grid_list ) interface_eq = pp.ad.Expression( interface_flow_eq, dof_manager, "flow on interface", grid_order=edge_list, ) eq_manager.equations += [ momentum_eq, contact_eq, force_balance_eq, flow_eq, interface_eq, ] self._eq_manager = eq_manager
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_flow(gb, node_discretization, source_discretization, folder, is_FV, discr_3d=None, discr_2d=None): if discr_3d is None: discr_3d = node_discretization if discr_2d is None: discr_2d = node_discretization kw_t = "transport" # For storing fluxes grid_variable = "pressure" mortar_variable = "mortar_flux" # Identifier of the discretization operator on each grid diffusion_term = "diffusion" # Identifier of the discretization operator between grids coupling_operator_keyword = "coupling_operator" edge_discretization_3d = pp.RobinCoupling("flow", discr_3d, discr_2d) edge_discretization_2d = pp.RobinCoupling("flow", discr_2d, node_discretization) edge_discretization = pp.RobinCoupling("flow", node_discretization, node_discretization) # Loop over the nodes in the GridBucket, define primary variables and discretization schemes for g, d in gb: # Assign primary variables on this grid. It has one degree of freedom per cell. if g.dim == 3 and isinstance(discr_3d, pp.MVEM): d[pp.PRIMARY_VARIABLES] = {grid_variable: {"cells": 1, "faces": 1}} elif g.dim == 2 and isinstance(discr_2d, pp.MVEM): d[pp.PRIMARY_VARIABLES] = {grid_variable: {"cells": 1, "faces": 1}} else: d[pp.PRIMARY_VARIABLES] = { grid_variable: { "cells": 1, "faces": int(not is_FV) } } # Assign discretization operator for the variable. if g.dim == 3: d[pp.DISCRETIZATION] = { grid_variable: { diffusion_term: discr_3d, } } elif g.dim == 2: d[pp.DISCRETIZATION] = { grid_variable: { diffusion_term: discr_2d, } } else: d[pp.DISCRETIZATION] = { grid_variable: { diffusion_term: node_discretization, } } # Loop over the edges in the GridBucket, define primary variables and discretizations for e, d in gb.edges(): g1, g2 = gb.nodes_of_edge(e) # The mortar variable has one degree of freedom per cell in the mortar grid d[pp.PRIMARY_VARIABLES] = {mortar_variable: {"cells": 1}} # The coupling discretization links an edge discretization with variables # and discretization operators on each neighboring grid if g2.dim == 3: d[pp.COUPLING_DISCRETIZATION] = { coupling_operator_keyword: { g1: (grid_variable, diffusion_term), g2: (grid_variable, diffusion_term), e: (mortar_variable, edge_discretization_3d), } } elif g2.dim == 2: d[pp.COUPLING_DISCRETIZATION] = { coupling_operator_keyword: { g1: (grid_variable, diffusion_term), g2: (grid_variable, diffusion_term), e: (mortar_variable, edge_discretization_2d), } } else: d[pp.COUPLING_DISCRETIZATION] = { coupling_operator_keyword: { g1: (grid_variable, diffusion_term), g2: (grid_variable, diffusion_term), e: (mortar_variable, edge_discretization), } } logger.info("Assembly and discretization using the discretizer " + str(node_discretization)) tic = time.time() assembler = pp.Assembler() # Assemble the linear system, using the information stored in the GridBucket A, b, block_dof, full_dof = assembler.assemble_matrix_rhs( gb, active_variables=[grid_variable, mortar_variable]) logger.info("Done. Elapsed time: " + str(time.time() - tic)) logger.info("Linear solver") tic = time.time() x = sps.linalg.spsolve(A, b) logger.info("Done. Elapsed time " + str(time.time() - tic)) # if is_FV: assembler.distribute_variable(gb, x, block_dof, full_dof) if is_FV: if isinstance(discr_3d, pp.MVEM): g = gb.grids_of_dimension(3)[0] d = gb.node_props(g) discr_scheme = d[pp.DISCRETIZATION][grid_variable][diffusion_term] d["pressure"] = discr_scheme.extract_pressure( g, d[grid_variable], d) if isinstance(discr_2d, pp.MVEM): for g in gb.grids_of_dimension(2): d = gb.node_props(g) discr_scheme = d[ pp.DISCRETIZATION][grid_variable][diffusion_term] d["pressure"] = discr_scheme.extract_pressure( g, d[grid_variable], d) # pp.fvutils.compute_darcy_flux(gb, lam_name=mortar_variable, keyword_store=kw_t) else: for g, d in gb: discr_scheme = d[pp.DISCRETIZATION][grid_variable][diffusion_term] #d[pp.PARAMETERS][kw_t]["darcy_flux"] = discr_scheme.extract_flux( # g, d[grid_variable], d #) # Note the order: we overwrite d["pressure"] so this has to be done after # extracting the flux d["pressure"] = discr_scheme.extract_pressure( g, d[grid_variable], d) for e, d in gb.edges(): # g1, g2 = gb.nodes_of_edge(e) d[pp.PARAMETERS][kw_t]["darcy_flux"] = d[mortar_variable].copy() export_flow(gb, folder) # sps_io.mmwrite(folder + "/matrix.mtx", A) return A, b, block_dof, full_dof
def assign_discretisations(self): """ Assign discretizations to the nodes and edges of the grid bucket. Note the attribute subtract_fracture_pressure: Indicates whether or not to subtract the fracture pressure contribution for the contact traction. This should not be done if the scalar variable is temperature. """ # Shorthand key_s, key_m = self.scalar_parameter_key, self.mechanics_parameter_key var_s, var_d = self.scalar_variable, self.displacement_variable # Define discretization # For the Nd domain we solve linear elasticity with mpsa. mpsa = pp.Mpsa(key_m) empty_discr = pp.VoidDiscretization(key_m, ndof_cell=self.Nd) # Scalar discretizations (all dimensions) diff_disc_s = IE_discretizations.ImplicitMpfa(key_s) mass_disc_s = IE_discretizations.ImplicitMassMatrix(key_s, var_s) source_disc_s = pp.ScalarSource(key_s) # Coupling discretizations # All dimensions div_u_disc = pp.DivU( key_m, key_s, variable=var_d, mortar_variable=self.mortar_displacement_variable, ) # Nd grad_p_disc = pp.GradP(key_m) stabilization_disc_s = pp.BiotStabilization(key_s, var_s) # Assign node discretizations for g, d in self.gb: if g.dim == self.Nd: d[pp.DISCRETIZATION] = { var_d: { "mpsa": mpsa }, var_s: { "diffusion": diff_disc_s, "mass": mass_disc_s, "stabilization": stabilization_disc_s, "source": source_disc_s, }, var_d + "_" + var_s: { "grad_p": grad_p_disc }, var_s + "_" + var_d: { "div_u": div_u_disc }, } elif g.dim == self.Nd - 1: d[pp.DISCRETIZATION] = { self.contact_traction_variable: { "empty": empty_discr }, var_s: { "diffusion": diff_disc_s, "mass": mass_disc_s, "source": source_disc_s, }, } # Define edge discretizations for the mortar grid contact_law = pp.ColoumbContact(self.mechanics_parameter_key, self.Nd) contact_discr = pp.PrimalContactCoupling(self.mechanics_parameter_key, mpsa, contact_law) # Account for the mortar displacements effect on scalar balance in the matrix, # as an internal boundary contribution, fracture, aperture changes appear as a # source contribution. div_u_coupling = pp.DivUCoupling(self.displacement_variable, div_u_disc, div_u_disc) # Account for the pressure contributions to the force balance on the fracture # (see contact_discr). # This discretization needs the keyword used to store the grad p discretization: grad_p_key = key_m matrix_scalar_to_force_balance = pp.MatrixScalarToForceBalance( grad_p_key, mass_disc_s, mass_disc_s) if self.subtract_fracture_pressure: fracture_scalar_to_force_balance = pp.FractureScalarToForceBalance( mass_disc_s, mass_disc_s) for e, d in self.gb.edges(): g_l, g_h = self.gb.nodes_of_edge(e) if g_h.dim == self.Nd: d[pp.COUPLING_DISCRETIZATION] = { self.friction_coupling_term: { g_h: (var_d, "mpsa"), g_l: (self.contact_traction_variable, "empty"), (g_h, g_l): (self.mortar_displacement_variable, contact_discr), }, self.scalar_coupling_term: { g_h: (var_s, "diffusion"), g_l: (var_s, "diffusion"), e: ( self.mortar_scalar_variable, pp.RobinCoupling(key_s, diff_disc_s), ), }, "div_u_coupling": { g_h: ( var_s, "mass", ), # This is really the div_u, but this is not implemented g_l: (var_s, "mass"), e: (self.mortar_displacement_variable, div_u_coupling), }, "matrix_scalar_to_force_balance": { g_h: (var_s, "mass"), g_l: (var_s, "mass"), e: ( self.mortar_displacement_variable, matrix_scalar_to_force_balance, ), }, } if self.subtract_fracture_pressure: d[pp.COUPLING_DISCRETIZATION].update({ "matrix_scalar_to_force_balance": { g_h: (var_s, "mass"), g_l: (var_s, "mass"), e: ( self.mortar_displacement_variable, fracture_scalar_to_force_balance, ), } }) else: raise ValueError( "assign_discretizations assumes no fracture intersections." )