Exemplo n.º 1
0
    def __init__(self, gb, flow, model="flow"):

        self.model = model
        self.gb = gb
        self.data = None
        self.assembler = None

        # discretization operator name
        self.discr_name = self.model + "_flux"
        self.discr = pp.Tpfa(self.model)

        self.coupling_name = self.discr_name + "_coupling"
        self.coupling = pp.RobinCoupling(self.model, self.discr)

        self.source_name = self.model + "_source"
        self.source = pp.ScalarSource(self.model)

        # master variable name
        self.variable = self.model + "_variable"
        self.mortar = self.model + "_lambda"

        # post process variables
        self.pressure = "pressure"
        self.flux = "darcy_flux"  # it has to be this one
        self.P0_flux = "P0_darcy_flux"
Exemplo n.º 2
0
def run_flow(gb, partition, folder):

    grid_variable = "pressure"
    mortar_variable = "mortar_flux"

    # Identifier of the discretization operator on each grid
    diffusion_term = "diffusion"
    # Identifier of the discretization operator between grids
    coupling_operator_keyword = "coupling_operator"

    # Loop over the nodes in the GridBucket, define primary variables and discretization schemes
    for g, d in gb:
        # retrieve the scheme
        discr = d["discr"]
        # Assign primary variables on this grid. It has one degree of freedom per cell.
        d[pp.PRIMARY_VARIABLES] = {grid_variable: discr["dof"]}
        # Assign discretization operator for the variable.
        d[pp.DISCRETIZATION] = {
            grid_variable: {
                diffusion_term: discr["scheme"]
            }
        }

    # Loop over the edges in the GridBucket, define primary variables and discretizations
    for e, d in gb.edges():
        # The mortar variable has one degree of freedom per cell in the mortar grid
        d[pp.PRIMARY_VARIABLES] = {mortar_variable: {"cells": 1}}

        # edge discretization
        discr1 = gb.node_props(
            e[0], pp.DISCRETIZATION)[grid_variable][diffusion_term]
        discr2 = gb.node_props(
            e[1], pp.DISCRETIZATION)[grid_variable][diffusion_term]
        edge_discretization = pp.RobinCoupling("flow", discr1, discr2)

        # The coupling discretization links an edge discretization with variables
        # and discretization operators on each neighboring grid
        d[pp.COUPLING_DISCRETIZATION] = {
            coupling_operator_keyword: {
                e[0]: (grid_variable, diffusion_term),
                e[1]: (grid_variable, diffusion_term),
                e: (mortar_variable, edge_discretization),
            }
        }

    assembler = pp.Assembler(gb,
                             active_variables=[grid_variable, mortar_variable])
    assembler.discretize()

    # Assemble the linear system, using the information stored in the GridBucket
    A, b = assembler.assemble_matrix_rhs()

    x = sps.linalg.spsolve(A, b)
    assembler.distribute_variable(x)
    for g, d in gb:
        discr = d[pp.DISCRETIZATION][grid_variable][diffusion_term]
        d[pp.STATE]["pressure"] = discr.extract_pressure(
            g, d[pp.STATE][grid_variable], d)

    _export_flow(gb, partition, folder)
    def __init__(self, gb, folder, tol):

        self.model = "flow"
        self.gb = gb
        self.data = None
        self.assembler = None

        # discretization operator name
        self.discr_name = "flux"
        self.discr = pp.RT0(self.model)

        self.mass_name = "mass"
        self.mass = pp.MixedMassMatrix(self.model)

        self.coupling_name = self.discr_name + "_coupling"
        self.coupling = pp.RobinCoupling(self.model, self.discr)

        self.source_name = "source"
        self.source = pp.DualScalarSource(self.model)

        # master variable name
        self.variable = "flow_variable"
        self.mortar = "lambda_" + self.variable

        # post process variables
        self.pressure = "pressure"
        self.flux = "darcy_flux"  # it has to be this one
        self.P0_flux = "P0_darcy_flux"

        # tolerance
        self.tol = tol

        # exporter
        self.save = pp.Exporter(self.gb, "solution", folder=folder)
Exemplo n.º 4
0
    def set_param_flow(self, gb, no_flow=False, kn=1e3, method="mpfa"):
        # Set up flow field with uniform flow in y-direction
        kw = "flow"
        for g, d in gb:
            parameter_dictionary = {}

            perm = pp.SecondOrderTensor(kxx=np.ones(g.num_cells))
            parameter_dictionary["second_order_tensor"] = perm

            b_val = np.zeros(g.num_faces)
            if g.dim == 2:
                bound_faces = pp.face_on_side(g, ["ymin", "ymax"])
                if no_flow:
                    b_val[bound_faces[0]] = 1
                    b_val[bound_faces[1]] = 1
                bound_faces = np.hstack((bound_faces[0], bound_faces[1]))
                labels = np.array(["dir"] * bound_faces.size)
                parameter_dictionary["bc"] = pp.BoundaryCondition(
                    g, bound_faces, labels)

                y_max_faces = pp.face_on_side(g, "ymax")[0]
                b_val[y_max_faces] = 1
            else:
                parameter_dictionary["bc"] = pp.BoundaryCondition(g)
            parameter_dictionary["bc_values"] = b_val
            parameter_dictionary["mpfa_inverter"] = "python"

            d[pp.PARAMETERS] = pp.Parameters(g, [kw], [parameter_dictionary])
            d[pp.DISCRETIZATION_MATRICES] = {"flow": {}}

        gb.add_edge_props("kn")
        for e, d in gb.edges():
            mg = d["mortar_grid"]
            flow_dictionary = {
                "normal_diffusivity": 2 * kn * np.ones(mg.num_cells)
            }
            d[pp.PARAMETERS] = pp.Parameters(keywords=["flow"],
                                             dictionaries=[flow_dictionary])
            d[pp.DISCRETIZATION_MATRICES] = {"flow": {}}

        discretization_key = kw + "_" + pp.DISCRETIZATION

        for g, d in gb:
            # Choose discretization and define the solver
            if method == "mpfa":
                discr = pp.Mpfa(kw)
            elif method == "mvem":
                discr = pp.MVEM(kw)
            else:
                discr = pp.Tpfa(kw)

            d[discretization_key] = discr

        for _, d in gb.edges():
            d[discretization_key] = pp.RobinCoupling(kw, discr)
Exemplo n.º 5
0
    def __init__(self, keyword, edges):
        if isinstance(edges, list):
            self._edges = edges
        else:
            self._edges = [edges]
        self._discretization = pp.RobinCoupling(keyword, primary_keyword=keyword)
        self._name = "Robin interface coupling"
        self.keyword = keyword

        self.mortar_scaling: _MergedOperator
        self.mortar_discr: _MergedOperator
        _wrap_discretization(self, self._discretization, edges)
Exemplo n.º 6
0
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
Exemplo n.º 7
0
    def assign_scalar_discretizations(self) -> None:
        """
        Assign discretizations to the nodes and edges of the grid bucket.

        Note the attribute subtract_fracture_pressure: Indicates whether or not to
        subtract the fracture pressure contribution for the contact traction. This
        should not be done if the scalar variable is temperature.
        """
        gb = self.gb
        # Shorthand
        key_s = self.scalar_parameter_key
        var_s = self.scalar_variable
        discr_key, coupling_discr_key = pp.DISCRETIZATION, pp.COUPLING_DISCRETIZATION

        # Scalar discretizations (all dimensions)
        diff_disc_s = implicit_euler.ImplicitMpfa(key_s)
        mass_disc_s = implicit_euler.ImplicitMassMatrix(key_s, var_s)
        source_disc_s = pp.ScalarSource(key_s)

        # Assign node discretizations
        for _, d in gb:
            add_nonpresent_dictionary(d, discr_key)

            d[discr_key].update({
                var_s: {
                    "diffusion": diff_disc_s,
                    "mass": mass_disc_s,
                    "source": source_disc_s,
                },
            })

        # Assign edge discretizations
        for e, d in gb.edges():
            g_l, g_h = gb.nodes_of_edge(e)
            add_nonpresent_dictionary(d, coupling_discr_key)

            d[coupling_discr_key].update({
                self.scalar_coupling_term: {
                    g_h: (var_s, "diffusion"),
                    g_l: (var_s, "diffusion"),
                    e: (
                        self.mortar_scalar_variable,
                        pp.RobinCoupling(key_s, diff_disc_s),
                    ),
                },
            })
    def assign_discretizations(self) -> None:
        """
        Assign discretizations to the nodes and edges of the grid bucket.

        Note the attribute subtract_fracture_pressure: Indicates whether or not to
        subtract the fracture pressure contribution for the contact traction. This
        should not be done if the scalar variable is temperature.
        """
        # from porepy.utils.derived_discretizations import (
        #     implicit_euler as IE_discretizations,
        # )

        # Shorthand
        key_s = self.scalar_parameter_key
        var_s = self.scalar_variable

        # Scalar discretizations (all dimensions)
        diff_disc_s = pp.Mpfa(key_s)
        # mass_disc_s = pp.MassMatrix(key_s)
        # diff_disc_s = IE_discretizations.ImplicitMpfa(key_s)
        # mass_disc_s = IE_discretizations.ImplicitMassMatrix(key_s, var_s)
        source_disc_s = pp.ScalarSource(key_s)

        # Assign node discretizations
        for g, d in self.gb:
            d[pp.DISCRETIZATION] = {
                var_s: {
                    "diffusion": diff_disc_s,
                    # "mass": mass_disc_s,
                    "source": source_disc_s,
                },
            }

        for e, d in self.gb.edges():
            g_l, g_h = self.gb.nodes_of_edge(e)
            d[pp.COUPLING_DISCRETIZATION] = {
                self.scalar_coupling_term: {
                    g_h: (var_s, "diffusion"),
                    g_l: (var_s, "diffusion"),
                    e: (
                        self.mortar_scalar_variable,
                        pp.RobinCoupling(key_s, diff_disc_s),
                    ),
                },
            }
Exemplo n.º 9
0
    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:]
Exemplo n.º 10
0
    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)
Exemplo n.º 11
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,
                        ),
                    },
                })
Exemplo n.º 12
0
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
Exemplo n.º 13
0
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"])
Exemplo n.º 14
0
    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)
Exemplo n.º 15
0
def test_md_flow():

    # Three fractures, will create intersection lines and point
    frac_1 = np.array([[2, 4, 4, 2], [3, 3, 3, 3], [0, 0, 6, 6]])
    frac_2 = np.array([[3, 3, 3, 3], [2, 4, 4, 2], [0, 0, 6, 6]])
    frac_3 = np.array([[0, 6, 6, 0], [2, 2, 4, 4], [3, 3, 3, 3]])

    gb = pp.meshing.cart_grid(fracs=[frac_1, frac_2, frac_3],
                              nx=np.array([6, 6, 6]))
    gb.compute_geometry()

    pressure_variable = "pressure"
    flux_variable = "mortar_flux"

    keyword = "flow"
    discr = pp.Tpfa(keyword)
    source_discr = pp.ScalarSource(keyword)
    coupling_discr = pp.RobinCoupling(keyword, discr, discr)

    for g, d in gb:

        # Assign data
        if g.dim == gb.dim_max():
            upper_left_ind = np.argmax(np.linalg.norm(g.face_centers, axis=0))
            bc = pp.BoundaryCondition(g, np.array([0, upper_left_ind]),
                                      ["dir", "dir"])
            bc_values = np.zeros(g.num_faces)
            bc_values[0] = 1
            sources = np.random.rand(g.num_cells) * g.cell_volumes
            specified_parameters = {
                "bc": bc,
                "bc_values": bc_values,
                "source": sources
            }
        else:
            sources = np.random.rand(g.num_cells) * g.cell_volumes
            specified_parameters = {"source": sources}

        # Initialize data
        pp.initialize_default_data(g, d, keyword, specified_parameters)

        # Declare grid primary variable
        d[pp.PRIMARY_VARIABLES] = {pressure_variable: {"cells": 1}}

        # Assign discretization
        d[pp.DISCRETIZATION] = {
            pressure_variable: {
                "diff": discr,
                "source": source_discr
            }
        }

        # Initialize state
        d[pp.STATE] = {
            pressure_variable: np.zeros(g.num_cells),
            pp.ITERATE: {
                pressure_variable: np.zeros(g.num_cells)
            },
        }
    for e, d in gb.edges():
        mg = d["mortar_grid"]
        pp.initialize_data(mg, d, keyword, {"normal_diffusivity": 1})

        d[pp.PRIMARY_VARIABLES] = {flux_variable: {"cells": 1}}
        d[pp.COUPLING_DISCRETIZATION] = {}
        d[pp.COUPLING_DISCRETIZATION]["coupling"] = {
            e[0]: (pressure_variable, "diff"),
            e[1]: (pressure_variable, "diff"),
            e: (flux_variable, coupling_discr),
        }
        d[pp.STATE] = {
            flux_variable: np.zeros(mg.num_cells),
            pp.ITERATE: {
                flux_variable: np.zeros(mg.num_cells)
            },
        }

    dof_manager = pp.DofManager(gb)
    assembler = pp.Assembler(gb, dof_manager)
    assembler.discretize()

    # Reference discretization
    A_ref, b_ref = assembler.assemble_matrix_rhs()

    manager = pp.ad.EquationManager(gb, dof_manager)

    grid_list = [g for g, _ in gb]
    edge_list = [e for e, _ in gb.edges()]

    node_discr = pp.ad.MpfaAd(keyword, grid_list)

    edge_discr = pp.ad.RobinCouplingAd(keyword, edge_list)

    bc_val = pp.ad.BoundaryCondition(keyword, grid_list)

    source = pp.ad.ParameterArray(param_keyword=keyword,
                                  array_keyword='source',
                                  grids=grid_list)

    projections = pp.ad.MortarProjections(gb=gb)
    div = pp.ad.Divergence(grids=grid_list)

    p = manager.merge_variables([(g, pressure_variable) for g in grid_list])
    lmbda = manager.merge_variables([(e, flux_variable) for e in edge_list])

    flux = (node_discr.flux * p + node_discr.bound_flux * bc_val +
            node_discr.bound_flux * projections.mortar_to_primary_int * lmbda)
    flow_eq = div * flux - projections.mortar_to_secondary_int * lmbda - source

    interface_flux = edge_discr.mortar_scaling * (
        projections.primary_to_mortar_avg * node_discr.bound_pressure_cell * p
        + projections.primary_to_mortar_avg * node_discr.bound_pressure_face *
        projections.mortar_to_primary_int * lmbda -
        projections.secondary_to_mortar_avg * p +
        edge_discr.mortar_discr * lmbda)

    flow_eq_ad = pp.ad.Expression(flow_eq, dof_manager, "flow on nodes")
    flow_eq_ad.discretize(gb)

    interface_eq_ad = pp.ad.Expression(interface_flux, dof_manager,
                                       "flow on interface")

    manager.equations += [flow_eq_ad, interface_eq_ad]

    state = np.zeros(gb.num_cells() + gb.num_mortar_cells())
    A, b = manager.assemble_matrix_rhs(state=state)
    diff = A - A_ref
    if diff.data.size > 0:
        assert np.max(np.abs(diff.data)) < 1e-10
    assert np.max(np.abs(b - b_ref)) < 1e-10
Exemplo n.º 16
0
def flow(gb, param, bc_flag):

    model = "flow"

    model_data = data_flow(gb, model, param, bc_flag)

    # discretization operator name
    flux_id = "flux"

    # master variable name
    variable = "flow_variable"
    mortar = "lambda_" + variable

    # post process variables
    pressure = "pressure"
    flux = "darcy_flux"  # it has to be this one

    # save variable name for the advection-diffusion problem
    param["pressure"] = pressure
    param["flux"] = flux
    param["mortar_flux"] = mortar

    # define the dof and discretization for the grids
    for g, d in gb:
        d[pp.PRIMARY_VARIABLES] = {variable: {"cells": 1, "faces": 1}}
        if g.dim == gb.dim_max():
            discr = pp.RT0(model_data)
        else:
            discr = RT0Multilayer(model_data)
        d[pp.DISCRETIZATION] = {variable: {flux_id: discr}}

    # define the interface terms to couple the grids
    for e, d in gb.edges():
        g_slave, g_master = gb.nodes_of_edge(e)
        d[pp.PRIMARY_VARIABLES] = {mortar: {"cells": 1}}

        # retrive the discretization of the master and slave grids
        discr_master = gb.node_props(g_master,
                                     pp.DISCRETIZATION)[variable][flux_id]
        discr_slave = gb.node_props(g_slave,
                                    pp.DISCRETIZATION)[variable][flux_id]

        if g_master.dim == gb.dim_max():
            # classical 2d-1d/3d-2d coupling condition
            coupling = pp.RobinCoupling(model_data, discr_master, discr_slave)

            d[pp.COUPLING_DISCRETIZATION] = {
                flux: {
                    g_slave: (variable, flux_id),
                    g_master: (variable, flux_id),
                    e: (mortar, coupling),
                }
            }
        elif g_master.dim < gb.dim_max():
            # the multilayer coupling condition
            coupling = RobinCouplingMultiLayer(model_data, discr_master,
                                               discr_slave)

            d[pp.COUPLING_DISCRETIZATION] = {
                flux: {
                    g_slave: (variable, flux_id),
                    g_master: (variable, flux_id),
                    e: (mortar, coupling),
                }
            }

    # solution of the darcy problem
    assembler = pp.Assembler()

    logger.info("Assemble the flow problem")
    A, b, block_dof, full_dof = assembler.assemble_matrix_rhs(gb)
    logger.info("done")

    logger.info("Solve the linear system")
    x = sps.linalg.spsolve(A, b)
    logger.info("done")

    logger.info("Variable post-process")
    assembler.distribute_variable(gb, x, block_dof, full_dof)
    for g, d in gb:
        d[pressure] = discr.extract_pressure(g, d[variable], d)
        d[flux] = discr.extract_flux(g, d[variable], d)

    # export the P0 flux reconstruction
    P0_flux = "P0_flux"
    param["P0_flux"] = P0_flux
    pp.project_flux(gb, discr, flux, P0_flux, mortar)

    # identification of layer and fault
    for g, d in gb:
        # save the identification of the fault
        if "fault" in g.name:
            d["fault"] = np.ones(g.num_cells)
            d["layer"] = np.zeros(g.num_cells)
        # save the identification of the layer
        elif "layer" in g.name:
            d["fault"] = np.zeros(g.num_cells)
            half_cells = int(g.num_cells / 2)
            d["layer"] = np.hstack(
                (np.ones(half_cells), 2 * np.ones(half_cells)))
        # save zero for the other cases
        else:
            d["fault"] = np.zeros(g.num_cells)
            d["layer"] = np.zeros(g.num_cells)

    save = pp.Exporter(gb, "solution", folder=param["folder"])
    save.write_vtk([pressure, P0_flux, "fault", "layer"])

    logger.info("done")
Exemplo n.º 17
0
def permeability_upscaling(network,
                           data,
                           mesh_args,
                           directions,
                           do_viz=True,
                           tracer_transport=False):
    """ Compute bulk permeabilities for a 2d domain with a fracture network.

    The function sets up a flow field in the specified directions, and calculates
    an upscaled permeability of corresponding to the calculated flow configuration.

    Parameters:
        network (pp.FractureNetwork2d): Network, with domain.
        data (dictionary): Data to be specified.
        mesh_args (dictionray): Parameters for meshing. See FractureNetwork2d.mesh()
            for details.
        directions (np.array): Directions to upscale permeabilities.
            Indicated by 0 (x-direction) and or 1 (y-direction).

    Returns:
        np.array, dim directions.size: Upscaled permeability in each of the directions.
        sim_info: Various information on the time spent on this simulation.

    """

    directions = np.asarray(directions)
    upscaled_perm = np.zeros(directions.size)

    sim_info = {}

    for di, direct in enumerate(directions):

        tic = time.time()
        filename = str(uuid.uuid4())
        gb = network.mesh(tol=network.tol,
                          mesh_args=mesh_args,
                          file_name=filename)

        toc = time.time()
        if di == 0:
            sim_info["num_cells_2d"] = gb.grids_of_dimension(2)[0].num_cells
            sim_info["time_for_meshing"] = toc - tic

        gb = _setup_simulation_flow(gb, data, direct)

        pressure_kw = "flow"

        mpfa = pp.Mpfa(pressure_kw)
        for g, d in gb:
            d[pp.PRIMARY_VARIABLES] = {"pressure": {"cells": 1}}
            d[pp.DISCRETIZATION] = {"pressure": {"diffusive": mpfa}}

        coupler = pp.RobinCoupling(pressure_kw, mpfa)
        for e, d in gb.edges():
            g1, g2 = gb.nodes_of_edge(e)
            d[pp.PRIMARY_VARIABLES] = {"mortar_darcy_flux": {"cells": 1}}
            d[pp.COUPLING_DISCRETIZATION] = {
                "lambda": {
                    g1: ("pressure", "diffusive"),
                    g2: ("pressure", "diffusive"),
                    e: ("mortar_darcy_flux", coupler),
                }
            }

        assembler = pp.Assembler(gb)
        assembler.discretize()
        # Discretize
        tic = time.time()
        A, b = assembler.assemble_matrix_rhs()
        if di == 0:
            toc = time.time()
            sim_info["Time_for_assembly"] = toc - tic
        tic = time.time()
        p = spla.spsolve(A, b)
        if di == 0:
            toc = time.time()
            sim_info["Time_for_pressure_solve"] = toc - tic

        assembler.distribute_variable(p)

        # Post processing to recover flux
        tot_pressure = 0
        tot_area = 0
        tot_inlet_flux = 0
        for g, d in gb:
            # Inlet faces of this grid
            inlet = d.get("inlet_faces", None)
            # If no inlet, no need to do anything
            if inlet is None or inlet.size == 0:
                continue

            # Compute flux field in the grid
            # Internal flux
            flux = (d[pp.DISCRETIZATION_MATRICES][pressure_kw]["flux"] *
                    d[pp.STATE]["pressure"])
            # Contribution from the boundary
            bound_flux_discr = d[
                pp.DISCRETIZATION_MATRICES][pressure_kw]["bound_flux"]
            flux += bound_flux_discr * d["parameters"][pressure_kw]["bc_values"]
            # Add contribution from all neighboring lower-dimensional interfaces
            for e, d_e in gb.edges_of_node(g):
                mg = d_e["mortar_grid"]
                if mg.dim == g.dim - 1:
                    flux += (bound_flux_discr * mg.mortar_to_master_int() *
                             d_e[pp.STATE]["mortar_darcy_flux"])

            # Add contribution to total inlet flux
            tot_inlet_flux += np.abs(flux[inlet]).sum()

            # Next, find the pressure at the inlet faces
            # The pressure is calculated as the values in the neighboring cells,
            # plus an offset from the incell-variations
            pressure_cell = (d[pp.DISCRETIZATION_MATRICES][pressure_kw]
                             ["bound_pressure_cell"] * d[pp.STATE]["pressure"])
            pressure_flux = (d[pp.DISCRETIZATION_MATRICES][pressure_kw]
                             ["bound_pressure_face"] *
                             d["parameters"][pressure_kw]["bc_values"])
            inlet_pressure = pressure_cell[inlet] + pressure_flux[inlet]
            # Scale the pressure at the face with the face length.
            # Also include an aperture scaling here: For the highest dimensional grid, this
            # will be as scaling with 1.
            aperture = d[pp.PARAMETERS][pressure_kw]["aperture"][0]
            tot_pressure += np.sum(inlet_pressure * g.face_areas[inlet] *
                                   aperture)
            # Add to the toal outlet area
            tot_area += g.face_areas[inlet].sum() * aperture
            # Also compute the cross sectional area of the domain
            if g.dim == gb.dim_max():
                # Extension of the domain in the active direction
                dx = g.nodes[direct].max() - g.nodes[direct].min()

        # Mean pressure at the inlet, which will also be mean pressure difference
        # over the domain
        mean_pressure = tot_pressure / tot_area

        # The upscaled permeability
        upscaled_perm[di] = (tot_inlet_flux / tot_area) * dx / mean_pressure
        # End of post processing for permeability upscaling

        if tracer_transport:

            _setup_simulation_tracer(gb, data, direct)

            tracer_variable = "temperature"

            temperature_kw = "transport"
            mortar_variable = "lambda_adv"

            # Identifier of the two terms of the equation
            adv = "advection"

            advection_term = "advection"
            mass_term = "mass"
            advection_coupling_term = "advection_coupling"

            adv_discr = pp.Upwind(temperature_kw)

            adv_coupling = pp.UpwindCoupling(temperature_kw)

            mass_discretization = pp.MassMatrix(temperature_kw)

            for g, d in gb:
                d[pp.PRIMARY_VARIABLES] = {tracer_variable: {"cells": 1}}
                d[pp.DISCRETIZATION] = {
                    tracer_variable: {
                        advection_term: adv_discr,
                        mass_term: mass_discretization,
                    }
                }

            for e, d in gb.edges():
                g1, g2 = gb.nodes_of_edge(e)
                d[pp.PRIMARY_VARIABLES] = {mortar_variable: {"cells": 1}}
                d[pp.COUPLING_DISCRETIZATION] = {
                    advection_coupling_term: {
                        g1: (tracer_variable, adv),
                        g2: (tracer_variable, adv),
                        e: (mortar_variable, adv_coupling),
                    }
                }

            pp.fvutils.compute_darcy_flux(gb,
                                          keyword_store=temperature_kw,
                                          lam_name="mortar_darcy_flux")

            A, b, block_dof, full_dof = assembler.assemble_matrix_rhs(
                gb,
                active_variables=[tracer_variable, mortar_variable],
                add_matrices=False,
            )

            advection_coupling_term += ("_" + mortar_variable + "_" +
                                        tracer_variable + "_" +
                                        tracer_variable)

            mass_term += "_" + tracer_variable
            advection_term += "_" + tracer_variable

            lhs = A[mass_term] + data["time_step"] * (
                A[advection_term] + A[advection_coupling_term])
            rhs_source_adv = data["time_step"] * (b[advection_term] +
                                                  b[advection_coupling_term])

            IEsolver = spla.factorized(lhs)

            save_every = 10
            n_steps = int(np.round(data["t_max"] / data["time_step"]))

            # Initial condition
            tracer = np.zeros(rhs_source_adv.size)
            assembler.distribute_variable(
                gb,
                tracer,
                block_dof,
                full_dof,
                variable_names=[tracer_variable, mortar_variable],
            )

            # Exporter
            exporter = pp.Exporter(gb, name=tracer_variable, folder="viz_tmp")
            export_fields = [tracer_variable]

            for i in range(n_steps):

                if np.isclose(i % save_every, 0):
                    # Export existing solution (final export is taken care of below)
                    assembler.distribute_variable(
                        gb,
                        tracer,
                        block_dof,
                        full_dof,
                        variable_names=[tracer_variable, mortar_variable],
                    )
                    exporter.write_vtk(export_fields,
                                       time_step=int(i // save_every))
                tracer = IEsolver(A[mass_term] * tracer + rhs_source_adv)

            exporter.write_vtk(export_fields,
                               time_step=(n_steps // save_every))
            time_steps = np.arange(0, data["t_max"] + data["time_step"],
                                   save_every * data["time_step"])
            exporter.write_pvd(time_steps)

    return upscaled_perm, sim_info
    def _assign_discretizations(self) -> None:
        """
        Assign discretizations to the nodes and edges of the grid bucket.

        Note the attribute subtract_fracture_pressure: Indicates whether or not to
        subtract the fracture pressure contribution for the contact traction. This
        should not be done if the scalar variable is temperature.
        """
        if not self._use_ad:

            # Shorthand
            key_s, key_m = self.scalar_parameter_key, self.mechanics_parameter_key
            var_s, var_d = self.scalar_variable, self.displacement_variable

            # Define discretization
            # For the Nd domain we solve linear elasticity with mpsa.
            mpsa = pp.Mpsa(key_m)
            empty_discr = pp.VoidDiscretization(key_m, ndof_cell=self._Nd)
            # Scalar discretizations (all dimensions)
            diff_disc_s = IE_discretizations.ImplicitMpfa(key_s)
            mass_disc_s = IE_discretizations.ImplicitMassMatrix(key_s, var_s)
            source_disc_s = pp.ScalarSource(key_s)
            # Coupling discretizations
            # All dimensions
            div_u_disc = pp.DivU(
                key_m,
                key_s,
                variable=var_d,
                mortar_variable=self.mortar_displacement_variable,
            )
            # Nd
            grad_p_disc = pp.GradP(key_m)
            stabilization_disc_s = pp.BiotStabilization(key_s, var_s)

            # Assign node discretizations
            for g, d in self.gb:
                if g.dim == self._Nd:
                    d[pp.DISCRETIZATION] = {
                        var_d: {"mpsa": mpsa},
                        var_s: {
                            "diffusion": diff_disc_s,
                            "mass": mass_disc_s,
                            "stabilization": stabilization_disc_s,
                            "source": source_disc_s,
                        },
                        var_d + "_" + var_s: {"grad_p": grad_p_disc},
                        var_s + "_" + var_d: {"div_u": div_u_disc},
                    }

                elif g.dim == self._Nd - 1:
                    d[pp.DISCRETIZATION] = {
                        self.contact_traction_variable: {"empty": empty_discr},
                        var_s: {
                            "diffusion": diff_disc_s,
                            "mass": mass_disc_s,
                            "source": source_disc_s,
                        },
                    }
                else:
                    d[pp.DISCRETIZATION] = {
                        var_s: {
                            "diffusion": diff_disc_s,
                            "mass": mass_disc_s,
                            "source": source_disc_s,
                        }
                    }

            # Define edge discretizations for the mortar grid
            contact_law = pp.ColoumbContact(
                self.mechanics_parameter_key, self._Nd, mpsa
            )
            contact_discr = pp.PrimalContactCoupling(
                self.mechanics_parameter_key, mpsa, contact_law
            )
            # Account for the mortar displacements effect on scalar balance in the matrix,
            # as an internal boundary contribution, fracture, aperture changes appear as a
            # source contribution.
            div_u_coupling = pp.DivUCoupling(
                self.displacement_variable, div_u_disc, div_u_disc
            )
            # Account for the pressure contributions to the force balance on the fracture
            # (see contact_discr).
            # This discretization needs the keyword used to store the grad p discretization:
            grad_p_key = key_m
            matrix_scalar_to_force_balance = pp.MatrixScalarToForceBalance(
                grad_p_key, mass_disc_s, mass_disc_s
            )
            if self.subtract_fracture_pressure:
                fracture_scalar_to_force_balance = pp.FractureScalarToForceBalance(
                    mass_disc_s, mass_disc_s
                )

            for e, d in self.gb.edges():
                g_l, g_h = self.gb.nodes_of_edge(e)

                if g_h.dim == self._Nd:
                    d[pp.COUPLING_DISCRETIZATION] = {
                        self.friction_coupling_term: {
                            g_h: (var_d, "mpsa"),
                            g_l: (self.contact_traction_variable, "empty"),
                            (g_h, g_l): (
                                self.mortar_displacement_variable,
                                contact_discr,
                            ),
                        },
                        self.scalar_coupling_term: {
                            g_h: (var_s, "diffusion"),
                            g_l: (var_s, "diffusion"),
                            e: (
                                self.mortar_scalar_variable,
                                pp.RobinCoupling(key_s, diff_disc_s),
                            ),
                        },
                        "div_u_coupling": {
                            g_h: (
                                var_s,
                                "mass",
                            ),  # This is really the div_u, but this is not implemented
                            g_l: (var_s, "mass"),
                            e: (self.mortar_displacement_variable, div_u_coupling),
                        },
                        "matrix_scalar_to_force_balance": {
                            g_h: (var_s, "mass"),
                            g_l: (var_s, "mass"),
                            e: (
                                self.mortar_displacement_variable,
                                matrix_scalar_to_force_balance,
                            ),
                        },
                    }
                    if self.subtract_fracture_pressure:
                        d[pp.COUPLING_DISCRETIZATION].update(
                            {
                                "fracture_scalar_to_force_balance": {
                                    g_h: (var_s, "mass"),
                                    g_l: (var_s, "mass"),
                                    e: (
                                        self.mortar_displacement_variable,
                                        fracture_scalar_to_force_balance,
                                    ),
                                }
                            }
                        )
                else:
                    d[pp.COUPLING_DISCRETIZATION] = {
                        self.scalar_coupling_term: {
                            g_h: (var_s, "diffusion"),
                            g_l: (var_s, "diffusion"),
                            e: (
                                self.mortar_scalar_variable,
                                pp.RobinCoupling(key_s, diff_disc_s),
                            ),
                        }
                    }

        else:

            gb = self.gb
            Nd = self._Nd
            dof_manager = pp.DofManager(gb)
            eq_manager = pp.ad.EquationManager(gb, dof_manager)

            g_primary = gb.grids_of_dimension(Nd)[0]
            g_frac = gb.grids_of_dimension(Nd - 1).tolist()

            grid_list = [
                g_primary,
                *g_frac,
                *gb.grids_of_dimension(Nd - 2),
                *gb.grids_of_dimension(Nd - 3),
            ]

            if len(gb.grids_of_dimension(Nd)) != 1:
                raise NotImplementedError("This will require further work")

            edge_list_highest = [(g_primary, g) for g in g_frac]
            edge_list = [e for e, _ in gb.edges()]

            mortar_proj_scalar = pp.ad.MortarProjections(edges=edge_list, gb=gb, nd=1)
            mortar_proj_vector = pp.ad.MortarProjections(
                edges=edge_list_highest, gb=gb, nd=self._Nd
            )
            subdomain_proj_scalar = pp.ad.SubdomainProjections(gb=gb)
            subdomain_proj_vector = pp.ad.SubdomainProjections(gb=gb, nd=self._Nd)

            tangential_normal_proj_list = []
            normal_proj_list = []
            for gf in g_frac:
                proj = gb.node_props(gf, "tangential_normal_projection")
                tangential_normal_proj_list.append(
                    proj.project_tangential_normal(gf.num_cells)
                )
                normal_proj_list.append(proj.project_normal(gf.num_cells))

            tangential_normal_proj = pp.ad.Matrix(
                sps.block_diag(tangential_normal_proj_list)
            )
            normal_proj = pp.ad.Matrix(sps.block_diag(normal_proj_list))

            # Ad representation of discretizations
            mpsa_ad = pp.ad.BiotAd(self.mechanics_parameter_key, g_primary)
            grad_p_ad = pp.ad.GradPAd(self.mechanics_parameter_key, g_primary)

            mpfa_ad = pp.ad.MpfaAd(self.scalar_parameter_key, grid_list)
            mass_ad = pp.ad.MassMatrixAd(self.scalar_parameter_key, grid_list)
            robin_ad = pp.ad.RobinCouplingAd(self.scalar_parameter_key, edge_list)

            div_u_ad = pp.ad.DivUAd(
                self.mechanics_parameter_key,
                grids=g_primary,
                mat_dict_keyword=self.scalar_parameter_key,
            )
            stab_biot_ad = pp.ad.BiotStabilizationAd(
                self.scalar_parameter_key, g_primary
            )

            coloumb_ad = pp.ad.ColoumbContactAd(
                self.mechanics_parameter_key, edge_list_highest
            )

            bc_ad = pp.ad.BoundaryCondition(
                self.mechanics_parameter_key, grids=[g_primary]
            )
            div_vector = pp.ad.Divergence(grids=[g_primary], dim=g_primary.dim)

            # Primary variables on Ad form
            u = eq_manager.variable(g_primary, self.displacement_variable)
            u_mortar = eq_manager.merge_variables(
                [(e, self.mortar_displacement_variable) for e in edge_list_highest]
            )
            contact_force = eq_manager.merge_variables(
                [(g, self.contact_traction_variable) for g in g_frac]
            )
            p = eq_manager.merge_variables(
                [(g, self.scalar_variable) for g in grid_list]
            )
            mortar_flux = eq_manager.merge_variables(
                [(e, self.mortar_scalar_variable) for e in edge_list]
            )

            u_prev = u.previous_timestep()
            u_mortar_prev = u_mortar.previous_timestep()
            p_prev = p.previous_timestep()

            # Reference pressure, corresponding to an initial stress free state
            p_reference = pp.ad.ParameterArray(
                param_keyword=self.mechanics_parameter_key,
                array_keyword="p_reference",
                grids=[g_primary],
                gb=gb,
            )

            # Stress in g_h
            stress = (
                mpsa_ad.stress * u
                + mpsa_ad.bound_stress * bc_ad
                + mpsa_ad.bound_stress
                * subdomain_proj_vector.face_restriction(g_primary)
                * mortar_proj_vector.mortar_to_primary_avg
                * u_mortar
                + grad_p_ad.grad_p
                * subdomain_proj_scalar.cell_restriction(g_primary)
                * p
                # The reference pressure is only defined on g_primary, thus there is no need
                # for a subdomain projection.
                - grad_p_ad.grad_p * p_reference
            )

            momentum_eq = pp.ad.Expression(
                div_vector * stress, dof_manager, "momentuum", grid_order=[g_primary]
            )

            jump = (
                subdomain_proj_vector.cell_restriction(g_frac)
                * mortar_proj_vector.mortar_to_secondary_avg
                * mortar_proj_vector.sign_of_mortar_sides
            )
            jump_rotate = tangential_normal_proj * jump

            # Contact conditions
            num_frac_cells = np.sum([g.num_cells for g in g_frac])

            jump_discr = coloumb_ad.displacement * jump_rotate * u_mortar
            tmp = np.ones(num_frac_cells * self._Nd)
            tmp[self._Nd - 1 :: self._Nd] = 0
            exclude_normal = pp.ad.Matrix(
                sps.dia_matrix((tmp, 0), shape=(tmp.size, tmp.size))
            )
            # Rhs of contact conditions
            rhs = (
                coloumb_ad.rhs
                + exclude_normal * coloumb_ad.displacement * jump_rotate * u_mortar_prev
            )
            contact_conditions = coloumb_ad.traction * contact_force + jump_discr - rhs
            contact_eq = pp.ad.Expression(
                contact_conditions, dof_manager, "contact", grid_order=g_frac
            )

            # Force balance
            mat = None
            for _, d in gb.edges():
                mg: pp.MortarGrid = d["mortar_grid"]
                if mg.dim < self._Nd - 1:
                    continue

                faces_on_fracture_surface = mg.primary_to_mortar_int().tocsr().indices
                m = pp.grid_utils.switch_sign_if_inwards_normal(
                    g_primary, self._Nd, faces_on_fracture_surface
                )
                if mat is None:
                    mat = m
                else:
                    mat += m

            sign_switcher = pp.ad.Matrix(mat)

            # Contact from primary grid and mortar displacements (via primary grid)
            contact_from_primary_mortar = (
                mortar_proj_vector.primary_to_mortar_int
                * subdomain_proj_vector.face_prolongation(g_primary)
                * sign_switcher
                * stress
            )
            contact_from_secondary = (
                mortar_proj_vector.sign_of_mortar_sides
                * mortar_proj_vector.secondary_to_mortar_int
                * subdomain_proj_vector.cell_prolongation(g_frac)
                * tangential_normal_proj.transpose()
                * contact_force
            )
            if self.subtract_fracture_pressure:
                # This gives an error because of -=

                mat = []

                for e in edge_list_highest:
                    mg = gb.edge_props(e, "mortar_grid")

                    faces_on_fracture_surface = (
                        mg.primary_to_mortar_int().tocsr().indices
                    )
                    sgn, _ = g_primary.signs_and_cells_of_boundary_faces(
                        faces_on_fracture_surface
                    )
                    fracture_normals = g_primary.face_normals[
                        : self._Nd, faces_on_fracture_surface
                    ]
                    outwards_fracture_normals = sgn * fracture_normals

                    data = outwards_fracture_normals.ravel("F")
                    row = np.arange(g_primary.dim * mg.num_cells)
                    col = np.tile(np.arange(mg.num_cells), (g_primary.dim, 1)).ravel(
                        "F"
                    )
                    n_dot_I = sps.csc_matrix((data, (row, col)))
                    # i) The scalar contribution to the contact stress is mapped to
                    # the mortar grid  and multiplied by n \dot I, with n being the
                    # outwards normals on the two sides.
                    # Note that by using different normals for the two sides, we do not need to
                    # adjust the secondary pressure with the corresponding signs by applying
                    # sign_of_mortar_sides as done in PrimalContactCoupling.
                    # iii) The contribution should be subtracted so that we balance the primary
                    # forces by
                    # T_contact - n dot I p,
                    # hence the minus.
                    mat.append(n_dot_I * mg.secondary_to_mortar_int(nd=1))
                # May need to do this as for tangential projections, additive that is
                normal_matrix = pp.ad.Matrix(sps.block_diag(mat))

                p_frac = subdomain_proj_scalar.cell_restriction(g_frac) * p

                contact_from_secondary2 = normal_matrix * p_frac
            force_balance_eq = pp.ad.Expression(
                contact_from_primary_mortar
                + contact_from_secondary
                + contact_from_secondary2,
                dof_manager,
                "force_balance",
                grid_order=edge_list_highest,
            )

            bc_val_scalar = pp.ad.BoundaryCondition(
                self.scalar_parameter_key, grid_list
            )

            div_scalar = pp.ad.Divergence(grids=grid_list)

            dt = self.time_step

            # FIXME: Need bc for div_u term, including previous time step
            accumulation_primary = (
                div_u_ad.div_u * (u - u_prev)
                + stab_biot_ad.stabilization
                * subdomain_proj_scalar.cell_restriction(g_primary)
                * (p - p_prev)
                + div_u_ad.bound_div_u
                * subdomain_proj_vector.face_restriction(g_primary)
                * mortar_proj_vector.mortar_to_primary_int
                * (u_mortar - u_mortar_prev)
            )

            # Accumulation term on the fractures.
            frac_vol = np.hstack([g.cell_volumes for g in g_frac])
            vol_mat = pp.ad.Matrix(
                sps.dia_matrix((frac_vol, 0), shape=(num_frac_cells, num_frac_cells))
            )
            accumulation_fracs = (
                vol_mat * normal_proj * jump * (u_mortar - u_mortar_prev)
            )

            accumulation_all = mass_ad.mass * (p - p_prev)

            flux = (
                mpfa_ad.flux * p
                + mpfa_ad.bound_flux * bc_val_scalar
                + mpfa_ad.bound_flux
                * mortar_proj_scalar.mortar_to_primary_int
                * mortar_flux
            )
            flow_md = (
                dt
                * (  # Time scaling of flux terms, both inter-dimensional and from
                    # the higher dimension
                    div_scalar * flux
                    - mortar_proj_scalar.mortar_to_secondary_int * mortar_flux
                )
                + accumulation_all
                + subdomain_proj_scalar.cell_prolongation(g_primary)
                * accumulation_primary
                + subdomain_proj_scalar.cell_prolongation(g_frac) * accumulation_fracs
            )

            interface_flow_eq = robin_ad.mortar_scaling * (
                mortar_proj_scalar.primary_to_mortar_avg
                * mpfa_ad.bound_pressure_cell
                * p
                + mortar_proj_scalar.primary_to_mortar_avg
                * mpfa_ad.bound_pressure_face
                * (
                    mortar_proj_scalar.mortar_to_primary_int * mortar_flux
                    + bc_val_scalar
                )
                - mortar_proj_scalar.secondary_to_mortar_avg * p
                + robin_ad.mortar_discr * mortar_flux
            )

            flow_eq = pp.ad.Expression(
                flow_md, dof_manager, "flow on nodes", grid_order=grid_list
            )
            interface_eq = pp.ad.Expression(
                interface_flow_eq,
                dof_manager,
                "flow on interface",
                grid_order=edge_list,
            )

            eq_manager.equations += [
                momentum_eq,
                contact_eq,
                force_balance_eq,
                flow_eq,
                interface_eq,
            ]
            self._eq_manager = eq_manager
Exemplo n.º 19
0
    def set_variables_discretizations_cell_basis(self, gb):
        """
        Assign variables, and set discretizations for the micro gb.

        NOTE: keywords and variable names are hardcoded here. This should be centralized.
        @Eirik we are keeping the same nomenclature, since the gb are different maybe we can
        change it a bit

        Args:
            gb (TYPE): the micro gb.

        Returns:
            None.

        """
        # Use mpfa for the fine-scale problem for now. We may generalize this at some
        # point, but that should be a technical detail.
        fine_scale_dicsr = pp.Mpfa(self.keyword)
        # In 1d, mpfa will end up calling tpfa, so use this directly, in a hope that
        # the reduced amount of boilerplate will save some time.
        fine_scale_dicsr_1d = pp.Tpfa(self.keyword)

        void_discr = pp.EllipticDiscretizationZeroPermeability(self.keyword)

        for g, d in gb:
            d[pp.PRIMARY_VARIABLES] = {
                self.cell_variable: {
                    "cells": 1,
                    "faces": 0
                }
            }
            if g.dim > 1:
                d[pp.DISCRETIZATION] = {
                    self.cell_variable: {
                        self.cell_discr: fine_scale_dicsr
                    }
                }
            else:
                d[pp.DISCRETIZATION] = {
                    self.cell_variable: {
                        self.cell_discr: fine_scale_dicsr_1d
                    }
                }

            d[pp.DISCRETIZATION_MATRICES] = {self.keyword: {}}

        # Loop over the edges in the GridBucket, define primary variables and discretizations
        # NOTE: No need to differ between Mpfa and Tpfa here; their treatment of interface
        # discretizations are the same.
        for e, d in gb.edges():
            g1, g2 = gb.nodes_of_edge(e)

            # Set the mortar variable.
            # NOTE: This is overriden in the case where the higher-dimensional grid is
            # auxiliary, see below.
            d[pp.PRIMARY_VARIABLES] = {self.mortar_variable: {"cells": 1}}

            # The type of lower-dimensional discretization depends on whether this is a
            # (part of a) fracture, or a transition between two line or surface grids.
            if g1.dim == 2 and g2.dim == 2:
                mortar_discr = pp.FluxPressureContinuity(
                    self.keyword, fine_scale_dicsr, fine_scale_dicsr)
            elif hasattr(g1, "is_auxiliary") and g1.is_auxiliary:
                if g2.dim > 1:
                    # This is a connection between a surface and an auxiliary line.
                    # Impose continuity conditions over the line.

                    assert g1.dim == 1  # Cannot imagine this is not True
                    mortar_discr = pp.FluxPressureContinuity(
                        self.keyword, fine_scale_dicsr, void_discr)
                else:
                    # Connection between a 1d line (an interaction region edge) and a
                    # point along that line (could be a coner along the edge.
                    mortar_discr = pp.FluxPressureContinuity(
                        self.keyword, fine_scale_dicsr_1d, void_discr)
            elif hasattr(g2, "is_auxiliary") and g2.is_auxiliary:
                # This is an auxiliary line being cut by a point, which should then
                # be a fracture point. Impose no condition here.
                assert g2.dim == 1
                # No variable for this edge - we have no equation for it.
                d[pp.PRIMARY_VARIABLES] = {}
                continue
            else:
                # Standard coupling, with resistance, for the final case.
                if g1.dim > 1:
                    mortar_discr = pp.RobinCoupling(self.keyword,
                                                    fine_scale_dicsr,
                                                    fine_scale_dicsr)
                elif g1.dim == 1:
                    mortar_discr = pp.RobinCoupling(self.keyword,
                                                    fine_scale_dicsr,
                                                    fine_scale_dicsr_1d)
                else:
                    mortar_discr = pp.RobinCoupling(self.keyword,
                                                    fine_scale_dicsr_1d,
                                                    fine_scale_dicsr_1d)

            d[pp.COUPLING_DISCRETIZATION] = {
                self.mortar_discr: {
                    g1: (self.cell_variable, self.cell_discr),
                    g2: (self.cell_variable, self.cell_discr),
                    e: (self.mortar_variable, mortar_discr),
                }
            }
            d[pp.DISCRETIZATION_MATRICES] = {self.keyword: {}}
Exemplo n.º 20
0
def run_flow(gb,
             node_discretization,
             source_discretization,
             folder,
             is_FV,
             discr_3d=None,
             discr_2d=None):

    if discr_3d is None:
        discr_3d = node_discretization
    if discr_2d is None:
        discr_2d = node_discretization

    kw_t = "transport"  # For storing fluxes

    grid_variable = "pressure"
    mortar_variable = "mortar_flux"

    # Identifier of the discretization operator on each grid
    diffusion_term = "diffusion"
    # Identifier of the discretization operator between grids
    coupling_operator_keyword = "coupling_operator"

    edge_discretization_3d = pp.RobinCoupling("flow", discr_3d, discr_2d)
    edge_discretization_2d = pp.RobinCoupling("flow", discr_2d,
                                              node_discretization)
    edge_discretization = pp.RobinCoupling("flow", node_discretization,
                                           node_discretization)

    # Loop over the nodes in the GridBucket, define primary variables and discretization schemes
    for g, d in gb:
        # Assign primary variables on this grid. It has one degree of freedom per cell.
        if g.dim == 3 and isinstance(discr_3d, pp.MVEM):
            d[pp.PRIMARY_VARIABLES] = {grid_variable: {"cells": 1, "faces": 1}}
        elif g.dim == 2 and isinstance(discr_2d, pp.MVEM):
            d[pp.PRIMARY_VARIABLES] = {grid_variable: {"cells": 1, "faces": 1}}
        else:
            d[pp.PRIMARY_VARIABLES] = {
                grid_variable: {
                    "cells": 1,
                    "faces": int(not is_FV)
                }
            }
        # Assign discretization operator for the variable.
        if g.dim == 3:
            d[pp.DISCRETIZATION] = {
                grid_variable: {
                    diffusion_term: discr_3d,
                }
            }
        elif g.dim == 2:
            d[pp.DISCRETIZATION] = {
                grid_variable: {
                    diffusion_term: discr_2d,
                }
            }
        else:
            d[pp.DISCRETIZATION] = {
                grid_variable: {
                    diffusion_term: node_discretization,
                }
            }
    # Loop over the edges in the GridBucket, define primary variables and discretizations
    for e, d in gb.edges():
        g1, g2 = gb.nodes_of_edge(e)
        # The mortar variable has one degree of freedom per cell in the mortar grid
        d[pp.PRIMARY_VARIABLES] = {mortar_variable: {"cells": 1}}

        # The coupling discretization links an edge discretization with variables
        # and discretization operators on each neighboring grid
        if g2.dim == 3:
            d[pp.COUPLING_DISCRETIZATION] = {
                coupling_operator_keyword: {
                    g1: (grid_variable, diffusion_term),
                    g2: (grid_variable, diffusion_term),
                    e: (mortar_variable, edge_discretization_3d),
                }
            }
        elif g2.dim == 2:
            d[pp.COUPLING_DISCRETIZATION] = {
                coupling_operator_keyword: {
                    g1: (grid_variable, diffusion_term),
                    g2: (grid_variable, diffusion_term),
                    e: (mortar_variable, edge_discretization_2d),
                }
            }

        else:
            d[pp.COUPLING_DISCRETIZATION] = {
                coupling_operator_keyword: {
                    g1: (grid_variable, diffusion_term),
                    g2: (grid_variable, diffusion_term),
                    e: (mortar_variable, edge_discretization),
                }
            }

    logger.info("Assembly and discretization using the discretizer " +
                str(node_discretization))
    tic = time.time()

    assembler = pp.Assembler()

    # Assemble the linear system, using the information stored in the GridBucket
    A, b, block_dof, full_dof = assembler.assemble_matrix_rhs(
        gb, active_variables=[grid_variable, mortar_variable])
    logger.info("Done. Elapsed time: " + str(time.time() - tic))

    logger.info("Linear solver")
    tic = time.time()
    x = sps.linalg.spsolve(A, b)
    logger.info("Done. Elapsed time " + str(time.time() - tic))
    #    if is_FV:
    assembler.distribute_variable(gb, x, block_dof, full_dof)
    if is_FV:
        if isinstance(discr_3d, pp.MVEM):
            g = gb.grids_of_dimension(3)[0]
            d = gb.node_props(g)
            discr_scheme = d[pp.DISCRETIZATION][grid_variable][diffusion_term]
            d["pressure"] = discr_scheme.extract_pressure(
                g, d[grid_variable], d)
        if isinstance(discr_2d, pp.MVEM):
            for g in gb.grids_of_dimension(2):
                d = gb.node_props(g)
                discr_scheme = d[
                    pp.DISCRETIZATION][grid_variable][diffusion_term]
                d["pressure"] = discr_scheme.extract_pressure(
                    g, d[grid_variable], d)


#        pp.fvutils.compute_darcy_flux(gb, lam_name=mortar_variable, keyword_store=kw_t)
    else:
        for g, d in gb:
            discr_scheme = d[pp.DISCRETIZATION][grid_variable][diffusion_term]

            #d[pp.PARAMETERS][kw_t]["darcy_flux"] = discr_scheme.extract_flux(
            #    g, d[grid_variable], d
            #)
            # Note the order: we overwrite d["pressure"] so this has to be done after
            # extracting the flux
            d["pressure"] = discr_scheme.extract_pressure(
                g, d[grid_variable], d)
        for e, d in gb.edges():
            #            g1, g2 = gb.nodes_of_edge(e)
            d[pp.PARAMETERS][kw_t]["darcy_flux"] = d[mortar_variable].copy()
    export_flow(gb, folder)
    #    sps_io.mmwrite(folder + "/matrix.mtx", A)
    return A, b, block_dof, full_dof
Exemplo n.º 21
0
    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."
                )