예제 #1
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))
    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))
    else:
        d[pp.PARAMETERS][param_key]["bc_values"] = bc_values
        d[pp.PARAMETERS][param_key]["source"] = source_term


#%% Set initial states
for g, d in gb:
    cc = g.cell_centers
    pp.set_state(d)
    pp.set_iterate(d)
    d[pp.STATE][pressure_var] = p_ex(cc[0], cc[1], time * np.ones_like(cc[0]))
    d[pp.STATE][pp.ITERATE][pressure_var] = d[pp.STATE][pressure_var].copy()

#%% AD variables and manager
grid_list = [g for g, _ in gb]
dof_manager = pp.DofManager(gb)
equation_manager = pp.ad.EquationManager(gb, dof_manager)
p = equation_manager.merge_variables([(g, pressure_var) for g in grid_list])
p_m = p.previous_iteration()
p_n = p.previous_timestep()

#%% We let the density to be a non-linear function of the pressure
def rho(p):
    if isinstance(p, pp.ad.Ad_array):
        return rho_ref * pp.ad.exp(c * (p - p_ref))
    else:
        return rho_ref * np.exp(c * (p - p_ref))

rho_ad = pp.ad.Function(rho, name="density")

#%% Initialize exporter
예제 #4
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))
예제 #5
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
예제 #6
0
def test_ad_variable_vrappers():
    fracs = [np.array([[0, 2], [1, 1]]), np.array([[1, 1], [0, 2]])]
    gb = pp.meshing.cart_grid(fracs, [2, 2])

    state_map = {}
    iterate_map = {}

    state_map_2, iterate_map_2 = {}, {}

    var = 'foo'
    var2 = 'bar'

    mortar_var = 'mv'

    def _compare_ad_objects(a, b):
        va, ja = a.val, a.jac
        vb, jb = b.val, b.jac

        assert np.allclose(va, vb)
        assert ja.shape == jb.shape
        d = ja - jb
        if d.data.size > 0:
            assert np.max(np.abs(d.data)) < 1e-10

    for g, d in gb:
        if g.dim == 1:
            num_dofs = 2
        else:
            num_dofs = 1

        d[pp.PRIMARY_VARIABLES] = {var: {'cells': num_dofs}}

        val_state = np.random.rand(g.num_cells * num_dofs)
        val_iterate = np.random.rand(g.num_cells * num_dofs)

        d[pp.STATE] = {var: val_state, pp.ITERATE: {var: val_iterate}}
        state_map[g] = val_state
        iterate_map[g] = val_iterate

        # Add a second variable to the 2d grid, just for the fun of it
        if g.dim == 2:
            d[pp.PRIMARY_VARIABLES][var2] = {'cells': 1}
            val_state = np.random.rand(g.num_cells)
            val_iterate = np.random.rand(g.num_cells)
            d[pp.STATE][var2] = val_state
            d[pp.STATE][pp.ITERATE][var2] = val_iterate
            state_map_2[g] = val_state
            iterate_map_2[g] = val_iterate

    for e, d in gb.edges():
        mg = d['mortar_grid']
        if mg.dim == 1:
            num_dofs = 2
        else:
            num_dofs = 1

        d[pp.PRIMARY_VARIABLES] = {mortar_var: {'cells': num_dofs}}

        val_state = np.random.rand(mg.num_cells * num_dofs)
        val_iterate = np.random.rand(mg.num_cells * num_dofs)

        d[pp.STATE] = {
            mortar_var: val_state,
            pp.ITERATE: {
                mortar_var: val_iterate
            }
        }
        state_map[e] = val_state
        iterate_map[e] = val_iterate

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

    # Manually assemble state and iterate
    true_state = np.zeros(dof_manager.num_dofs())
    true_iterate = np.zeros(dof_manager.num_dofs())

    # Also a state array that differs from the stored iterates
    double_iterate = np.zeros(dof_manager.num_dofs())

    for (g, v) in dof_manager.block_dof:
        inds = dof_manager.dof_ind(g, v)
        if v == var2:
            true_state[inds] = state_map_2[g]
            true_iterate[inds] = iterate_map_2[g]
            double_iterate[inds] = 2 * iterate_map_2[g]
        else:
            true_state[inds] = state_map[g]
            true_iterate[inds] = iterate_map[g]
            double_iterate[inds] = 2 * iterate_map[g]

    grid_list = [
        gb.grids_of_dimension(2)[0], *gb.grids_of_dimension(1),
        gb.grids_of_dimension(0)[0]
    ]

    # Generate merged variables via the EquationManager.
    var_ad = eq_manager.merge_variables([(g, var) for g in grid_list])

    # Check equivalence between the two approaches to generation.
    eq_1 = pp.ad.Expression(var_ad, dof_manager)

    # Check that the state is correctly evaluated.
    inds_var = np.hstack([dof_manager.dof_ind(g, var) for g in grid_list])
    assert np.allclose(true_iterate[inds_var],
                       eq_1.to_ad(gb, true_iterate).val)

    # Check evaluation when no state is passed to the parser, and information must
    # instead be glued together from the GridBucket
    assert np.allclose(true_iterate[inds_var], eq_1.to_ad(gb).val)

    # Evaluate the equation using the double iterate
    assert np.allclose(2 * true_iterate[inds_var],
                       eq_1.to_ad(gb, double_iterate).val)

    # Represent the variable on the previous time step. This should be a numpy array
    prev_var_ad = var_ad.previous_timestep()
    eq_prev = pp.ad.Expression(prev_var_ad, dof_manager)
    prev_evaluated = eq_prev.to_ad(gb)
    assert isinstance(prev_evaluated, np.ndarray)
    assert np.allclose(true_state[inds_var], prev_evaluated)

    # Also check that state values given to the ad parser are ignored for previous
    # values
    assert np.allclose(prev_evaluated, eq_prev.to_ad(gb, double_iterate))

    ## Next, test edge variables. This should be much the same as the grid variables,
    # so the testing is less thorough.
    # Form an edge variable, evaluate this
    edge_list = [e for e, _ in gb.edges()]
    var_edge = eq_manager.merge_variables([(e, mortar_var) for e in edge_list])

    eq_2 = pp.ad.Expression(var_edge, dof_manager)
    edge_inds = np.hstack(
        [dof_manager.dof_ind(e, mortar_var) for e in edge_list])
    assert np.allclose(true_iterate[edge_inds],
                       eq_2.to_ad(gb, true_iterate).val)

    # Finally, test a single variable; everything should work then as well
    g = gb.grids_of_dimension(2)[0]
    v1 = eq_manager.variable(g, var)
    v2 = eq_manager.variable(g, var2)

    eq_3 = pp.ad.Expression(v1, dof_manager)
    eq_4 = pp.ad.Expression(v2, dof_manager)

    ind1 = dof_manager.dof_ind(g, var)
    ind2 = dof_manager.dof_ind(g, var2)

    assert np.allclose(true_iterate[ind1], eq_3.to_ad(gb, true_iterate).val)
    assert np.allclose(true_iterate[ind2], eq_4.to_ad(gb, true_iterate).val)

    v1_prev = v1.previous_timestep()
    eq_5 = pp.ad.Expression(v1_prev, dof_manager)
    assert np.allclose(true_state[ind1], eq_5.to_ad(gb, true_iterate))
    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
예제 #8
0
edge_list = [e for e, _ in gb.edges()]

# The divergence
div = pp.ad.Divergence(grid_list)

# projection between subdomains and mortar grids
if len(edge_list) > 0:
    mortar_projection = pp.ad.MortarProjections(gb=gb, nd=1)
# end if

# Boundary condionts
bound_ad = pp.ad.BoundaryCondition(kw_f, grids=grid_list)

#%% Set an dof and equation manager to keep track of the equations

dof_manager = pp.DofManager(gb)
equation_manager = pp.ad.EquationManager(gb, dof_manager)

p = equation_manager.merge_variables([(g, grid_variable) for g in grid_list])

if len(edge_list) > 0:
    lam = equation_manager.merge_variables([(e, mortar_variable)
                                            for e in edge_list])
# end if

#%% Discretize the flow problem

# AD version of mpfa
mpfa = pp.ad.MpfaAd(kw_f, grids=grid_list)

interior_flux = mpfa.flux * p  # the flux on the subdomains
예제 #9
0
    def _verify(self, gb, split_faces):
        # Common function for all tests.
        # Propagate fracture, verify that variables are mapped correctly, and that
        # new variables are added with correct values

        # Individual grids
        g_2d = gb.grids_of_dimension(2)[0]
        g_1d = gb.grids_of_dimension(1)

        # Model used for fracture propagation
        model = MockPropagationModel({})
        model.gb = gb

        # Cell variable in 2d. Should stay constant throughout the process.
        cell_val_2d = np.random.rand(g_2d.num_cells)

        ## Initialize variables for all grids
        # Cell variable in 1d. Sholud be expanded, but initial size is num_vars_2d
        var_sz_1d = 2
        # 1d variables defined as a dict, indexed on the grid
        cell_val_1d = {g: np.random.rand(var_sz_1d) for g in g_1d}

        var_sz_mortar = 3
        cell_val_mortar = {g: np.random.rand(var_sz_mortar * 2) for g in g_1d}

        # Define variables on all grids.
        # Initialize the state by the known variable, and the iterate as twice that value
        # (mostly a why not)
        d = gb.node_props(g_2d)
        d[pp.PRIMARY_VARIABLES] = {self.cv2: {"cells": 1}}
        d[pp.STATE] = {
            self.cv2: cell_val_2d,
            pp.ITERATE: {
                self.cv2: np.array(2 * cell_val_2d)
            },
        }

        for g in g_1d:
            d = gb.node_props(g)
            d[pp.PRIMARY_VARIABLES] = {self.cv1: {"cells": var_sz_1d}}
            d[pp.STATE] = {
                self.cv1: cell_val_1d[g],
                pp.ITERATE: {
                    self.cv1: np.array(2 * cell_val_1d[g])
                },
            }

            d = gb.edge_props((g_2d, g))
            d[pp.PRIMARY_VARIABLES] = {self.mv: {"cells": var_sz_mortar}}
            d[pp.STATE] = {
                self.mv: cell_val_mortar[g],
                pp.ITERATE: {
                    self.mv: np.array(2 * cell_val_mortar[g])
                },
            }

        # Define assembler, thereby a dof ordering
        dof_manager = pp.DofManager(gb)
        assembler = pp.Assembler(gb, dof_manager)
        model.assembler = assembler

        # Define and initialize a state vector
        x = np.zeros(dof_manager.full_dof.sum())
        x[dof_manager.dof_ind(g_2d, self.cv2)] = cell_val_2d
        for g in g_1d:
            x[dof_manager.dof_ind(g, self.cv1)] = cell_val_1d[g]
            x[dof_manager.dof_ind((g_2d, g), self.mv)] = cell_val_mortar[g]

        # Keep track of the previous values for each grid.
        # Not needed in 2d, where no updates are expected
        val_1d_prev = {g: cell_val_1d[g] for g in g_1d}
        val_mortar_prev = {g: cell_val_mortar[g] for g in g_1d}
        val_1d_iterate_prev = {g: 2 * cell_val_1d[g] for g in g_1d}
        val_mortar_iterate_prev = {g: 2 * cell_val_mortar[g] for g in g_1d}

        # Loop over all propagation steps
        for i, split in enumerate(split_faces):

            # Propagate the fracture. This will also generate mappings from old to new
            # cells
            pp.propagate_fracture.propagate_fractures(gb, split)

            # Update variables
            x_new = model._map_variables(x)

            # The values of the 2d cell should not change
            self.assertTrue(
                np.all(
                    x_new[dof_manager.dof_ind(g_2d, self.cv2)] == cell_val_2d))
            # Also check that pp.STATE and ITERATE has been correctly updated
            d = gb.node_props(g_2d)
            self.assertTrue(np.all(d[pp.STATE][self.cv2] == cell_val_2d))
            self.assertTrue(
                np.all(d[pp.STATE][pp.ITERATE][self.cv2] == 2 * cell_val_2d))

            # Loop over all 1d grids, check both grid and the associated mortar grid
            for g in g_1d:

                num_new_cells = split[g].size

                # mapped variable
                x_1d = x_new[dof_manager.dof_ind(g, self.cv1)]

                # Extension of the 1d grid. All values should be 42 (see propagation class)
                extended_1d = np.full(var_sz_1d * num_new_cells, 42)

                # True values for 1d (both grid and iterate)
                truth_1d = np.r_[val_1d_prev[g], extended_1d]
                truth_iterate = np.r_[val_1d_iterate_prev[g], extended_1d]
                self.assertTrue(np.allclose(x_1d, truth_1d))

                # Also check that pp.STATE and ITERATE has been correctly updated
                d = gb.node_props(g)
                self.assertTrue(np.all(d[pp.STATE][self.cv1] == truth_1d))
                self.assertTrue(
                    np.all(d[pp.STATE][pp.ITERATE][self.cv1] == truth_iterate))

                # The propagation model will assign the value 42 to new cells also in the
                # next step. To be sure values from the first propagation are mapped
                # correctly, we alter the true value (add 1), and update this both in
                # the solution vector, state and previous iterate
                x_new[dof_manager.dof_ind(g,
                                          self.cv1)] = np.r_[val_1d_prev[g],
                                                             extended_1d + 1]
                val_1d_prev[g] = x_new[dof_manager.dof_ind(g, self.cv1)]
                d[pp.STATE][self.cv1] = x_new[dof_manager.dof_ind(g, self.cv1)]
                val_1d_iterate_prev[g] = np.r_[val_1d_iterate_prev[g],
                                               extended_1d + 1]
                d[pp.STATE][pp.ITERATE][self.cv1] = val_1d_iterate_prev[g]

                ## Check mortar grid - see 1d case above for comments
                x_mortar = x_new[dof_manager.dof_ind((g_2d, g), self.mv)]

                sz = int(np.round(val_mortar_prev[g].size / 2))

                # The new mortar cells are appended such that the cells are ordered
                # contiguously on the two sides.
                truth_mortar = np.r_[val_mortar_prev[g][:sz],
                                     np.full(var_sz_mortar *
                                             num_new_cells, 42),
                                     val_mortar_prev[g][sz:2 * sz],
                                     np.full(var_sz_mortar *
                                             num_new_cells, 42), ]
                truth_iterate = np.r_[val_mortar_iterate_prev[g][:sz],
                                      np.full(var_sz_mortar *
                                              num_new_cells, 42),
                                      val_mortar_iterate_prev[g][sz:2 * sz],
                                      np.full(var_sz_mortar *
                                              num_new_cells, 42), ]
                d = gb.edge_props((g_2d, g))

                self.assertTrue(np.all(x_mortar == truth_mortar))
                self.assertTrue(np.all(d[pp.STATE][self.mv] == truth_mortar))
                self.assertTrue(
                    np.all(d[pp.STATE][pp.ITERATE][self.mv] == truth_iterate))

                x_new[dof_manager.dof_ind(
                    (g_2d, g),
                    self.mv)] = np.r_[val_mortar_prev[g][:sz],
                                      np.full(var_sz_mortar *
                                              num_new_cells, 43),
                                      val_mortar_prev[g][sz:2 * sz],
                                      np.full(var_sz_mortar *
                                              num_new_cells, 43), ]
                val_mortar_prev[g] = x_new[dof_manager.dof_ind((g_2d, g),
                                                               self.mv)]
                val_mortar_iterate_prev[g] = np.r_[
                    val_mortar_iterate_prev[g][:sz],
                    np.full(var_sz_mortar * num_new_cells, 43),
                    val_mortar_iterate_prev[g][sz:2 * sz],
                    np.full(var_sz_mortar * num_new_cells, 43), ]
                d[pp.STATE][self.mv] = val_mortar_prev[g]
                d[pp.STATE][pp.ITERATE][self.mv] = val_mortar_iterate_prev[g]

                x = x_new
예제 #10
0
    def _assign_discretizations(self) -> None:
        """
        Assign discretizations to the nodes and edges of the grid bucket.

        """
        # For the Nd domain we solve linear elasticity with mpsa.
        Nd = self._Nd
        gb = self.gb

        if not self._use_ad:
            mpsa = pp.Mpsa(self.mechanics_parameter_key)
            # We need a void discretization for the contact traction variable defined on
            # the fractures.
            empty_discr = pp.VoidDiscretization(self.mechanics_parameter_key,
                                                ndof_cell=Nd)

            for g, d in gb:
                if g.dim == Nd:
                    d[pp.DISCRETIZATION] = {
                        self.displacement_variable: {
                            "mpsa": mpsa
                        }
                    }
                elif g.dim == Nd - 1:
                    d[pp.DISCRETIZATION] = {
                        self.contact_traction_variable: {
                            "empty": empty_discr
                        }
                    }

            # Define the contact condition on the mortar grid
            coloumb = pp.ColoumbContact(self.mechanics_parameter_key, Nd, mpsa)
            contact = pp.PrimalContactCoupling(self.mechanics_parameter_key,
                                               mpsa, coloumb)

            for e, d in gb.edges():
                g_l, g_h = gb.nodes_of_edge(e)
                if g_h.dim == Nd:
                    d[pp.COUPLING_DISCRETIZATION] = {
                        self.friction_coupling_term: {
                            g_h: (self.displacement_variable, "mpsa"),
                            g_l: (self.contact_traction_variable, "empty"),
                            (g_h, g_l):
                            (self.mortar_displacement_variable, contact),
                        }
                    }
        else:

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

            g_primary: pp.Grid = gb.grids_of_dimension(Nd)[0]
            g_frac: List[pp.Grid] = gb.grids_of_dimension(Nd - 1).tolist()
            num_frac_cells = np.sum([g.num_cells for g in g_frac])

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

            edge_list = [(g_primary, g) for g in g_frac]

            mortar_proj = pp.ad.MortarProjections(edges=edge_list,
                                                  gb=gb,
                                                  nd=self._Nd)
            subdomain_proj = pp.ad.SubdomainProjections(gb=gb, nd=self._Nd)
            tangential_proj_lists = [
                gb.node_props(
                    gf,
                    "tangential_normal_projection").project_tangential_normal(
                        gf.num_cells) for gf in g_frac
            ]
            tangential_proj = pp.ad.Matrix(
                sps.block_diag(tangential_proj_lists))

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

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

            u_mortar_prev = u_mortar.previous_timestep()

            # Stress in g_h
            stress = (mpsa_ad.stress * u + mpsa_ad.bound_stress * bc_ad +
                      mpsa_ad.bound_stress *
                      subdomain_proj.face_restriction(g_primary) *
                      mortar_proj.mortar_to_primary_avg * u_mortar)

            # momentum balance equation in g_h
            momentum_eq = pp.ad.Expression(div * stress,
                                           dof_manager,
                                           name="momentuum",
                                           grid_order=[g_primary])

            coloumb_ad = pp.ad.ColoumbContactAd(self.mechanics_parameter_key,
                                                edge_list)
            jump_rotate = (tangential_proj *
                           subdomain_proj.cell_restriction(g_frac) *
                           mortar_proj.mortar_to_secondary_avg *
                           mortar_proj.sign_of_mortar_sides)

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

            # Force balance

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

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

            sign_switcher = pp.ad.Matrix(mat)

            # Contact from primary grid and mortar displacements (via primary grid)
            contact_from_primary_mortar = (
                mortar_proj.primary_to_mortar_int *
                subdomain_proj.face_prolongation(g_primary) * sign_switcher *
                stress)
            contact_from_secondary = (
                mortar_proj.sign_of_mortar_sides *
                mortar_proj.secondary_to_mortar_int *
                subdomain_proj.cell_prolongation(g_frac) *
                tangential_proj.transpose() * contact_force)
            force_balance_eq = pp.ad.Expression(
                contact_from_primary_mortar + contact_from_secondary,
                dof_manager,
                name="force_balance",
                grid_order=edge_list,
            )
            #            eq2 = pp.ad.Equation(contact_from_secondary, dof_manager).to_ad(gb)

            eq_manager.equations += [momentum_eq, contact_eq, force_balance_eq]

            self._eq_manager = eq_manager