Beispiel #1
0
    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 assign_biot_discretizations(self) -> None:
        """ Assign discretizations to the nodes and edges of the grid bucket"""
        # Shorthand
        key_s, key_m = self.scalar_parameter_key, self.mechanics_parameter_key
        var_s, var_m = self.scalar_variable, self.displacement_variable
        var_mortar = self.mortar_displacement_variable
        discr_key, coupling_discr_key = pp.DISCRETIZATION, pp.COUPLING_DISCRETIZATION
        gb = self.gb

        # Assign flow and mechanics discretizations
        self.assign_scalar_discretizations()
        self.assign_mechanics_discretizations()

        # Coupling discretizations
        # All dimensions
        div_u_disc = pp.DivU(
            mechanics_keyword=key_m,
            flow_keyword=key_s,
            variable=var_m,
            mortar_variable=var_mortar,
        )
        # Nd
        grad_p_disc = pp.GradP(keyword=key_m)
        stabilization_disc_s = pp.BiotStabilization(keyword=key_s, variable=var_s)

        # Assign node discretizations
        for g, d in gb:
            if g.dim == self.Nd:
                d[discr_key][var_s].update(
                    {"stabilization": stabilization_disc_s,}
                )

                d[discr_key].update(
                    {
                        var_m + "_" + var_s: {"grad_p": grad_p_disc},
                        var_s + "_" + var_m: {"div_u": div_u_disc},
                    }
                )

        # Define edge discretizations for the mortar grid

        # fetch the previously created discretizations
        d_ = gb.node_props(self._nd_grid())
        mass_disc_s = d_[discr_key][var_s]["mass"]

        # 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(var_m, 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 gb.edges():
            g_l, g_h = gb.nodes_of_edge(e)

            if g_h.dim == self.Nd:
                d[coupling_discr_key].update(
                    {
                        "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: (var_mortar, div_u_coupling),
                        },
                        "matrix_scalar_to_force_balance": {
                            g_h: (var_s, "mass"),
                            g_l: (var_s, "mass"),
                            e: (var_mortar, matrix_scalar_to_force_balance),
                        },
                    }
                )

                if self.subtract_fracture_pressure:
                    d[coupling_discr_key].update(
                        {
                            "fracture_scalar_to_force_balance": {
                                g_h: (var_s, "mass"),
                                g_l: (var_s, "mass"),
                                e: (
                                    var_mortar,
                                    fracture_scalar_to_force_balance,  # noqa
                                ),
                            }
                        }
                    )
    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_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."
                )