def assign_discretizations(self): """ Assign discretizations to the nodes and edges of the grid bucket. """ # For the Nd domain we solve linear elasticity with mpsa. Nd = self.Nd gb = self.gb mpsa = pp.Mpsa(self.mechanics_parameter_key) # We need a void discretization for the contact traction variable defined on # the fractures. empty_discr = pp.VoidDiscretization(self.mechanics_parameter_key, ndof_cell=Nd) for g, d in gb: if g.dim == Nd: d[pp.DISCRETIZATION] = {self.displacement_variable: {"mpsa": mpsa}} elif g.dim == Nd - 1: d[pp.DISCRETIZATION] = { self.contact_traction_variable: {"empty": empty_discr} } # Define the contact condition on the mortar grid coloumb = pp.ColoumbContact(self.mechanics_parameter_key, Nd, mpsa) contact = pp.PrimalContactCoupling(self.mechanics_parameter_key, mpsa, coloumb) for e, d in gb.edges(): g_l, g_h = gb.nodes_of_edge(e) if g_h.dim == Nd: d[pp.COUPLING_DISCRETIZATION] = { self.friction_coupling_term: { g_h: (self.displacement_variable, "mpsa"), g_l: (self.contact_traction_variable, "empty"), (g_h, g_l): (self.mortar_displacement_variable, contact), } }
def assign_mechanics_discretizations(self) -> None: """ Assign discretizations to the nodes and edges of the grid bucket. """ gb = self.gb nd = self.Nd # Shorthand key_m, var_m = self.mechanics_parameter_key, self.displacement_variable var_contact = self.contact_traction_variable var_mortar = self.mortar_displacement_variable discr_key, coupling_discr_key = pp.DISCRETIZATION, pp.COUPLING_DISCRETIZATION # For the Nd domain we solve linear elasticity with mpsa. mpsa = pp.Mpsa(key_m) # We need a void discretization for the contact traction variable defined on # the fractures. empty_discr = pp.VoidDiscretization(key_m, ndof_cell=nd) # Assign node discretizations for g, d in gb: add_nonpresent_dictionary(d, discr_key) if g.dim == nd: d[discr_key].update( {var_m: {"mpsa": mpsa},} ) elif g.dim == nd - 1: d[discr_key].update( {var_contact: {"empty": empty_discr},} ) # Define the contact condition on the mortar grid coloumb = pp.ColoumbContact(key_m, nd, mpsa) contact = pp.PrimalContactCoupling(key_m, mpsa, coloumb) for e, d in gb.edges(): g_l, g_h = gb.nodes_of_edge(e) add_nonpresent_dictionary(d, coupling_discr_key) if g_h.dim == nd: d[coupling_discr_key].update( { self.friction_coupling_term: { g_h: (var_m, "mpsa"), g_l: (var_contact, "empty"), e: (var_mortar, contact), }, } )
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." )
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 _assign_discretizations(self) -> None: """ Assign discretizations to the nodes and edges of the grid bucket. """ # For the Nd domain we solve linear elasticity with mpsa. Nd = self._Nd gb = self.gb if not self._use_ad: mpsa = pp.Mpsa(self.mechanics_parameter_key) # We need a void discretization for the contact traction variable defined on # the fractures. empty_discr = pp.VoidDiscretization(self.mechanics_parameter_key, ndof_cell=Nd) for g, d in gb: if g.dim == Nd: d[pp.DISCRETIZATION] = { self.displacement_variable: { "mpsa": mpsa } } elif g.dim == Nd - 1: d[pp.DISCRETIZATION] = { self.contact_traction_variable: { "empty": empty_discr } } # Define the contact condition on the mortar grid coloumb = pp.ColoumbContact(self.mechanics_parameter_key, Nd, mpsa) contact = pp.PrimalContactCoupling(self.mechanics_parameter_key, mpsa, coloumb) for e, d in gb.edges(): g_l, g_h = gb.nodes_of_edge(e) if g_h.dim == Nd: d[pp.COUPLING_DISCRETIZATION] = { self.friction_coupling_term: { g_h: (self.displacement_variable, "mpsa"), g_l: (self.contact_traction_variable, "empty"), (g_h, g_l): (self.mortar_displacement_variable, contact), } } else: dof_manager = pp.DofManager(gb) eq_manager = pp.ad.EquationManager(gb, dof_manager) g_primary: pp.Grid = gb.grids_of_dimension(Nd)[0] g_frac: List[pp.Grid] = gb.grids_of_dimension(Nd - 1).tolist() num_frac_cells = np.sum([g.num_cells for g in g_frac]) if len(gb.grids_of_dimension(Nd)) != 1: raise NotImplementedError("This will require further work") edge_list = [(g_primary, g) for g in g_frac] mortar_proj = pp.ad.MortarProjections(edges=edge_list, gb=gb, nd=self._Nd) subdomain_proj = pp.ad.SubdomainProjections(gb=gb, nd=self._Nd) tangential_proj_lists = [ gb.node_props( gf, "tangential_normal_projection").project_tangential_normal( gf.num_cells) for gf in g_frac ] tangential_proj = pp.ad.Matrix( sps.block_diag(tangential_proj_lists)) mpsa_ad = mpsa_ad = pp.ad.MpsaAd(self.mechanics_parameter_key, g_primary) bc_ad = pp.ad.BoundaryCondition(self.mechanics_parameter_key, grids=[g_primary]) div = 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 ]) contact_force = eq_manager.merge_variables([ (g, self.contact_traction_variable) for g in g_frac ]) u_mortar_prev = u_mortar.previous_timestep() # Stress in g_h stress = (mpsa_ad.stress * u + mpsa_ad.bound_stress * bc_ad + mpsa_ad.bound_stress * subdomain_proj.face_restriction(g_primary) * mortar_proj.mortar_to_primary_avg * u_mortar) # momentum balance equation in g_h momentum_eq = pp.ad.Expression(div * stress, dof_manager, name="momentuum", grid_order=[g_primary]) coloumb_ad = pp.ad.ColoumbContactAd(self.mechanics_parameter_key, edge_list) jump_rotate = (tangential_proj * subdomain_proj.cell_restriction(g_frac) * mortar_proj.mortar_to_secondary_avg * mortar_proj.sign_of_mortar_sides) # Contact conditions 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, grid_order=g_frac, name="contact") # 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.primary_to_mortar_int * subdomain_proj.face_prolongation(g_primary) * sign_switcher * stress) contact_from_secondary = ( mortar_proj.sign_of_mortar_sides * mortar_proj.secondary_to_mortar_int * subdomain_proj.cell_prolongation(g_frac) * tangential_proj.transpose() * contact_force) force_balance_eq = pp.ad.Expression( contact_from_primary_mortar + contact_from_secondary, dof_manager, name="force_balance", grid_order=edge_list, ) # eq2 = pp.ad.Equation(contact_from_secondary, dof_manager).to_ad(gb) eq_manager.equations += [momentum_eq, contact_eq, force_balance_eq] self._eq_manager = eq_manager