Пример #1
0
    def discretize(self) -> None:
        """ Discretize all terms
        """
        if not self.assembler:
            self.assembler = pp.Assembler(self.gb)

        self.assembler.discretize()
    def discretize(self) -> None:
        """ Discretize all terms
        """
        if not self.assembler:
            self.assembler = pp.Assembler(self.gb)

        g_max = self.gb.grids_of_dimension(self.Nd)[0]

        logger.info("Discretize")

        # Discretization is a bit cumbersome, as the Biot discetization removes the
        # one-to-one correspondence between discretization objects and blocks in the matrix.
        # First, Discretize with the biot class
        self._discretize_biot()

        # Next, discretize term on the matrix grid not covered by the Biot discretization,
        # i.e. the source term
        # Here, we also discretize the edge terms in the entire gb
        self.assembler.discretize(grid=g_max, term_filter=["source"])

        # Finally, discretize terms on the lower-dimensional grids. This can be done
        # in the traditional way, as there is no Biot discretization here.
        for g, _ in self.gb:
            if g.dim < self.Nd:
                # No need to discretize edges here, this was done above.
                self.assembler.discretize(grid=g, edges=False)
Пример #3
0
def setup_discr_tpfa(gb, key="flow"):
    """ Setup the discretization Tpfa. """
    discr = pp.Tpfa(key)
    p_trace = pp.CellDofFaceDofMap(key)
    interface = pp.FluxPressureContinuity(key, discr, p_trace)

    for g, d in gb:
        if g.dim == gb.dim_max():
            d[pp.PRIMARY_VARIABLES] = {key: {"cells": 1}}
            d[pp.DISCRETIZATION] = {key: {"flux": discr}}
        else:
            d[pp.PRIMARY_VARIABLES] = {key: {"cells": 1}}
            d[pp.DISCRETIZATION] = {key: {"flux": p_trace}}

    for e, d in gb.edges():
        g_slave, g_master = gb.nodes_of_edge(e)
        d[pp.PRIMARY_VARIABLES] = {key: {"cells": 1}}
        d[pp.COUPLING_DISCRETIZATION] = {
            "flux": {
                g_slave: (key, "flux"),
                g_master: (key, "flux"),
                e: (key, interface),
            }
        }

    return pp.Assembler(gb), (discr, p_trace)
    def matrix_rhs(self):

        # empty the matrices
        for g, d in self.gb:
            d[pp.DISCRETIZATION_MATRICES] = {self.model: {}}

        for e, d in self.gb.edges():
            d[pp.DISCRETIZATION_MATRICES] = {self.model: {}}

        # solution of the darcy problem
        self.assembler = pp.Assembler(
            self.gb, active_variables=[self.variable, self.mortar])

        logger.info("Assemble the flow problem")
        block_A, block_b = self.assembler.assemble_matrix_rhs(
            add_matrices=False)

        # unpack the matrices just computed
        coupling_name = self.coupling_name + (
            "_" + self.mortar + "_" + self.variable + "_" + self.variable)
        discr_name = self.discr_name + "_" + self.variable
        mass_name = self.mass_name + "_" + self.variable
        source_name = self.source_name + "_" + self.variable

        # need a sign for the convention of the conservation equation
        M = -block_A[mass_name]
        A = M + block_A[discr_name] + block_A[coupling_name]
        b = block_b[discr_name] + block_b[coupling_name] + block_b[source_name]

        return A, M, b, self.assembler.block_dof, self.assembler.full_dof
Пример #5
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)
Пример #6
0
    def discretize_flow(self):
        """
        Discretize the flow operators
        """
        gb = self.data.gb
        flow_kw = self.data.flow_keyword
        elliptic_disc(gb, flow_kw)
        # Assemble matrices
        assembler = pp.Assembler(gb)

        # Fluid flow. Darcy + mass conservation
        flux = assembler.assemble_operator(flow_kw, "flux")
        bound_flux = assembler.assemble_operator(flow_kw, "bound_flux")
        trace_cell = assembler.assemble_operator(flow_kw,
                                                 "bound_pressure_cell")
        trace_face = assembler.assemble_operator(flow_kw,
                                                 "bound_pressure_face")
        div = assembler.assemble_operator(flow_kw, "div")

        # Assemble discrete parameters and geometric values
        bc_val = assembler.assemble_parameter(flow_kw, "bc_values")
        kn = [
            d[pp.PARAMETERS][flow_kw]["normal_diffusivity"]
            for _, d in gb.edges()
        ]
        kn = np.hstack(kn)

        self.mat[flow_kw] = {
            "flux": flux,
            "bound_flux": bound_flux,
            "trace_cell": trace_cell,
            "trace_face": trace_face,
            "bc_values": bc_val,
            "kn": kn,
        }
Пример #7
0
def main():
    # example in 2d
    file_name = "network.csv"
    write_network(file_name)

    # load the network and split it, we assume 2d
    domain = {"xmin": 0, "xmax": 1, "ymin": 0, "ymax": 1}
    global_network = pp.fracture_importer.network_2d_from_csv(file_name,
                                                              domain=domain)
    global_network = global_network.split_intersections()

    # select a subsample of the fractures
    macro_network, micro_network = split_network(global_network,
                                                 Criterion.every)
    # NOTE: the explicit network is empty so far

    # create the macroscopic grid
    mesh_size = 2
    mesh_kwargs = {
        "mesh_size_frac": mesh_size,
        "mesh_size_min": mesh_size / 20
    }
    gb = macro_network.mesh(mesh_kwargs)

    # plot the suff
    pp.plot_fractures(micro_network.pts, micro_network.edges,
                      micro_network.domain)
    pp.plot_grid(gb, info="c", alpha=0)

    # construct the solver
    tpfa_dfm = Tpfa_DFM()

    # Set data for the upscaling problem
    g = gb.grids_of_dimension(gb.dim_max())[0]
    d = gb.node_props(g)

    # store the
    # EK: We probably need to initialize some of the nested dictionaries
    d[pp.PARAMETERS][tpfa_dfm.keyword][
        tpfa_dfm.network_keyword] = micro_network

    # set parameters and discretization variables
    # EK: This must be done in this run-script - it is not the responsibility of the
    # discretization. We may want to construct a model for this.
    # tpfa_dfm.set_parameters(gb)
    # tpfa_dfm.set_variables_discretizations(gb)

    # discretize with the assembler
    assembler = pp.Assembler(gb)
    assembler.discretize()

    A, b = assembler.assemble_matrix_rhs()

    # Solve and distribute
    x = spsolve(A, b)
    assembler.distribute_variable(x)
Пример #8
0
    def _discretize(self) -> None:
        """Discretize all terms"""

        if not hasattr(self, "assembler"):
            self.assembler = pp.Assembler(self.gb)

        tic = time.time()
        logger.info("Discretize")
        self.assembler.discretize()
        logger.info("Done. Elapsed time {}".format(time.time() - tic))
Пример #9
0
    def discretize(self):
        """ Discretize all terms
        """

        self.assembler = pp.Assembler(self.gb)

        tic = time.time()
        logger.info("Discretize")
        self.assembler.discretize()
        logger.info("Done. Elapsed time {}".format(time.time() - tic))
    def discretize(self):
        """ Discretize the mechanics stress term
        """
        if not hasattr(self, "assembler"):
            self.assembler = pp.Assembler(
                self.gb, active_variables=[self.displacement_variable])

        g_max = self.gb.grids_of_dimension(self.Nd)[0]
        self.assembler.discretize(grid=g_max,
                                  variable_filter=self.displacement_variable)
Пример #11
0
    def discretize_transport(self):
        """
        Discretize the transport operators
        """
        gb = self.problem.gb
        transport_kw = self.problem.transport_keyword
        elliptic_disc(gb, transport_kw, self._use_mpfa)
        upwind_disc(gb, transport_kw)

        # Assemble global matrices
        assembler = pp.Assembler(gb)

        # Transport. Upwind + mass conservation
        diff = assembler.assemble_operator(transport_kw, "flux")
        bound_diff = assembler.assemble_operator(transport_kw, "bound_flux")
        trace_cell = assembler.assemble_operator(transport_kw,
                                                 "bound_pressure_cell")
        trace_face = assembler.assemble_operator(transport_kw,
                                                 "bound_pressure_face")

        pos_cells = assembler.assemble_operator(transport_kw, "pos_cells")
        neg_cells = assembler.assemble_operator(transport_kw, "neg_cells")

        # Finite volume divergence operator
        div = assembler.assemble_operator(transport_kw, "div")

        # Assemble discrete parameters and geometric values
        bc_val = assembler.assemble_parameter(transport_kw, "bc_values")
        bc_sgn = assembler.assemble_parameter(transport_kw, "bc_sgn")
        frac_bc = assembler.assemble_parameter(transport_kw, "frac_bc")

        mass_weight = assembler.assemble_parameter(transport_kw, "mass_weight")
        dn = [
            d[pp.PARAMETERS][transport_kw]["normal_diffusivity"]
            for _, d in gb.edges()
        ]
        if len(dn) > 0:
            dn = np.hstack(dn)
        else:
            dn = np.array([], dtype=float)

        # Store  discretizations
        self.mat[transport_kw] = {
            "flux": diff,
            "bound_flux": bound_diff,
            "trace_cell": trace_cell,
            "trace_face": trace_face,
            "bc_values": bc_val,
            "pos_cells": pos_cells,
            "neg_cells": neg_cells,
            "bc_sgn": bc_sgn,
            "frac_bc": frac_bc,
            "dn": dn,
            "mass_weight": mass_weight * gb.cell_volumes(),
        }
    def discretize(self):
        """ Discretize the flow terms only"""
        if not hasattr(self, "assembler"):
            self.assembler = pp.Assembler(
                self.gb,
                active_variables=[
                    self.scalar_variable, self.mortar_scalar_variable
                ],
            )

        self.assembler.discretize()
Пример #13
0
 def _discretize(self) -> None:
     """Discretize all terms"""
     if not hasattr(self, "dof_manager"):
         self.dof_manager = pp.DofManager(self.gb)
         self.assembler = pp.Assembler(self.gb, self.dof_manager)
     tic = time.time()
     logger.info("Discretize")
     if self._use_ad:
         self._eq_manager.discretize(self.gb)
     else:
         self.assembler.discretize()
     logger.info("Done. Elapsed time {}".format(time.time() - tic))
Пример #14
0
    def set_discr(self):

        for _, d in self.gb:
            d[pp.PRIMARY_VARIABLES].update({self.variable: {"cells": 1}})

        for _, d in self.gb.edges():
            d[pp.PRIMARY_VARIABLES].update({self.mortar_dummy_1: {"cells": 1},
                                            self.mortar_dummy_2: {"cells": 1}})

        # assembler
        variables = [self.variable, self.mortar_dummy_1, self.mortar_dummy_2]
        self.assembler = pp.Assembler(self.gb, active_variables=variables)
    def _discretize(self) -> None:
        """Discretize all terms"""
        if not hasattr(self, "dof_manager"):
            self.dof_manager = pp.DofManager(self.gb)

        if not hasattr(self, "assembler"):
            self.assembler = pp.Assembler(self.gb, self.dof_manager)

        g_max = self.gb.grids_of_dimension(self._Nd)[0]

        tic = time.time()
        logger.info("Discretize")

        if self._use_ad:
            self._eq_manager.discretize(self.gb)
        else:
            # Discretization is a bit cumbersome, as the Biot discetization removes the
            # one-to-one correspondence between discretization objects and blocks in
            # the matrix.
            # First, Discretize with the biot class
            self._discretize_biot()

            # Next, discretize term on the matrix grid not covered by the Biot discretization,
            # i.e. the diffusion, mass and source terms
            filt = pp.assembler_filters.ListFilter(
                grid_list=[g_max], term_list=["source", "mass", "diffusion"]
            )
            self.assembler.discretize(filt=filt)

            # Build a list of all edges, and all couplings
            edge_list: List[
                Union[
                    Tuple[pp.Grid, pp.Grid],
                    Tuple[pp.Grid, pp.Grid, Tuple[pp.Grid, pp.Grid]],
                ]
            ] = []
            for e, _ in self.gb.edges():
                edge_list.append(e)
                edge_list.append((e[0], e[1], e))
            if len(edge_list) > 0:
                filt = pp.assembler_filters.ListFilter(grid_list=edge_list)  # type: ignore
                self.assembler.discretize(filt=filt)

            # Finally, discretize terms on the lower-dimensional grids. This can be done
            # in the traditional way, as there is no Biot discretization here.
            for dim in range(0, self._Nd):
                grid_list = self.gb.grids_of_dimension(dim)
                if len(grid_list) > 0:
                    filt = pp.assembler_filters.ListFilter(grid_list=grid_list)
                    self.assembler.discretize(filt=filt)

        logger.info("Done. Elapsed time {}".format(time.time() - tic))
Пример #16
0
    def set_data(self, data, bc_flag):
        self.data = data
        self.bc_flag = bc_flag
        self.time_step = data["end_time"] / float(data["num_steps"])
        self.all_time = np.arange(self.data["num_steps"]) * self.time_step

        # set the data for the nodes
        self._set_data_nodes()
        # set the data for the edges, if present
        if self.gb.num_graph_edges():
            self._set_data_edges()

        # assembler
        variables = [self.variable, self.mortar]
        self.assembler = pp.Assembler(self.gb, active_variables=variables)
Пример #17
0
    def set_data(self, data, bc_flag):
        """ Set the data of the problem
        """
        self.data = data
        self.bc_flag = bc_flag

        # set the data for the nodes
        self._set_data_nodes()
        # set the data for the edges, if present
        if self.gb.num_graph_edges():
            self._set_data_edges()

        # assembler
        variables = [self.variable, self.mortar]
        self.assembler = pp.Assembler(self.gb, active_variables=variables)
Пример #18
0
    def set_discr(self):

        # set the discretization for the grids
        for _, d in self.gb:
            d[pp.PRIMARY_VARIABLES].update({self.variable: {"cells": 1}})

            diff = self.diff(self.diff_name)
            adv = self.adv(self.adv_name)
            mass = self.mass(self.mass_name)
            source = self.source(self.source_name)
            d[pp.DISCRETIZATION].update({self.variable: {self.diff_name: diff,
                                                         self.adv_name: adv,
                                                         self.mass_name: mass,
                                                         self.source_name: source}})

            d[pp.DISCRETIZATION_MATRICES].update({self.diff_name: {}, self.adv_name: {},
                                                  self.mass_name: {}, self.source_name: {}})

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

            coupling_diff = self.coupling_diff(self.diff_name, self.diff(self.diff_name))
            d[pp.COUPLING_DISCRETIZATION].update({
                self.coupling_diff_name: {
                    g_slave: (self.variable, self.diff_name),
                    g_master: (self.variable, self.diff_name),
                    e: (self.mortar_diff, coupling_diff),
                }
            })

            coupling_adv = self.coupling_adv(self.adv_name)
            d[pp.COUPLING_DISCRETIZATION].update({
                self.coupling_adv_name: {
                    g_slave: (self.variable, self.adv_name),
                    g_master: (self.variable, self.adv_name),
                    e: (self.mortar_adv, coupling_adv),
                }
            })

            d[pp.DISCRETIZATION_MATRICES].update({self.diff_name: {}, self.adv_name: {},
                                                  self.mass_name: {}, self.source_name: {}})

        # assembler
        variables = [self.variable, self.mortar_diff, self.mortar_adv]
        self.assembler = pp.Assembler(self.gb, active_variables=variables)
Пример #19
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
Пример #20
0
    def discretize(self) -> None:
        """ Discretize all terms
        """
        if not hasattr(self, "assembler"):
            self.assembler = pp.Assembler(self.gb)

        tic = time.time()
        logger.info("Discretize")

        # Discretization is a bit cumbersome, as the Biot discetization removes the
        # one-to-one correspondence between discretization objects and blocks in the matrix.
        # First, Discretize with the biot class
        self.discretize_biot()
        self.copy_biot_discretizations()

        # Next, discretize term on the matrix grid not covered by the Biot discretization,
        # i.e. the source term
        pressure_terms = ["source"]
        self.assembler.discretize(
            grid=self._nd_grid(),
            term_filter=pressure_terms,
            variable_filter=self.scalar_variable,
        )
        # Then the temperature discretizations
        temperature_terms = [
            "source", "diffusion", "mass", self.advection_term
        ]
        self.assembler.discretize(
            grid=self._nd_grid(),
            term_filter=temperature_terms,
            variable_filter=self.temperature_variable,
        )

        coupling_terms = [self.s2t_coupling_term, self.t2s_coupling_term]
        self.assembler.discretize(
            grid=self._nd_grid(),
            term_filter=coupling_terms,
            variable_filter=[self.temperature_variable, self.scalar_variable],
        )

        # Finally, discretize terms on the lower-dimensional grids. This can be done
        # in the traditional way, as there is no Biot discretization here.
        for g, _ in self.gb:
            if g.dim < self.Nd:
                self.assembler.discretize(grid=g)

        logger.info("Done. Elapsed time {}".format(time.time() - tic))
Пример #21
0
    def matrix_rhs(self):

        # set the discretization for the grids
        for g, d in self.gb:
            discr = self.discr(self.model)
            source = self.source(self.model)

            d[pp.PRIMARY_VARIABLES] = {self.variable: {"cells": 1, "faces": 1}}
            d[pp.DISCRETIZATION] = {
                self.variable: {
                    self.discr_name: discr,
                    self.source_name: source
                }
            }
            d[pp.DISCRETIZATION_MATRICES] = {self.model: {}}

        # define the interface terms to couple the grids
        for e, d in self.gb.edges():
            g_slave, g_master = self.gb.nodes_of_edge(e)

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

            d[pp.PRIMARY_VARIABLES] = {self.mortar: {"cells": 1}}
            d[pp.COUPLING_DISCRETIZATION] = {
                self.coupling_name: {
                    g_slave: (self.variable, self.discr_name),
                    g_master: (self.variable, self.discr_name),
                    e: (self.mortar, coupling),
                }
            }
            d[pp.DISCRETIZATION_MATRICES] = {self.model: {}}

        # assembler
        self.assembler = pp.Assembler(self.gb)
        self.assembler.discretize()
        return self.assembler.assemble_matrix_rhs()
Пример #22
0
    def set_discr(self):

        # set the discretization for the grids
        for _, d in self.gb:
            d[pp.PRIMARY_VARIABLES].update(
                {self.variable: {
                    "cells": 1,
                    "faces": 1
                }})

            discr = self.discr(self.model)
            source = self.source(self.model)
            d[pp.DISCRETIZATION].update({
                self.variable: {
                    self.discr_name: discr,
                    self.source_name: source
                }
            })

            d[pp.DISCRETIZATION_MATRICES].update({self.model: {}})

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

            coupling = self.coupling(self.model, self.discr(self.model))
            d[pp.COUPLING_DISCRETIZATION].update({
                self.coupling_name: {
                    g_slave: (self.variable, self.discr_name),
                    g_master: (self.variable, self.discr_name),
                    e: (self.mortar, coupling),
                }
            })

            d[pp.DISCRETIZATION_MATRICES].update({self.model: {}})

        # assembler
        variables = [self.variable, self.mortar]
        self.assembler = pp.Assembler(self.gb, active_variables=variables)
Пример #23
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:]
Пример #24
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)
def run_mechanics(setup):
    """
    Function for solving linear elasticity with a non-linear Coulomb contact.
    
    There are some assumtions on the variable and discretization names given to the
    grid bucket:
        'u': The displacement variable
        'lam': The mortar variable
        'mpsa': The mpsa discretization
    
    In addition to the standard parameters for mpsa we also require the following
    under the contact mechanics keyword (returned from setup.set_parameters):
        'friction_coeff' : The coefficient of friction
        'c' : The numerical parameter in the non-linear complementary function.

    Arguments:
        setup: A setup class with methods:
                set_parameters(g, data_node, mg, data_edge): assigns data to grid bucket.
                    Returns the keyword for the linear elastic parameters and a keyword
                    for the contact mechanics parameters.
                create_grid(): Create and return the grid bucket
                initial_condition(): Returns initial guess for 'u' and 'lam'.
            and attributes:
                out_name(): returns a string. The data from the simulation will be
                written to the file 'res_data/' + setup.out_name and the vtk files to
                'res_plot/' + setup.out_name
    """
    gb = setup.create_grid()
    # Extract the grids we use
    dim = gb.dim_max()
    g = gb.grids_of_dimension(dim)[0]
    data_node = gb.node_props(g)
    data_edge = gb.edge_props((g, g))
    mg = data_edge['mortar_grid']

    # set parameters
    key, key_m = setup.set_parameters(g, data_node, mg, data_edge)

    # Get shortcut to some of the parameters
    F = data_edge[pp.PARAMETERS][key_m]['friction_coeff']
    c_num = data_edge[pp.PARAMETERS][key_m]['c']

    # Define rotations
    M_inv, nc = utils.normal_tangential_rotations(g, mg)
    # Set up assembler and discretize
    mpsa = data_node[pp.DISCRETIZATION]['u']['mpsa']
    mpsa.discretize(g, data_node)
    assembler = pp.Assembler()

    # prepare for iteration
    u0, uc, Tc = setup.initial_condition(g, mg, nc)
    T_contact = []
    u_contact = []
    save_sliding = []

    errors = []

    counter_newton = 0
    converged_newton = False
    max_newton = 15

    while counter_newton <= max_newton and not converged_newton:
        print('Newton iteration number: ', counter_newton, '/', max_newton)
        counter_newton += 1
        # Calculate numerical friction bound used in  the contact condition
        bf = F * np.clip(np.sum(nc * (-Tc + c_num * uc), axis=0), 0, np.inf)
        # Find the robin weight
        mortar_weight, robin_weight, rhs = contact_coulomb(
            Tc, uc, F, bf, c_num, c_num, M_inv)
        data_edge[pp.PARAMETERS][key_m]['robin_weight'] = robin_weight
        data_edge[pp.PARAMETERS][key_m]['mortar_weight'] = mortar_weight
        data_edge[pp.PARAMETERS][key_m]['robin_rhs'] = rhs

        # Re-discretize and solve
        A, b, block_dof, full_dof = assembler.assemble_matrix_rhs(gb)

        if gb.num_cells() > 4000:
            sol = solvers.amg(gb, A, b)
        else:
            sol = sps.linalg.spsolve(A, b)
        # Split solution into displacement variable and mortar variable
        assembler.distribute_variable(gb, sol, block_dof, full_dof)
        u = data_node['u'].reshape((g.dim, -1), order='F')
        Tc = data_edge['lam'].reshape((g.dim, -1), order='F')

        # Reconstruct the displacement on the fractures
        uc = reconstruct_mortar_displacement(u, Tc, g, mg, data_node,
                                             data_edge, key, key_m)

        # Calculate the error
        if np.sum((u - u0)**2 * g.cell_volumes) / np.sum(
                u**2 * g.cell_volumes) < 1e-10:
            converged_newton = True

        print('error: ', np.sum((u - u0)**2) / np.sum(u**2))
        errors.append(np.sum((u - u0)**2) / np.sum(u**2))

        # Prepare for next iteration
        u0 = u
        T_contact.append(Tc)
        u_contact.append(uc)

    # Store vtk of solution:
    viz.export_nodal_values(g, mg, data_node, u, None, Tc, key, key_m,
                            setup.out_name, "res_plot")
    if dim == 3:
        m_exp_name = setup.out_name + "_mortar_grid"
        viz.export_mortar_grid(g, mg, data_edge, uc, Tc, key, key_m,
                               m_exp_name, "res_plot")
def run_biot(setup):
    """
    Function for solving the time dependent Biot equations with a non-linear Coulomb
    contact condition on the fractures.
    
    There are some assumtions on the variable and discretization names given to the
    grid bucket:
        'u': The displacement variable
        'p': Fluid pressure variable
        'lam': The mortar variable
    Furthermore, the parameter keyword from the elasticity is assumed the same as the
    parameter keyword from the contact condition.

    In addition to the standard parameters for Biot we also require the following
    under the mechanics keyword (returned from setup.set_parameters):
        'friction_coeff' : The coefficient of friction
        'c' : The numerical parameter in the non-linear complementary function.
    and for the parameters under the fluid flow keyword:
        'time_step': time step of implicit Euler.

    Arguments:
        setup: A setup class with methods:
                set_parameters(g, data_node, mg, data_edge): assigns data to grid bucket.
                    Returns the keyword for the linear elastic parameters and a keyword
                    for the contact mechanics parameters.
                create_grid(): Create and return the grid bucket
                initial_condition(): Returns initial guess for 'u' and 'lam'.
            and attributes:
                out_name(): returns a string. The data from the simulation will be
                    written to the file 'res_data/' + setup.out_name and the vtk files
                    to 'res_plot/' + setup.out_name
                end_time: End time time of simulation.
    """
    gb = setup.create_grid()
    # Extract the grids we use
    dim = gb.dim_max()
    g = gb.grids_of_dimension(dim)[0]
    data_node = gb.node_props(g)
    data_edge = gb.edge_props((g, g))
    mg = data_edge['mortar_grid']

    # set parameters
    key_m, key_f = setup.set_parameters(g, data_node, mg, data_edge)
    # Short hand for some parameters
    F = data_edge[pp.PARAMETERS][key_m]['friction_coeff']
    c_num = data_edge[pp.PARAMETERS][key_m]['c']
    dt = data_node[pp.PARAMETERS][key_f]["time_step"]

    # Define rotations
    M_inv, nc = utils.normal_tangential_rotations(g, mg)

    # Set up assembler and get initial condition
    assembler = pp.Assembler()

    u0 = data_node[pp.PARAMETERS][key_m]['state']['displacement'].reshape(
        (g.dim, -1), order='F')
    p0 = data_node[pp.PARAMETERS][key_f]['state']
    Tc = data_edge[pp.PARAMETERS][key_m]['state'].reshape((g.dim, -1),
                                                          order='F')

    # Reconstruct displacement jump on fractures
    uc = reconstruct_mortar_displacement(u0,
                                         Tc,
                                         g,
                                         mg,
                                         data_node,
                                         data_edge,
                                         key_m,
                                         key_f,
                                         pressure=p0)
    uc0 = uc.copy()

    # Define function for splitting the global solution vector
    def split_solution_vector(x, block_dof, full_dof):
        # full_dof contains the number of dofs per block. To get a global ordering, use
        global_dof = np.r_[0, np.cumsum(full_dof)]

        # split global variable
        block_u = block_dof[(g, "u")]
        block_p = block_dof[(g, "p")]
        block_lam = block_dof[((g, g), "lam_u")]
        # Get the global displacement and pressure dofs
        u_dof = np.arange(global_dof[block_u], global_dof[block_u + 1])
        p_dof = np.arange(global_dof[block_p], global_dof[block_p + 1])
        lam_dof = np.arange(global_dof[block_lam], global_dof[block_lam + 1])

        # Plot pressure and displacements
        u = x[u_dof].reshape((g.dim, -1), order="F")
        p = x[p_dof]
        lam = x[lam_dof].reshape((g.dim, -1), order="F")
        return u, p, lam

    # prepare for time loop
    sol = None  # inital guess for Newton solver.
    T_contact = []
    u_contact = []
    save_sliding = []
    errors = []
    exporter = pp.Exporter(g, setup.out_name, 'res_plot')
    t = 0.0
    T = setup.end_time
    k = 0
    times = []
    newton_it = 0
    while t < T:
        t += dt
        k += 1
        print('Time step: ', k, '/', int(np.ceil(T / dt)))

        times.append(t)
        # Prepare for Newton
        counter_newton = 0
        converged_newton = False
        max_newton = 12
        newton_errors = []
        while counter_newton <= max_newton and not converged_newton:
            print('Newton iteration number: ', counter_newton, '/', max_newton)
            counter_newton += 1
            bf = F * np.clip(np.sum(nc *
                                    (-Tc + c_num * uc), axis=0), 0, np.inf)
            ducdt = (uc - uc0) / dt
            # Find the robin weight
            mortar_weight, robin_weight, rhs = contact_coulomb(
                Tc, ducdt, F, bf, c_num, c_num, M_inv)
            rhs = rhs.reshape((g.dim, -1), order='F')
            for i in range(mg.num_cells):
                robin_weight[i] = robin_weight[i] / dt
                rhs[:, i] = rhs[:, i] + robin_weight[i].dot(uc0[:, i])
            data_edge[pp.PARAMETERS][key_m]['robin_weight'] = robin_weight
            data_edge[pp.PARAMETERS][key_m]['mortar_weight'] = mortar_weight
            data_edge[pp.PARAMETERS][key_m]['robin_rhs'] = rhs.ravel('F')

            # Re-discretize and solve
            A, b, block_dof, full_dof = assembler.assemble_matrix_rhs(gb)
            print('max A: ', np.max(np.abs(A)))
            print('max A sum: ', np.max(np.sum(np.abs(A), axis=1)))
            print('min A sum: ', np.min(np.sum(np.abs(A), axis=1)))

            if A.shape[0] > 10000:
                sol, msg, err = solvers.fixed_stress(gb, A, b, block_dof,
                                                     full_dof, sol)
                if msg != 0:
                    # did not converge.
                    print('Iterative solver failed.')
            else:
                sol = sps.linalg.spsolve(A, b)

            # Split solution in the different variables
            u, p, Tc = split_solution_vector(sol, block_dof, full_dof)

            # Reconstruct displacement jump on mortar boundary
            uc = reconstruct_mortar_displacement(u,
                                                 Tc,
                                                 g,
                                                 mg,
                                                 data_node,
                                                 data_edge,
                                                 key_m,
                                                 key_f,
                                                 pressure=p)
            # Calculate the error
            if np.sum((u - u0)**2 * g.cell_volumes) / np.sum(
                    u**2 * g.cell_volumes) < 1e-10:
                converged_newton = True

            print('error: ', np.sum((u - u0)**2) / np.sum(u**2))
            newton_errors.append(np.sum((u - u0)**2) / np.sum(u**2))
            # Prepare for nect newton iteration
            u0 = u
            newton_it += 1

        errors.append(newton_errors)
        # Prepare for next time step
        uc0 = uc.copy()
        T_contact.append(Tc)
        u_contact.append(uc)

        mech_bc = data_node[pp.PARAMETERS][key_m]['bc_values'].copy()
        data_node[pp.PARAMETERS][key_m]['bc_values'] = setup.bc_values(
            g, t + dt, key_m)
        data_node[pp.PARAMETERS][key_f]['bc_values'] = setup.bc_values(
            g, t + dt, key_f)

        data_node[pp.PARAMETERS][key_m]["state"]["displacement"] = u.ravel(
            'F').copy()
        data_node[pp.PARAMETERS][key_m]["state"]["bc_values"] = mech_bc
        data_node[pp.PARAMETERS][key_f]["state"] = p.copy()
        data_edge[pp.PARAMETERS][key_m]["state"] = Tc.ravel('F').copy()

        if g.dim == 2:
            u_exp = np.vstack((u, np.zeros(u.shape[1])))
        elif g.dim == 3:
            u_exp = u.copy()
            m_exp_name = setup.out_name + "_mortar_grid"
            viz.export_mortar_grid(g,
                                   mg,
                                   data_edge,
                                   uc,
                                   Tc,
                                   key_m,
                                   key_m,
                                   m_exp_name,
                                   "res_plot",
                                   time_step=k)

        exporter.write_vtk({"u": u_exp, 'p': p}, time_step=k)

    exporter.write_pvd(np.array(times))
Пример #27
0
    def _discretize(self) -> None:
        """Discretize all terms"""
        if not hasattr(self, "dof_manager"):
            self.dof_manager = pp.DofManager(self.gb)
        if not hasattr(self, "assembler"):
            self.assembler = pp.Assembler(self.gb, self.dof_manager)

        tic = time.time()
        logger.info("Discretize")

        # Discretization is a bit cumbersome, as the Biot discetization removes the
        # one-to-one correspondence between discretization objects and blocks in the matrix.
        # First, Discretize with the biot class
        self._discretize_biot()
        self._copy_biot_discretizations()

        # Next, discretize term on the matrix grid not covered by the Biot discretization,
        # i.e. the source, diffusion and mass terms
        filt = pp.assembler_filters.ListFilter(
            grid_list=[self._nd_grid()],
            variable_list=[self.scalar_variable],
            term_list=["source", "diffusion", "mass"],
        )
        self.assembler.discretize(filt=filt)

        # Then the temperature discretizations
        temperature_terms = ["source", "diffusion", "mass", self.advection_term]
        filt = pp.assembler_filters.ListFilter(
            grid_list=[self._nd_grid()],
            variable_list=[self.temperature_variable],
            term_list=temperature_terms,
        )
        self.assembler.discretize(filt=filt)

        # Coupling terms
        coupling_terms = [self.s2t_coupling_term, self.t2s_coupling_term]
        filt = pp.assembler_filters.ListFilter(
            grid_list=[self._nd_grid()],
            variable_list=[self.temperature_variable, self.scalar_variable],
            term_list=coupling_terms,
        )
        self.assembler.discretize(filt=filt)

        # Build a list of all edges, and all couplings
        edge_list: List[
            Union[
                Tuple[pp.Grid, pp.Grid],
                Tuple[pp.Grid, pp.Grid, Tuple[pp.Grid, pp.Grid]],
            ]
        ] = []
        for e, _ in self.gb.edges():
            edge_list.append(e)
            edge_list.append((e[0], e[1], e))
        if len(edge_list) > 0:
            filt = pp.assembler_filters.ListFilter(grid_list=edge_list)  # type: ignore
            self.assembler.discretize(filt=filt)

        # Finally, discretize terms on the lower-dimensional grids. This can be done
        # in the traditional way, as there is no Biot discretization here.
        for dim in range(0, self._Nd):
            grid_list = self.gb.grids_of_dimension(dim)
            if len(grid_list) > 0:
                filt = pp.assembler_filters.ListFilter(grid_list=grid_list)
                self.assembler.discretize(filt=filt)

        logger.info("Done. Elapsed time {}".format(time.time() - tic))
Пример #28
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
Пример #29
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"])
Пример #30
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)