Beispiel #1
0
    def __init__(self, keyword, grids):
        if isinstance(grids, list):
            self._grids = grids
        else:
            self._grids = [grids]
        self._discretization = pp.BiotStabilization(keyword)
        self._name = "Biot stabilization term"
        self.keyword = keyword

        self.stabilization: _MergedOperator

        _wrap_discretization(self, self._discretization, grids)
Beispiel #2
0
    def assign_discretizations(self) -> None:
        """
        Assign discretizations to the nodes and edges of the grid bucket.

        Note the attribute subtract_fracture_pressure: Indicates whether or not to
        subtract the fracture pressure contribution for the contact traction. This
        should not be done if the scalar variable is temperature.
        """
        # Call parent class for disrcetizations for the poro-elastic system.
        super().assign_discretizations()

        # What remains is terms related to temperature

        # Shorthand for parameter keywords
        key_t = self.temperature_parameter_key
        key_m = self.mechanics_parameter_key

        # distinguish the coupling terms from temperature terms (grad_p, as opposed to grad_T)
        key_mt = self.mechanics_temperature_parameter_key
        var_s = self.scalar_variable
        var_t = self.temperature_variable
        var_d = self.displacement_variable

        # Define discretization
        # Scalar discretizations (will be assigned to all dimensions)
        diff_disc_t = IE_discretizations.ImplicitMpfa(key_t)
        adv_disc_t = IE_discretizations.ImplicitUpwind(key_t)
        mass_disc_t = IE_discretizations.ImplicitMassMatrix(key_t, var_t)
        source_disc_t = pp.ScalarSource(key_t)

        # Coupling discretizations
        # All dimensions
        div_u_disc_t = pp.DivU(
            key_m,
            key_t,
            variable=var_d,
            mortar_variable=self.mortar_displacement_variable,
        )

        # Nd
        grad_t_disc = pp.GradP(
            key_mt
        )  # pp.GradP(key_m) # Kanskje denne (og andre) b;r erstattes av spesiallagde
        # varianter som henter ut s-varianten og ganger med alpha/beta?

        stabilization_disc_t = pp.BiotStabilization(key_t, var_t)
        s2t_disc = IE_discretizations.ImplicitMassMatrix(
            keyword=self.s2t_parameter_key, variable=var_s)
        t2s_disc = IE_discretizations.ImplicitMassMatrix(
            keyword=self.t2s_parameter_key, variable=var_t)

        # Assign node discretizations
        for g, d in self.gb:
            if g.dim == self.Nd:
                d[pp.DISCRETIZATION].update(
                    {  # advection-diffusion equation for temperature
                        var_t: {
                            "diffusion": diff_disc_t,
                            self.advection_term: adv_disc_t,
                            "mass": mass_disc_t,
                            # Also the stabilization term from Biot
                            "stabilization": stabilization_disc_t,
                            "source": source_disc_t,
                        },
                        # grad T term in the momentuum equation
                        var_d + "_" + var_t: {"grad_p": grad_t_disc},
                        # div u term in the energy equation
                        var_t + "_" + var_d: {"div_u": div_u_disc_t},
                        # Accumulation of pressure in energy equation
                        var_t + "_" + var_s: {self.s2t_coupling_term: s2t_disc},
                        # Accumulation of temperature / energy in pressure equation
                        var_s + "_" + var_t: {self.t2s_coupling_term: t2s_disc},
                    }
                )
            else:
                # Inside fracture network
                d[pp.DISCRETIZATION].update(
                    {  # Advection-diffusion equation, no biot stabilization
                        var_t: {
                            "diffusion": diff_disc_t,
                            self.advection_term: adv_disc_t,
                            "mass": mass_disc_t,
                            "source": source_disc_t,
                        },
                        # Standard coupling terms
                        var_t + "_" + var_s: {self.s2t_coupling_term: s2t_disc},
                        var_s + "_" + var_t: {self.t2s_coupling_term: t2s_disc},
                    }
                )
        # Next, the edges in the gb.
        # Account for the mortar displacements effect on energy balance in the matrix,
        # as an internal boundary contribution
        div_u_coupling_t = pp.DivUCoupling(self.displacement_variable,
                                           div_u_disc_t, div_u_disc_t)
        # Account for the temperature contributions to the force balance on the fracture
        # (see contact_discr).
        # This discretization needs the keyword used to store the grad p discretization:
        grad_t_key = key_mt
        matrix_temperature_to_force_balance = pp.MatrixScalarToForceBalance(
            grad_t_key, mass_disc_t, mass_disc_t)

        # Coupling of advection and diffusion terms in the energy equation
        adv_coupling_t = IE_discretizations.ImplicitUpwindCoupling(key_t)
        diff_coupling_t = pp.RobinCoupling(key_t, diff_disc_t)

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

            d[pp.COUPLING_DISCRETIZATION].update({
                self.advection_coupling_term: {
                    g_h: (self.temperature_variable, self.advection_term),
                    g_l: (self.temperature_variable, self.advection_term),
                    e: (self.mortar_temperature_advection_variable,
                        adv_coupling_t),
                },
                self.temperature_coupling_term: {
                    g_h: (var_t, "diffusion"),
                    g_l: (var_t, "diffusion"),
                    e: (self.mortar_temperature_variable, diff_coupling_t),
                },
            })
            if g_h.dim == self.Nd:
                d[pp.COUPLING_DISCRETIZATION].update({
                    "div_u_coupling_t": {
                        g_h: (
                            var_t,
                            "mass",
                        ),  # This is really the div_u, but this is not implemented
                        g_l: (var_t, "mass"),
                        e:
                        (self.mortar_displacement_variable, div_u_coupling_t),
                    },
                    "matrix_temperature_to_force_balance": {
                        g_h: (var_t, "mass"),
                        g_l: (var_t, "mass"),
                        e: (
                            self.mortar_displacement_variable,
                            matrix_temperature_to_force_balance,
                        ),
                    },
                })
    def _assign_discretizations(self) -> None:
        """
        Assign discretizations to the nodes and edges of the grid bucket.

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

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

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

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

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

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

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

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

        else:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            sign_switcher = pp.ad.Matrix(mat)

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

                mat = []

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

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

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

                p_frac = subdomain_proj_scalar.cell_restriction(g_frac) * p

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

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

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

            dt = self.time_step

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

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

            accumulation_all = mass_ad.mass * (p - p_prev)

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

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

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

            eq_manager.equations += [
                momentum_eq,
                contact_eq,
                force_balance_eq,
                flow_eq,
                interface_eq,
            ]
            self._eq_manager = eq_manager
    def assign_biot_discretizations(self) -> None:
        """ Assign discretizations to the nodes and edges of the grid bucket"""
        # Shorthand
        key_s, key_m = self.scalar_parameter_key, self.mechanics_parameter_key
        var_s, var_m = self.scalar_variable, self.displacement_variable
        var_mortar = self.mortar_displacement_variable
        discr_key, coupling_discr_key = pp.DISCRETIZATION, pp.COUPLING_DISCRETIZATION
        gb = self.gb

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

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

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

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

        # Define edge discretizations for the mortar grid

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

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

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

            if g_h.dim == self.Nd:
                d[coupling_discr_key].update(
                    {
                        "div_u_coupling": {
                            g_h: (
                                var_s,
                                "mass",
                            ),  # This is really the div_u, but this is not implemented
                            g_l: (var_s, "mass"),
                            e: (var_mortar, div_u_coupling),
                        },
                        "matrix_scalar_to_force_balance": {
                            g_h: (var_s, "mass"),
                            g_l: (var_s, "mass"),
                            e: (var_mortar, matrix_scalar_to_force_balance),
                        },
                    }
                )

                if self.subtract_fracture_pressure:
                    d[coupling_discr_key].update(
                        {
                            "fracture_scalar_to_force_balance": {
                                g_h: (var_s, "mass"),
                                g_l: (var_s, "mass"),
                                e: (
                                    var_mortar,
                                    fracture_scalar_to_force_balance,  # noqa
                                ),
                            }
                        }
                    )
Beispiel #5
0
    def test_assemble_biot(self):
        """ Test the assembly of the Biot problem using the assembler.

        The test checks whether the discretization matches that of the Biot class.
        """
        gb = pp.meshing.cart_grid([], [2, 1])
        g = gb.grids_of_dimension(2)[0]
        d = gb.node_props(g)
        # Parameters identified by two keywords
        kw_m = "mechanics"
        kw_f = "flow"
        variable_m = "displacement"
        variable_f = "pressure"
        bound_mech, bound_flow = self.make_boundary_conditions(g)
        initial_disp, initial_pressure, initial_state = self.make_initial_conditions(
            g, x0=0, y0=0, p0=0)
        state = {
            variable_f: initial_pressure,
            variable_m: initial_disp,
            kw_m: {
                "bc_values": np.zeros(g.num_faces * g.dim)
            },
        }
        parameters_m = {"bc": bound_mech, "biot_alpha": 1}

        parameters_f = {"bc": bound_flow, "biot_alpha": 1}
        pp.initialize_default_data(g, d, kw_m, parameters_m)
        pp.initialize_default_data(g, d, kw_f, parameters_f)
        pp.set_state(d, state)
        # Discretize the mechanics related terms using the Biot class
        biot_discretizer = pp.Biot()
        biot_discretizer._discretize_mech(g, d)

        # Set up the structure for the assembler. First define variables and equation
        # term names.
        v_0 = variable_m
        v_1 = variable_f
        term_00 = "stress_divergence"
        term_01 = "pressure_gradient"
        term_10 = "displacement_divergence"
        term_11_0 = "fluid_mass"
        term_11_1 = "fluid_flux"
        term_11_2 = "stabilization"
        d[pp.PRIMARY_VARIABLES] = {v_0: {"cells": g.dim}, v_1: {"cells": 1}}
        d[pp.DISCRETIZATION] = {
            v_0: {
                term_00: pp.Mpsa(kw_m)
            },
            v_1: {
                term_11_0: pp.MassMatrix(kw_f),
                term_11_1: pp.Mpfa(kw_f),
                term_11_2: pp.BiotStabilization(kw_f),
            },
            v_0 + "_" + v_1: {
                term_01: pp.GradP(kw_m)
            },
            v_1 + "_" + v_0: {
                term_10: pp.DivU(kw_m)
            },
        }
        # Assemble. Also discretizes the flow terms (fluid_mass and fluid_flux)
        general_assembler = pp.Assembler(gb)
        general_assembler.discretize(term_filter=["fluid_mass", "fluid_flux"])
        A, b = general_assembler.assemble_matrix_rhs()

        # Re-discretize and assemble using the Biot class
        A_class, b_class = biot_discretizer.matrix_rhs(g, d, discretize=False)

        # Make sure the variable ordering of the matrix assembled by the assembler
        # matches that of the Biot class.
        grids = [g, g]
        variables = [v_0, v_1]
        A, b = permute_matrix_vector(
            A,
            b,
            general_assembler.block_dof,
            general_assembler.full_dof,
            grids,
            variables,
        )

        # Compare the matrices and rhs vectors
        self.assertTrue(np.all(np.isclose(A.A, A_class.A)))
        self.assertTrue(np.all(np.isclose(b, b_class)))
Beispiel #6
0
    def test_assemble_biot_rhs_transient(self):
        """ Test the assembly of a Biot problem with a non-zero rhs using the assembler.

        The test checks whether the discretization matches that of the Biot class and
        that the solution reaches the expected steady state.
        """
        gb = pp.meshing.cart_grid([], [3, 3], physdims=[1, 1])
        g = gb.grids_of_dimension(2)[0]
        d = gb.node_props(g)

        # Parameters identified by two keywords. Non-default parameters of somewhat
        # arbitrary values are assigned to make the test more revealing.
        kw_m = "mechanics"
        kw_f = "flow"
        variable_m = "displacement"
        variable_f = "pressure"
        bound_mech, bound_flow = self.make_boundary_conditions(g)
        val_mech = np.ones(g.dim * g.num_faces)
        val_flow = np.ones(g.num_faces)
        initial_disp, initial_pressure, initial_state = self.make_initial_conditions(
            g, x0=1, y0=2, p0=0)
        dt = 1e0
        biot_alpha = 0.6
        state = {
            variable_f: initial_pressure,
            variable_m: initial_disp,
            kw_m: {
                "bc_values": val_mech
            },
        }
        parameters_m = {
            "bc": bound_mech,
            "bc_values": val_mech,
            "time_step": dt,
            "biot_alpha": biot_alpha,
        }
        parameters_f = {
            "bc": bound_flow,
            "bc_values": val_flow,
            "time_step": dt,
            "biot_alpha": biot_alpha,
            "mass_weight": 0.1 * np.ones(g.num_cells),
        }
        pp.initialize_default_data(g, d, kw_m, parameters_m)
        pp.initialize_default_data(g, d, kw_f, parameters_f)
        pp.set_state(d, state)
        # Initial condition fot the Biot class
        #        d["state"] = initial_state

        # Set up the structure for the assembler. First define variables and equation
        # term names.
        v_0 = variable_m
        v_1 = variable_f
        term_00 = "stress_divergence"
        term_01 = "pressure_gradient"
        term_10 = "displacement_divergence"
        term_11_0 = "fluid_mass"
        term_11_1 = "fluid_flux"
        term_11_2 = "stabilization"
        d[pp.PRIMARY_VARIABLES] = {v_0: {"cells": g.dim}, v_1: {"cells": 1}}
        d[pp.DISCRETIZATION] = {
            v_0: {
                term_00: pp.Mpsa(kw_m)
            },
            v_1: {
                term_11_0: IE_discretizations.ImplicitMassMatrix(kw_f, v_1),
                term_11_1: IE_discretizations.ImplicitMpfa(kw_f),
                term_11_2: pp.BiotStabilization(kw_f),
            },
            v_0 + "_" + v_1: {
                term_01: pp.GradP(kw_m)
            },
            v_1 + "_" + v_0: {
                term_10: pp.DivU(kw_m)
            },
        }

        # Discretize the mechanics related terms using the Biot class
        biot_discretizer = pp.Biot()
        biot_discretizer._discretize_mech(g, d)

        general_assembler = pp.Assembler(gb)
        # Discretize terms that are not handled by the call to biot_discretizer
        general_assembler.discretize(term_filter=["fluid_mass", "fluid_flux"])

        times = np.arange(5)
        for _ in times:
            # Assemble. Also discretizes the flow terms (fluid_mass and fluid_flux)
            A, b = general_assembler.assemble_matrix_rhs()

            # Assemble using the Biot class
            A_class, b_class = biot_discretizer.matrix_rhs(g,
                                                           d,
                                                           discretize=False)

            # Make sure the variable ordering of the matrix assembled by the assembler
            # matches that of the Biot class.
            grids = [g, g]
            variables = [v_0, v_1]
            A, b = permute_matrix_vector(
                A,
                b,
                general_assembler.block_dof,
                general_assembler.full_dof,
                grids,
                variables,
            )

            # Compare the matrices and rhs vectors
            self.assertTrue(np.all(np.isclose(A.A, A_class.A)))
            self.assertTrue(np.all(np.isclose(b, b_class)))

            # Store the current solution for the next time step.
            x_i = sps.linalg.spsolve(A_class, b_class)
            u_i = x_i[:(g.dim * g.num_cells)]
            p_i = x_i[(g.dim * g.num_cells):]
            state = {variable_f: p_i, variable_m: u_i}
            pp.set_state(d, state)

        # Check that the solution has converged to the expected, uniform steady state
        # dictated by the BCs.
        self.assertTrue(
            np.all(np.isclose(x_i, np.ones((g.dim + 1) * g.num_cells))))
    def assign_discretisations(self):
        """
        Assign discretizations to the nodes and edges of the grid bucket.

        Note the attribute subtract_fracture_pressure: Indicates whether or not to
        subtract the fracture pressure contribution for the contact traction. This
        should not be done if the scalar variable is temperature.
        """
        # Shorthand
        key_s, key_m = self.scalar_parameter_key, self.mechanics_parameter_key
        var_s, var_d = self.scalar_variable, self.displacement_variable

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

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

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

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

            if g_h.dim == self.Nd:
                d[pp.COUPLING_DISCRETIZATION] = {
                    self.friction_coupling_term: {
                        g_h: (var_d, "mpsa"),
                        g_l: (self.contact_traction_variable, "empty"),
                        (g_h, g_l):
                        (self.mortar_displacement_variable, contact_discr),
                    },
                    self.scalar_coupling_term: {
                        g_h: (var_s, "diffusion"),
                        g_l: (var_s, "diffusion"),
                        e: (
                            self.mortar_scalar_variable,
                            pp.RobinCoupling(key_s, diff_disc_s),
                        ),
                    },
                    "div_u_coupling": {
                        g_h: (
                            var_s,
                            "mass",
                        ),  # This is really the div_u, but this is not implemented
                        g_l: (var_s, "mass"),
                        e: (self.mortar_displacement_variable, div_u_coupling),
                    },
                    "matrix_scalar_to_force_balance": {
                        g_h: (var_s, "mass"),
                        g_l: (var_s, "mass"),
                        e: (
                            self.mortar_displacement_variable,
                            matrix_scalar_to_force_balance,
                        ),
                    },
                }
                if self.subtract_fracture_pressure:
                    d[pp.COUPLING_DISCRETIZATION].update({
                        "matrix_scalar_to_force_balance": {
                            g_h: (var_s, "mass"),
                            g_l: (var_s, "mass"),
                            e: (
                                self.mortar_displacement_variable,
                                fracture_scalar_to_force_balance,
                            ),
                        }
                    })
            else:
                raise ValueError(
                    "assign_discretizations assumes no fracture intersections."
                )
Beispiel #8
0
def discretize(
    grid_bucket,
    data_dictionary,
    parameter_keyword_flow,
    parameter_keyword_mechanics,
    variable_flow,
    variable_mechanics,
):
    """
    Discretize the problem.

    Parameters:
        grid_bucket (PorePy object):          Grid bucket
        data_dictionary (Dict):               Model's data dictionary
        parameter_keyword_flow (String):      Keyword for the flow parameter
        parameter_keyword_mechanics (String): Keyword for the mechanics parameter
        variable_flow (String):               Primary variable of the flow problem
        variable_mechanics (String):          Primary variable of the mechanics problem

    Output:
        assembler (PorePy object):            Assembler containing discretization
    """

    # The Mpfa discretization assumes unit viscosity. Hence we need to
    # overwrite the class to include it.
    class ImplicitMpfa(pp.Mpfa):
        def assemble_matrix_rhs(self, g, d):
            """
            Overwrite MPFA method to be consistent with Biot's
            time discretization and inclusion of viscosity in Darcy's law
            """
            viscosity = d[pp.PARAMETERS][self.keyword]["viscosity"]
            a, b = super().assemble_matrix_rhs(g, d)
            dt = d[pp.PARAMETERS][self.keyword]["time_step"]
            return a * (1 / viscosity) * dt, b * (1 / viscosity) * dt

    # Redefining input parameters
    gb = grid_bucket
    g = gb.grids_of_dimension(2)[0]
    d = data_dictionary
    kw_f = parameter_keyword_flow
    kw_m = parameter_keyword_mechanics
    v_0 = variable_mechanics
    v_1 = variable_flow

    # Discretize the subproblems using Biot's class, which employs
    # MPSA for the mechanics problem and MPFA for the flow problem
    biot_discretizer = pp.Biot(kw_m, kw_f, v_0, v_1)
    biot_discretizer._discretize_mech(g, d)  # discretize mech problem
    biot_discretizer._discretize_flow(g, d)  # discretize flow problem

    # Names of the five terms of the equation + additional stabilization term.
    ####################################### Term in the Biot equation:
    term_00 = "stress_divergence"  ######## div symmetric grad u
    term_01 = "pressure_gradient"  ######## alpha grad p
    term_10 = "displacement_divergence"  ## d/dt alpha div u
    term_11_0 = "fluid_mass"  ############# d/dt beta p
    term_11_1 = "fluid_flux"  ############# div (rho g - K grad p)
    term_11_2 = "stabilization"  ##########

    # Store in the data dictionary and specify discretization objects.
    d[pp.PRIMARY_VARIABLES] = {v_0: {"cells": g.dim}, v_1: {"cells": 1}}
    d[pp.DISCRETIZATION] = {
        v_0: {
            term_00: pp.Mpsa(kw_m)
        },
        v_1: {
            term_11_0: ImplicitMassMatrix(kw_f, v_1),
            term_11_1: ImplicitMpfa(kw_f),
            term_11_2: pp.BiotStabilization(kw_f, v_1),
        },
        v_0 + "_" + v_1: {
            term_01: pp.GradP(kw_m)
        },
        v_1 + "_" + v_0: {
            term_10: pp.DivU(kw_m, kw_f, v_0)
        },
    }

    assembler = pp.Assembler(gb)
    # Discretize the flow and accumulation terms - the other are already handled
    # by the biot_discretizer
    assembler.discretize(term_filter=[term_11_0, term_11_1])

    return assembler
Beispiel #9
0
    def set_parameters(self, g, data_node, mg, data_edge):
        """
        Set the parameters for the simulation. The stress is given in GPa.
        """
        # First set the parameters used in the pure elastic simulation
        key_m, key_c = super().set_parameters(g, data_node, mg, data_edge)
        key_f = 'flow'

        if not key_m == key_c:
            raise ValueError('Mechanics keyword must equal contact keyword')
        self.key_m = key_m
        self.key_f = key_f
        # Set fluid parameters
        kxx = self.k * np.ones(g.num_cells) / self.length_scale**2
        viscosity = self.viscosity / self.pressure_scale
        K = pp.SecondOrderTensor(g.dim, kxx / viscosity)

        # Define Biot parameters
        alpha = 1
        dt = self.end_time / 20
        # Define the finite volume sub grid
        s_t = pp.fvutils.SubcellTopology(g)

        # Define boundary conditions for flow
        top = g.face_centers[2] > np.max(g.nodes[2]) - 1e-9
        bot = g.face_centers[2] < np.min(g.nodes[2]) + 1e-9
        east = g.face_centers[0] > np.max(g.nodes[0]) - 1e-9
        bc_flow = pp.BoundaryCondition(g, top, 'dir')
        bc_flow = pp.fvutils.boundary_to_sub_boundary(bc_flow, s_t)

        # Set boundary condition values.
        p_bc = self.bc_values(g, dt, key_f)

        # Set initial solution
        u0 = np.zeros(g.dim * g.num_cells)
        p0 = np.zeros(g.num_cells)
        lam_u0 = np.zeros(g.dim * mg.num_cells)
        u_bc0 = self.bc_values(g, 0, key_m)
        u_bc = self.bc_values(g, dt, key_m)
        # Collect parameters in dictionaries

        # Add biot parameters to mechanics
        data_node[pp.PARAMETERS][key_m]['biot_alpha'] = alpha
        data_node[pp.PARAMETERS][key_m]['time_step'] = dt
        data_node[pp.PARAMETERS][key_m]['bc_values'] = u_bc
        data_node[pp.PARAMETERS][key_m]['state'] = {
            'displacement': u0,
            'bc_values': u_bc0
        }

        data_edge[pp.PARAMETERS][key_c]['state'] = lam_u0

        # Add fluid flow dictionary
        data_node = pp.initialize_data(
            g, data_node, key_f, {
                'bc': bc_flow,
                'bc_values': p_bc.ravel('F'),
                'second_order_tensor': K,
                'mass_weight': self.S,
                'aperture': np.ones(g.num_cells),
                'biot_alpha': alpha,
                'time_step': dt,
                'state': p0,
            })

        # Define discretization.
        # For the domain we solve linear elasticity with mpsa and fluid flow with mpfa.
        # In addition we add a storage term (ImplicitMassMatrix) to the fluid mass balance.
        # The coupling terms are:
        # BiotStabilization, pressure contribution to the div u term.
        # GrapP, pressure contribution to stress equation.
        # div_u, displacement contribution to div u term.
        data_node[pp.PRIMARY_VARIABLES] = {
            "u": {
                "cells": g.dim
            },
            "p": {
                "cells": 1
            }
        }

        mpfa_disc = discretizations.ImplicitMpfa(key_f)
        data_node[pp.DISCRETIZATION] = {
            "u": {
                "div_sigma": pp.Mpsa(key_m)
            },
            "p": {
                "flux": mpfa_disc,
                "mass": discretizations.ImplicitMassMatrix(key_f),
                "stab": pp.BiotStabilization(key_f),
            },
            "u_p": {
                "grad_p": pp.GradP(key_m)
            },
            "p_u": {
                "div_u": pp.DivD(key_m)
            },
        }

        # On the mortar grid we define two variables and sets of equations. The first
        # adds a Robin condition to the elasticity equation. The second enforces full
        # fluid pressure and flux continuity over the fractures. We also have to be
        # carefull to obtain the contribution of the coupling discretizations gradP on
        # the Robin contact condition, and the contribution from the mechanical mortar
        # variable on the div_u term.

        # Contribution from fluid pressure on displacement jump at fractures
        gradP_disp = pp.numerics.interface_laws.elliptic_interface_laws.RobinContactBiotPressure(
            key_m, pp.numerics.fv.biot.GradP(key_m))
        # Contribution from mechanics mortar on div_u term
        div_u_lam = pp.numerics.interface_laws.elliptic_interface_laws.DivU_StressMortar(
            key_m, pp.numerics.fv.biot.DivD(key_m))
        # gradP_disp and pp.RobinContact will now give the correct Robin contact
        # condition.
        # div_u (from above) and div_u_lam will now give the correct div u term in the
        # fluid mass balance
        data_edge[pp.PRIMARY_VARIABLES] = {"lam_u": {"cells": g.dim}}
        data_edge[pp.COUPLING_DISCRETIZATION] = {
            "robin_discretization": {
                g: ("u", "div_sigma"),
                g: ("u", "div_sigma"),
                (g, g): ("lam_u", pp.RobinContact(key_m, pp.Mpsa(key_m))),
            },
            "p_contribution_to_displacement": {
                g: ("p", "flux"
                    ),  # "flux" should be "grad_p", but the assembler does not
                g: ("p", "flux"
                    ),  # support this. However, in FV this is not used anyway.
                (g, g): ("lam_u", gradP_disp),
            },
            "lam_u_contr_2_div_u": {
                g: ("p", "flux"),  # "flux" -> "div_u"
                g: ("p", "flux"),
                (g, g): ("lam_u", div_u_lam),
            },
        }
        # Discretize with biot
        pp.Biot(key_m, key_f).discretize(g, data_node)
        return key_m, key_f