Run this tutorial
Click here to run this tutorial on mybinder.org:Tutorial: discontinuous IPDG for the stationary heat equation#
This tutorial shows how to solve the stationary heat equation with homogeneous Dirichlet boundary conditions using interior penalty (IP) discontinuous Galerkin (DG) Finite Elements with dune-gdt.
This is work in progress [WIP], still missing:
- mathematical theory on IPDG methods 
- explanation of the IPDG implementation 
- non-homonegenous Dirichlet boundary values 
- Neumann boundary values 
- Robin boundary values 
# wurlitzer: display dune's output in the notebook
%load_ext wurlitzer
%matplotlib notebook
import numpy as np
np.warnings.filterwarnings('ignore') # silence numpys warnings
from dune.xt.grid import Dim
from dune.xt.functions import ConstantFunction, ExpressionFunction
d = 2
omega = ([0, 0], [1, 1])
kappa = ConstantFunction(dim_domain=Dim(d), dim_range=Dim(1), value=[1.], name='kappa')
# note that we need to prescribe the approximation order, which determines the quadrature on each element
f = ExpressionFunction(dim_domain=Dim(d), variable='x', expression='exp(x[0]*x[1])', order=3, name='f')
from dune.xt.grid import Simplex, make_cube_grid, visualize_grid
grid = make_cube_grid(Dim(d), Simplex(), lower_left=omega[0], upper_right=omega[1], num_elements=[2, 2])
grid.global_refine(1) # we need to refine once to obtain a symmetric grid
print(f'grid has {grid.size(0)} elements, {grid.size(d - 1)} edges and {grid.size(d)} vertices')
_ = visualize_grid(grid)
grid has 16 elements, 28 edges and 13 vertices
GridParameterBlock: Parameter 'refinementedge' not specified, defaulting to 'ARBITRARY'.
1.9: everything in a single function#
For a better overview, the above discretization code is also available in a single function in the file discretize_elliptic_ipdg.py.
import inspect
from discretize_elliptic_ipdg import discretize_elliptic_ipdg_dirichlet_zero
print(inspect.getsource(discretize_elliptic_ipdg_dirichlet_zero))
def discretize_elliptic_ipdg_dirichlet_zero(
    grid, diffusion, source, symmetry_factor=1, penalty_parameter=None, weight=1
):
    """
    Discretizes the stationary heat equation with homogeneous Dirichlet boundary values everywhere
    with dune-gdt using an interior penalty (IP) discontinuous Galerkin (DG) method based on first
    order Lagrange finite elements.
    The type of IPDG scheme is determined by `symmetry_factor` and `weight`:
    * with `weight==1` we obtain
    - `symmetry_factor==-1`: non-symmetric interior penalty scheme (NIPDG)
    - `symmetry_factor==0`: incomplete interior penalty scheme (IIPDG)
    - `symmetry_factor==1`: symmetric interior penalty scheme (SIPDG)
    * with `weight=diffusion`, we obtain
    - `symmetry_factor==1`: symmetric weighted interior penalty scheme (SWIPDG)
    Parameters
    ----------
    grid
        The grid, given as a GridProvider from dune.xt.grid.
    diffusion
        Diffusion function with values in R^{d x d}, anything that dune.xt.functions.GridFunction
        can handle.
    source
        Right hand side source function with values in R, anything that
        dune.xt.functions.GridFunction can handle.
    symmetry_factor
        Usually one of -1, 0, 1, determines the IPDG scheme (see above).
    penalty_parameter
        Positive number to ensure coercivity of the resulting diffusion bilinear form,
        e.g. 16 for simplicial non-degenerate grids in 2d and `diffusion==1`.
        Determined automatically if `None`.
    weight
        Usually 1 or diffusion, determines the IPDG scheme (see above).
    Returns
    -------
    u_h
        The computed solution as a dune.gdt.DiscreteFunction for postprocessing.
    """
    d = grid.dimension
    diffusion = GF(grid, diffusion, dim_range=(Dim(d), Dim(d)))
    source = GF(grid, source)
    weight = GF(grid, weight, dim_range=(Dim(d), Dim(d)))
    boundary_info = AllDirichletBoundaryInfo(grid)
    V_h = DiscontinuousLagrangeSpace(grid, order=1)
    if not penalty_parameter:
        # TODO: check if we need to include diffusion for the coercivity here!
        # TODO: each is a grid walk, compute this in one grid walk with the sparsity pattern
        C_G = estimate_element_to_intersection_equivalence_constant(grid)
        C_M_times_1_plus_C_T = estimate_combined_inverse_trace_inequality_constant(V_h)
        penalty_parameter = C_G * C_M_times_1_plus_C_T
        if symmetry_factor == 1:
            penalty_parameter *= 4
    assert isinstance(penalty_parameter, Number)
    assert penalty_parameter > 0
    l_h = VectorFunctional(grid, source_space=V_h)
    l_h += LocalElementIntegralFunctional(
        LocalElementProductIntegrand(GF(grid, 1)).with_ansatz(source)
    )
    a_h = MatrixOperator(
        grid,
        source_space=V_h,
        range_space=V_h,
        sparsity_pattern=make_element_and_intersection_sparsity_pattern(V_h),
    )
    a_h += LocalElementIntegralBilinearForm(LocalLaplaceIntegrand(diffusion))
    a_h += (
        LocalCouplingIntersectionIntegralBilinearForm(
            LocalLaplaceIPDGInnerCouplingIntegrand(symmetry_factor, diffusion, weight)
            + LocalIPDGInnerPenaltyIntegrand(penalty_parameter, weight)
        ),
        {},
        ApplyOnInnerIntersectionsOnce(grid),
    )
    a_h += (
        LocalIntersectionIntegralBilinearForm(
            LocalIPDGBoundaryPenaltyIntegrand(penalty_parameter, weight)
            + LocalLaplaceIPDGDirichletCouplingIntegrand(symmetry_factor, diffusion)
        ),
        {},
        ApplyOnCustomBoundaryIntersections(grid, boundary_info, DirichletBoundary()),
    )
    walker = Walker(grid)
    walker.append(a_h)
    walker.append(l_h)
    walker.walk()
    u_h = DiscreteFunction(V_h, name="u_h")
    a_h.apply_inverse(l_h.vector, u_h.dofs.vector)
    return u_h
from dune.gdt import visualize_function
u_h = discretize_elliptic_ipdg_dirichlet_zero(
    grid, kappa, f,
    symmetry_factor=1, penalty_parameter=16, weight=1) # SIPDG scheme
_ = visualize_function(u_h)
Download the code:
example__ipdg_stationary_heat_equation.md
example__ipdg_stationary_heat_equation.ipynb