def discretize(self) -> None: """ Discretize all terms """ if not self.assembler: self.assembler = pp.Assembler(self.gb) self.assembler.discretize()
def discretize(self) -> None: """ Discretize all terms """ if not self.assembler: self.assembler = pp.Assembler(self.gb) g_max = self.gb.grids_of_dimension(self.Nd)[0] logger.info("Discretize") # Discretization is a bit cumbersome, as the Biot discetization removes the # one-to-one correspondence between discretization objects and blocks in the matrix. # First, Discretize with the biot class self._discretize_biot() # Next, discretize term on the matrix grid not covered by the Biot discretization, # i.e. the source term # Here, we also discretize the edge terms in the entire gb self.assembler.discretize(grid=g_max, term_filter=["source"]) # Finally, discretize terms on the lower-dimensional grids. This can be done # in the traditional way, as there is no Biot discretization here. for g, _ in self.gb: if g.dim < self.Nd: # No need to discretize edges here, this was done above. self.assembler.discretize(grid=g, edges=False)
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 matrix_rhs(self): # empty the matrices for g, d in self.gb: d[pp.DISCRETIZATION_MATRICES] = {self.model: {}} for e, d in self.gb.edges(): d[pp.DISCRETIZATION_MATRICES] = {self.model: {}} # solution of the darcy problem self.assembler = pp.Assembler( self.gb, active_variables=[self.variable, self.mortar]) logger.info("Assemble the flow problem") block_A, block_b = self.assembler.assemble_matrix_rhs( add_matrices=False) # unpack the matrices just computed coupling_name = self.coupling_name + ( "_" + self.mortar + "_" + self.variable + "_" + self.variable) discr_name = self.discr_name + "_" + self.variable mass_name = self.mass_name + "_" + self.variable source_name = self.source_name + "_" + self.variable # need a sign for the convention of the conservation equation M = -block_A[mass_name] A = M + block_A[discr_name] + block_A[coupling_name] b = block_b[discr_name] + block_b[coupling_name] + block_b[source_name] return A, M, b, self.assembler.block_dof, self.assembler.full_dof
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 discretize_flow(self): """ Discretize the flow operators """ gb = self.data.gb flow_kw = self.data.flow_keyword elliptic_disc(gb, flow_kw) # Assemble matrices assembler = pp.Assembler(gb) # Fluid flow. Darcy + mass conservation flux = assembler.assemble_operator(flow_kw, "flux") bound_flux = assembler.assemble_operator(flow_kw, "bound_flux") trace_cell = assembler.assemble_operator(flow_kw, "bound_pressure_cell") trace_face = assembler.assemble_operator(flow_kw, "bound_pressure_face") div = assembler.assemble_operator(flow_kw, "div") # Assemble discrete parameters and geometric values bc_val = assembler.assemble_parameter(flow_kw, "bc_values") kn = [ d[pp.PARAMETERS][flow_kw]["normal_diffusivity"] for _, d in gb.edges() ] kn = np.hstack(kn) self.mat[flow_kw] = { "flux": flux, "bound_flux": bound_flux, "trace_cell": trace_cell, "trace_face": trace_face, "bc_values": bc_val, "kn": kn, }
def main(): # example in 2d file_name = "network.csv" write_network(file_name) # load the network and split it, we assume 2d domain = {"xmin": 0, "xmax": 1, "ymin": 0, "ymax": 1} global_network = pp.fracture_importer.network_2d_from_csv(file_name, domain=domain) global_network = global_network.split_intersections() # select a subsample of the fractures macro_network, micro_network = split_network(global_network, Criterion.every) # NOTE: the explicit network is empty so far # create the macroscopic grid mesh_size = 2 mesh_kwargs = { "mesh_size_frac": mesh_size, "mesh_size_min": mesh_size / 20 } gb = macro_network.mesh(mesh_kwargs) # plot the suff pp.plot_fractures(micro_network.pts, micro_network.edges, micro_network.domain) pp.plot_grid(gb, info="c", alpha=0) # construct the solver tpfa_dfm = Tpfa_DFM() # Set data for the upscaling problem g = gb.grids_of_dimension(gb.dim_max())[0] d = gb.node_props(g) # store the # EK: We probably need to initialize some of the nested dictionaries d[pp.PARAMETERS][tpfa_dfm.keyword][ tpfa_dfm.network_keyword] = micro_network # set parameters and discretization variables # EK: This must be done in this run-script - it is not the responsibility of the # discretization. We may want to construct a model for this. # tpfa_dfm.set_parameters(gb) # tpfa_dfm.set_variables_discretizations(gb) # discretize with the assembler assembler = pp.Assembler(gb) assembler.discretize() A, b = assembler.assemble_matrix_rhs() # Solve and distribute x = spsolve(A, b) assembler.distribute_variable(x)
def _discretize(self) -> None: """Discretize all terms""" if not hasattr(self, "assembler"): self.assembler = pp.Assembler(self.gb) tic = time.time() logger.info("Discretize") self.assembler.discretize() logger.info("Done. Elapsed time {}".format(time.time() - tic))
def discretize(self): """ Discretize all terms """ self.assembler = pp.Assembler(self.gb) tic = time.time() logger.info("Discretize") self.assembler.discretize() logger.info("Done. Elapsed time {}".format(time.time() - tic))
def discretize(self): """ Discretize the mechanics stress term """ if not hasattr(self, "assembler"): self.assembler = pp.Assembler( self.gb, active_variables=[self.displacement_variable]) g_max = self.gb.grids_of_dimension(self.Nd)[0] self.assembler.discretize(grid=g_max, variable_filter=self.displacement_variable)
def discretize_transport(self): """ Discretize the transport operators """ gb = self.problem.gb transport_kw = self.problem.transport_keyword elliptic_disc(gb, transport_kw, self._use_mpfa) upwind_disc(gb, transport_kw) # Assemble global matrices assembler = pp.Assembler(gb) # Transport. Upwind + mass conservation diff = assembler.assemble_operator(transport_kw, "flux") bound_diff = assembler.assemble_operator(transport_kw, "bound_flux") trace_cell = assembler.assemble_operator(transport_kw, "bound_pressure_cell") trace_face = assembler.assemble_operator(transport_kw, "bound_pressure_face") pos_cells = assembler.assemble_operator(transport_kw, "pos_cells") neg_cells = assembler.assemble_operator(transport_kw, "neg_cells") # Finite volume divergence operator div = assembler.assemble_operator(transport_kw, "div") # Assemble discrete parameters and geometric values bc_val = assembler.assemble_parameter(transport_kw, "bc_values") bc_sgn = assembler.assemble_parameter(transport_kw, "bc_sgn") frac_bc = assembler.assemble_parameter(transport_kw, "frac_bc") mass_weight = assembler.assemble_parameter(transport_kw, "mass_weight") dn = [ d[pp.PARAMETERS][transport_kw]["normal_diffusivity"] for _, d in gb.edges() ] if len(dn) > 0: dn = np.hstack(dn) else: dn = np.array([], dtype=float) # Store discretizations self.mat[transport_kw] = { "flux": diff, "bound_flux": bound_diff, "trace_cell": trace_cell, "trace_face": trace_face, "bc_values": bc_val, "pos_cells": pos_cells, "neg_cells": neg_cells, "bc_sgn": bc_sgn, "frac_bc": frac_bc, "dn": dn, "mass_weight": mass_weight * gb.cell_volumes(), }
def discretize(self): """ Discretize the flow terms only""" if not hasattr(self, "assembler"): self.assembler = pp.Assembler( self.gb, active_variables=[ self.scalar_variable, self.mortar_scalar_variable ], ) self.assembler.discretize()
def _discretize(self) -> None: """Discretize all terms""" if not hasattr(self, "dof_manager"): self.dof_manager = pp.DofManager(self.gb) self.assembler = pp.Assembler(self.gb, self.dof_manager) tic = time.time() logger.info("Discretize") if self._use_ad: self._eq_manager.discretize(self.gb) else: self.assembler.discretize() logger.info("Done. Elapsed time {}".format(time.time() - tic))
def set_discr(self): for _, d in self.gb: d[pp.PRIMARY_VARIABLES].update({self.variable: {"cells": 1}}) for _, d in self.gb.edges(): d[pp.PRIMARY_VARIABLES].update({self.mortar_dummy_1: {"cells": 1}, self.mortar_dummy_2: {"cells": 1}}) # assembler variables = [self.variable, self.mortar_dummy_1, self.mortar_dummy_2] self.assembler = pp.Assembler(self.gb, active_variables=variables)
def _discretize(self) -> None: """Discretize all terms""" if not hasattr(self, "dof_manager"): self.dof_manager = pp.DofManager(self.gb) if not hasattr(self, "assembler"): self.assembler = pp.Assembler(self.gb, self.dof_manager) g_max = self.gb.grids_of_dimension(self._Nd)[0] tic = time.time() logger.info("Discretize") if self._use_ad: self._eq_manager.discretize(self.gb) else: # Discretization is a bit cumbersome, as the Biot discetization removes the # one-to-one correspondence between discretization objects and blocks in # the matrix. # First, Discretize with the biot class self._discretize_biot() # Next, discretize term on the matrix grid not covered by the Biot discretization, # i.e. the diffusion, mass and source terms filt = pp.assembler_filters.ListFilter( grid_list=[g_max], term_list=["source", "mass", "diffusion"] ) self.assembler.discretize(filt=filt) # Build a list of all edges, and all couplings edge_list: List[ Union[ Tuple[pp.Grid, pp.Grid], Tuple[pp.Grid, pp.Grid, Tuple[pp.Grid, pp.Grid]], ] ] = [] for e, _ in self.gb.edges(): edge_list.append(e) edge_list.append((e[0], e[1], e)) if len(edge_list) > 0: filt = pp.assembler_filters.ListFilter(grid_list=edge_list) # type: ignore self.assembler.discretize(filt=filt) # Finally, discretize terms on the lower-dimensional grids. This can be done # in the traditional way, as there is no Biot discretization here. for dim in range(0, self._Nd): grid_list = self.gb.grids_of_dimension(dim) if len(grid_list) > 0: filt = pp.assembler_filters.ListFilter(grid_list=grid_list) self.assembler.discretize(filt=filt) logger.info("Done. Elapsed time {}".format(time.time() - tic))
def set_data(self, data, bc_flag): self.data = data self.bc_flag = bc_flag self.time_step = data["end_time"] / float(data["num_steps"]) self.all_time = np.arange(self.data["num_steps"]) * self.time_step # set the data for the nodes self._set_data_nodes() # set the data for the edges, if present if self.gb.num_graph_edges(): self._set_data_edges() # assembler variables = [self.variable, self.mortar] self.assembler = pp.Assembler(self.gb, active_variables=variables)
def set_data(self, data, bc_flag): """ Set the data of the problem """ self.data = data self.bc_flag = bc_flag # set the data for the nodes self._set_data_nodes() # set the data for the edges, if present if self.gb.num_graph_edges(): self._set_data_edges() # assembler variables = [self.variable, self.mortar] self.assembler = pp.Assembler(self.gb, active_variables=variables)
def set_discr(self): # set the discretization for the grids for _, d in self.gb: d[pp.PRIMARY_VARIABLES].update({self.variable: {"cells": 1}}) diff = self.diff(self.diff_name) adv = self.adv(self.adv_name) mass = self.mass(self.mass_name) source = self.source(self.source_name) d[pp.DISCRETIZATION].update({self.variable: {self.diff_name: diff, self.adv_name: adv, self.mass_name: mass, self.source_name: source}}) d[pp.DISCRETIZATION_MATRICES].update({self.diff_name: {}, self.adv_name: {}, self.mass_name: {}, self.source_name: {}}) # define the interface terms to couple the grids for e, d in self.gb.edges(): g_slave, g_master = self.gb.nodes_of_edge(e) d[pp.PRIMARY_VARIABLES].update({self.mortar_diff: {"cells": 1}, self.mortar_adv: {"cells": 1}}) coupling_diff = self.coupling_diff(self.diff_name, self.diff(self.diff_name)) d[pp.COUPLING_DISCRETIZATION].update({ self.coupling_diff_name: { g_slave: (self.variable, self.diff_name), g_master: (self.variable, self.diff_name), e: (self.mortar_diff, coupling_diff), } }) coupling_adv = self.coupling_adv(self.adv_name) d[pp.COUPLING_DISCRETIZATION].update({ self.coupling_adv_name: { g_slave: (self.variable, self.adv_name), g_master: (self.variable, self.adv_name), e: (self.mortar_adv, coupling_adv), } }) d[pp.DISCRETIZATION_MATRICES].update({self.diff_name: {}, self.adv_name: {}, self.mass_name: {}, self.source_name: {}}) # assembler variables = [self.variable, self.mortar_diff, self.mortar_adv] self.assembler = pp.Assembler(self.gb, active_variables=variables)
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 discretize(self) -> None: """ Discretize all terms """ if not hasattr(self, "assembler"): self.assembler = pp.Assembler(self.gb) tic = time.time() logger.info("Discretize") # Discretization is a bit cumbersome, as the Biot discetization removes the # one-to-one correspondence between discretization objects and blocks in the matrix. # First, Discretize with the biot class self.discretize_biot() self.copy_biot_discretizations() # Next, discretize term on the matrix grid not covered by the Biot discretization, # i.e. the source term pressure_terms = ["source"] self.assembler.discretize( grid=self._nd_grid(), term_filter=pressure_terms, variable_filter=self.scalar_variable, ) # Then the temperature discretizations temperature_terms = [ "source", "diffusion", "mass", self.advection_term ] self.assembler.discretize( grid=self._nd_grid(), term_filter=temperature_terms, variable_filter=self.temperature_variable, ) coupling_terms = [self.s2t_coupling_term, self.t2s_coupling_term] self.assembler.discretize( grid=self._nd_grid(), term_filter=coupling_terms, variable_filter=[self.temperature_variable, self.scalar_variable], ) # Finally, discretize terms on the lower-dimensional grids. This can be done # in the traditional way, as there is no Biot discretization here. for g, _ in self.gb: if g.dim < self.Nd: self.assembler.discretize(grid=g) logger.info("Done. Elapsed time {}".format(time.time() - tic))
def matrix_rhs(self): # set the discretization for the grids for g, d in self.gb: discr = self.discr(self.model) source = self.source(self.model) d[pp.PRIMARY_VARIABLES] = {self.variable: {"cells": 1, "faces": 1}} d[pp.DISCRETIZATION] = { self.variable: { self.discr_name: discr, self.source_name: source } } d[pp.DISCRETIZATION_MATRICES] = {self.model: {}} # define the interface terms to couple the grids for e, d in self.gb.edges(): g_slave, g_master = self.gb.nodes_of_edge(e) # retrive the discretization of the master and slave grids discr_master = self.gb.node_props( g_master, pp.DISCRETIZATION)[self.variable][self.discr_name] discr_slave = self.gb.node_props( g_slave, pp.DISCRETIZATION)[self.variable][self.discr_name] coupling = self.coupling(self.model, discr_master, discr_slave) d[pp.PRIMARY_VARIABLES] = {self.mortar: {"cells": 1}} d[pp.COUPLING_DISCRETIZATION] = { self.coupling_name: { g_slave: (self.variable, self.discr_name), g_master: (self.variable, self.discr_name), e: (self.mortar, coupling), } } d[pp.DISCRETIZATION_MATRICES] = {self.model: {}} # assembler self.assembler = pp.Assembler(self.gb) self.assembler.discretize() return self.assembler.assemble_matrix_rhs()
def set_discr(self): # set the discretization for the grids for _, d in self.gb: d[pp.PRIMARY_VARIABLES].update( {self.variable: { "cells": 1, "faces": 1 }}) discr = self.discr(self.model) source = self.source(self.model) d[pp.DISCRETIZATION].update({ self.variable: { self.discr_name: discr, self.source_name: source } }) d[pp.DISCRETIZATION_MATRICES].update({self.model: {}}) # define the interface terms to couple the grids for e, d in self.gb.edges(): g_slave, g_master = self.gb.nodes_of_edge(e) d[pp.PRIMARY_VARIABLES].update({self.mortar: {"cells": 1}}) coupling = self.coupling(self.model, self.discr(self.model)) d[pp.COUPLING_DISCRETIZATION].update({ self.coupling_name: { g_slave: (self.variable, self.discr_name), g_master: (self.variable, self.discr_name), e: (self.mortar, coupling), } }) d[pp.DISCRETIZATION_MATRICES].update({self.model: {}}) # assembler variables = [self.variable, self.mortar] self.assembler = pp.Assembler(self.gb, active_variables=variables)
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 run_mechanics(setup): """ Function for solving linear elasticity with a non-linear Coulomb contact. There are some assumtions on the variable and discretization names given to the grid bucket: 'u': The displacement variable 'lam': The mortar variable 'mpsa': The mpsa discretization In addition to the standard parameters for mpsa we also require the following under the contact mechanics keyword (returned from setup.set_parameters): 'friction_coeff' : The coefficient of friction 'c' : The numerical parameter in the non-linear complementary function. Arguments: setup: A setup class with methods: set_parameters(g, data_node, mg, data_edge): assigns data to grid bucket. Returns the keyword for the linear elastic parameters and a keyword for the contact mechanics parameters. create_grid(): Create and return the grid bucket initial_condition(): Returns initial guess for 'u' and 'lam'. and attributes: out_name(): returns a string. The data from the simulation will be written to the file 'res_data/' + setup.out_name and the vtk files to 'res_plot/' + setup.out_name """ gb = setup.create_grid() # Extract the grids we use dim = gb.dim_max() g = gb.grids_of_dimension(dim)[0] data_node = gb.node_props(g) data_edge = gb.edge_props((g, g)) mg = data_edge['mortar_grid'] # set parameters key, key_m = setup.set_parameters(g, data_node, mg, data_edge) # Get shortcut to some of the parameters F = data_edge[pp.PARAMETERS][key_m]['friction_coeff'] c_num = data_edge[pp.PARAMETERS][key_m]['c'] # Define rotations M_inv, nc = utils.normal_tangential_rotations(g, mg) # Set up assembler and discretize mpsa = data_node[pp.DISCRETIZATION]['u']['mpsa'] mpsa.discretize(g, data_node) assembler = pp.Assembler() # prepare for iteration u0, uc, Tc = setup.initial_condition(g, mg, nc) T_contact = [] u_contact = [] save_sliding = [] errors = [] counter_newton = 0 converged_newton = False max_newton = 15 while counter_newton <= max_newton and not converged_newton: print('Newton iteration number: ', counter_newton, '/', max_newton) counter_newton += 1 # Calculate numerical friction bound used in the contact condition bf = F * np.clip(np.sum(nc * (-Tc + c_num * uc), axis=0), 0, np.inf) # Find the robin weight mortar_weight, robin_weight, rhs = contact_coulomb( Tc, uc, F, bf, c_num, c_num, M_inv) data_edge[pp.PARAMETERS][key_m]['robin_weight'] = robin_weight data_edge[pp.PARAMETERS][key_m]['mortar_weight'] = mortar_weight data_edge[pp.PARAMETERS][key_m]['robin_rhs'] = rhs # Re-discretize and solve A, b, block_dof, full_dof = assembler.assemble_matrix_rhs(gb) if gb.num_cells() > 4000: sol = solvers.amg(gb, A, b) else: sol = sps.linalg.spsolve(A, b) # Split solution into displacement variable and mortar variable assembler.distribute_variable(gb, sol, block_dof, full_dof) u = data_node['u'].reshape((g.dim, -1), order='F') Tc = data_edge['lam'].reshape((g.dim, -1), order='F') # Reconstruct the displacement on the fractures uc = reconstruct_mortar_displacement(u, Tc, g, mg, data_node, data_edge, key, key_m) # Calculate the error if np.sum((u - u0)**2 * g.cell_volumes) / np.sum( u**2 * g.cell_volumes) < 1e-10: converged_newton = True print('error: ', np.sum((u - u0)**2) / np.sum(u**2)) errors.append(np.sum((u - u0)**2) / np.sum(u**2)) # Prepare for next iteration u0 = u T_contact.append(Tc) u_contact.append(uc) # Store vtk of solution: viz.export_nodal_values(g, mg, data_node, u, None, Tc, key, key_m, setup.out_name, "res_plot") if dim == 3: m_exp_name = setup.out_name + "_mortar_grid" viz.export_mortar_grid(g, mg, data_edge, uc, Tc, key, key_m, m_exp_name, "res_plot")
def run_biot(setup): """ Function for solving the time dependent Biot equations with a non-linear Coulomb contact condition on the fractures. There are some assumtions on the variable and discretization names given to the grid bucket: 'u': The displacement variable 'p': Fluid pressure variable 'lam': The mortar variable Furthermore, the parameter keyword from the elasticity is assumed the same as the parameter keyword from the contact condition. In addition to the standard parameters for Biot we also require the following under the mechanics keyword (returned from setup.set_parameters): 'friction_coeff' : The coefficient of friction 'c' : The numerical parameter in the non-linear complementary function. and for the parameters under the fluid flow keyword: 'time_step': time step of implicit Euler. Arguments: setup: A setup class with methods: set_parameters(g, data_node, mg, data_edge): assigns data to grid bucket. Returns the keyword for the linear elastic parameters and a keyword for the contact mechanics parameters. create_grid(): Create and return the grid bucket initial_condition(): Returns initial guess for 'u' and 'lam'. and attributes: out_name(): returns a string. The data from the simulation will be written to the file 'res_data/' + setup.out_name and the vtk files to 'res_plot/' + setup.out_name end_time: End time time of simulation. """ gb = setup.create_grid() # Extract the grids we use dim = gb.dim_max() g = gb.grids_of_dimension(dim)[0] data_node = gb.node_props(g) data_edge = gb.edge_props((g, g)) mg = data_edge['mortar_grid'] # set parameters key_m, key_f = setup.set_parameters(g, data_node, mg, data_edge) # Short hand for some parameters F = data_edge[pp.PARAMETERS][key_m]['friction_coeff'] c_num = data_edge[pp.PARAMETERS][key_m]['c'] dt = data_node[pp.PARAMETERS][key_f]["time_step"] # Define rotations M_inv, nc = utils.normal_tangential_rotations(g, mg) # Set up assembler and get initial condition assembler = pp.Assembler() u0 = data_node[pp.PARAMETERS][key_m]['state']['displacement'].reshape( (g.dim, -1), order='F') p0 = data_node[pp.PARAMETERS][key_f]['state'] Tc = data_edge[pp.PARAMETERS][key_m]['state'].reshape((g.dim, -1), order='F') # Reconstruct displacement jump on fractures uc = reconstruct_mortar_displacement(u0, Tc, g, mg, data_node, data_edge, key_m, key_f, pressure=p0) uc0 = uc.copy() # Define function for splitting the global solution vector def split_solution_vector(x, block_dof, full_dof): # full_dof contains the number of dofs per block. To get a global ordering, use global_dof = np.r_[0, np.cumsum(full_dof)] # split global variable block_u = block_dof[(g, "u")] block_p = block_dof[(g, "p")] block_lam = block_dof[((g, g), "lam_u")] # Get the global displacement and pressure dofs u_dof = np.arange(global_dof[block_u], global_dof[block_u + 1]) p_dof = np.arange(global_dof[block_p], global_dof[block_p + 1]) lam_dof = np.arange(global_dof[block_lam], global_dof[block_lam + 1]) # Plot pressure and displacements u = x[u_dof].reshape((g.dim, -1), order="F") p = x[p_dof] lam = x[lam_dof].reshape((g.dim, -1), order="F") return u, p, lam # prepare for time loop sol = None # inital guess for Newton solver. T_contact = [] u_contact = [] save_sliding = [] errors = [] exporter = pp.Exporter(g, setup.out_name, 'res_plot') t = 0.0 T = setup.end_time k = 0 times = [] newton_it = 0 while t < T: t += dt k += 1 print('Time step: ', k, '/', int(np.ceil(T / dt))) times.append(t) # Prepare for Newton counter_newton = 0 converged_newton = False max_newton = 12 newton_errors = [] while counter_newton <= max_newton and not converged_newton: print('Newton iteration number: ', counter_newton, '/', max_newton) counter_newton += 1 bf = F * np.clip(np.sum(nc * (-Tc + c_num * uc), axis=0), 0, np.inf) ducdt = (uc - uc0) / dt # Find the robin weight mortar_weight, robin_weight, rhs = contact_coulomb( Tc, ducdt, F, bf, c_num, c_num, M_inv) rhs = rhs.reshape((g.dim, -1), order='F') for i in range(mg.num_cells): robin_weight[i] = robin_weight[i] / dt rhs[:, i] = rhs[:, i] + robin_weight[i].dot(uc0[:, i]) data_edge[pp.PARAMETERS][key_m]['robin_weight'] = robin_weight data_edge[pp.PARAMETERS][key_m]['mortar_weight'] = mortar_weight data_edge[pp.PARAMETERS][key_m]['robin_rhs'] = rhs.ravel('F') # Re-discretize and solve A, b, block_dof, full_dof = assembler.assemble_matrix_rhs(gb) print('max A: ', np.max(np.abs(A))) print('max A sum: ', np.max(np.sum(np.abs(A), axis=1))) print('min A sum: ', np.min(np.sum(np.abs(A), axis=1))) if A.shape[0] > 10000: sol, msg, err = solvers.fixed_stress(gb, A, b, block_dof, full_dof, sol) if msg != 0: # did not converge. print('Iterative solver failed.') else: sol = sps.linalg.spsolve(A, b) # Split solution in the different variables u, p, Tc = split_solution_vector(sol, block_dof, full_dof) # Reconstruct displacement jump on mortar boundary uc = reconstruct_mortar_displacement(u, Tc, g, mg, data_node, data_edge, key_m, key_f, pressure=p) # Calculate the error if np.sum((u - u0)**2 * g.cell_volumes) / np.sum( u**2 * g.cell_volumes) < 1e-10: converged_newton = True print('error: ', np.sum((u - u0)**2) / np.sum(u**2)) newton_errors.append(np.sum((u - u0)**2) / np.sum(u**2)) # Prepare for nect newton iteration u0 = u newton_it += 1 errors.append(newton_errors) # Prepare for next time step uc0 = uc.copy() T_contact.append(Tc) u_contact.append(uc) mech_bc = data_node[pp.PARAMETERS][key_m]['bc_values'].copy() data_node[pp.PARAMETERS][key_m]['bc_values'] = setup.bc_values( g, t + dt, key_m) data_node[pp.PARAMETERS][key_f]['bc_values'] = setup.bc_values( g, t + dt, key_f) data_node[pp.PARAMETERS][key_m]["state"]["displacement"] = u.ravel( 'F').copy() data_node[pp.PARAMETERS][key_m]["state"]["bc_values"] = mech_bc data_node[pp.PARAMETERS][key_f]["state"] = p.copy() data_edge[pp.PARAMETERS][key_m]["state"] = Tc.ravel('F').copy() if g.dim == 2: u_exp = np.vstack((u, np.zeros(u.shape[1]))) elif g.dim == 3: u_exp = u.copy() m_exp_name = setup.out_name + "_mortar_grid" viz.export_mortar_grid(g, mg, data_edge, uc, Tc, key_m, key_m, m_exp_name, "res_plot", time_step=k) exporter.write_vtk({"u": u_exp, 'p': p}, time_step=k) exporter.write_pvd(np.array(times))
def _discretize(self) -> None: """Discretize all terms""" if not hasattr(self, "dof_manager"): self.dof_manager = pp.DofManager(self.gb) if not hasattr(self, "assembler"): self.assembler = pp.Assembler(self.gb, self.dof_manager) tic = time.time() logger.info("Discretize") # Discretization is a bit cumbersome, as the Biot discetization removes the # one-to-one correspondence between discretization objects and blocks in the matrix. # First, Discretize with the biot class self._discretize_biot() self._copy_biot_discretizations() # Next, discretize term on the matrix grid not covered by the Biot discretization, # i.e. the source, diffusion and mass terms filt = pp.assembler_filters.ListFilter( grid_list=[self._nd_grid()], variable_list=[self.scalar_variable], term_list=["source", "diffusion", "mass"], ) self.assembler.discretize(filt=filt) # Then the temperature discretizations temperature_terms = ["source", "diffusion", "mass", self.advection_term] filt = pp.assembler_filters.ListFilter( grid_list=[self._nd_grid()], variable_list=[self.temperature_variable], term_list=temperature_terms, ) self.assembler.discretize(filt=filt) # Coupling terms coupling_terms = [self.s2t_coupling_term, self.t2s_coupling_term] filt = pp.assembler_filters.ListFilter( grid_list=[self._nd_grid()], variable_list=[self.temperature_variable, self.scalar_variable], term_list=coupling_terms, ) self.assembler.discretize(filt=filt) # Build a list of all edges, and all couplings edge_list: List[ Union[ Tuple[pp.Grid, pp.Grid], Tuple[pp.Grid, pp.Grid, Tuple[pp.Grid, pp.Grid]], ] ] = [] for e, _ in self.gb.edges(): edge_list.append(e) edge_list.append((e[0], e[1], e)) if len(edge_list) > 0: filt = pp.assembler_filters.ListFilter(grid_list=edge_list) # type: ignore self.assembler.discretize(filt=filt) # Finally, discretize terms on the lower-dimensional grids. This can be done # in the traditional way, as there is no Biot discretization here. for dim in range(0, self._Nd): grid_list = self.gb.grids_of_dimension(dim) if len(grid_list) > 0: filt = pp.assembler_filters.ListFilter(grid_list=grid_list) self.assembler.discretize(filt=filt) logger.info("Done. Elapsed time {}".format(time.time() - tic))
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)