264 : Stokes+Darcy

(source code)

This example solves the coupled Stokes-Darcy problem in a domain $\Omega := \Omega_\text{FF} \cup \Omega_\text{PM}$ that has a free flow region $\Omega_\text{FF}$ and a porous media regions $\Omega_\text{PM}$. In the free flow region a Stokes problem is solved that seeks a velocity $\mathbf{u}_\text{FF}$ and a pressure $\mathbf{p}_\text{FF}$ such that

\[\begin{aligned} - 2\mu \mathrm{div}(\epsilon(\mathbf{u}_\text{FF}) - p_\text{FF}I) & = \mathbf{f}_\text{FF}\\ \mathrm{div}(\mathbf{u}_\text{FF}) & = 0. \end{aligned}\]

In the porous media region the Darcy problem is solved that seeks a velocity $\mathbf{u}_\text{PM}$ and a pressure $\mathbf{p}_\text{PM}$ such that

\[\begin{aligned} \mathbf{u}_\text{PM} + k \nabla p_\text{PM} & = 0\\ \mathrm{div}(\mathbf{u}_\text{PM}) & = f_\text{PM}. \end{aligned}\]

On the interface $\Gamma := \partial \Omega_\text{FF} \cap \partial \Omega_\text{PM}$ the two velocities are coupled via several conditions, i.e., the conservation of mass

\[\mathbf{u}_\text{FF} \cdot \mathbf{n} = \mathbf{u}_\text{PM} \cdot \mathbf{n} \quad \text{on } \Gamma,\]

the balance of normal forces

\[p_\text{FF} - 2\mu \epsilon(\mathbf{u}_\text{FF}) \mathbf{n}_\text{FF} \cdot \mathbf{n}_\text{FF} = p_\text{PM},\]

and the Beavers-Joseph-Saffman condition

\[\mathbf{u}_\text{FF} \cdot \mathbf{\tau} = -\frac{\sqrt{\mu k}}{\mu \alpha} 2 \epsilon(\mathbf{u}_\text{FF}) \mathbf{n}_\text{FF} \cdot \mathbf{\tau}.\]

The interface condition for the normal fluxes is realized weakly via a Lagrange multiplier $\lambda$ that only lives on the interface.

The weak formulation leads to the problem: seek $(\mathbf{u}_\text{FF}, \mathbf{u}_\text{PM}, p_\text{FF}, p_\text{PM}, \lambda)$ such that, for all $(\mathbf{v}_\text{FF}, \mathbf{v}_\text{PM}, q_\text{FF}, q_\text{PM}, \chi)$,

\[\begin{aligned} a_1(\mathbf{u}_\text{FF},\mathbf{v}_\text{FF}) + a_2(\mathbf{u}_\text{PM}, \mathbf{v}_\text{PM}) + b_1(p_\text{FF},\mathbf{v}_\text{FF}) + b_2(p_\text{PM},\mathbf{v}_\text{PM}) + b_{\Gamma}(\mathbf{v}_\text{FF} - \mathbf{v}_\text{PM}, \lambda) & = (\mathbf{f}_\text{FF}, \mathbf{v}_\text{FF})_{L^2(\Omega_\text{FF})}\\ b_1(q_\text{FF},\mathbf{u}_\text{FF}) + b_2(q_\text{PM},\mathbf{u}_\text{PM}) & = (f_\text{PM}, q_\text{PM})_{L^2(\Omega_\text{PM})}\\ b_{\Gamma}(\mathbf{u}_\text{FF} - \mathbf{u}_\text{PM}, \chi) & = 0. \end{aligned}\]

The bilinearforms read

\[\begin{aligned} a_1(\mathbf{u}_\text{FF},\mathbf{v}_\text{FF}) & := 2\mu (\epsilon(\mathbf{u}_\text{FF}), \epsilon(\mathbf{v}_\text{FF}))_{L^2(\Omega_\text{FF})} + \frac{αμ}{\sqrt{μk}} (\mathbf{u}_\text{FF} \cdot \mathbf{\tau},\mathbf{v}_\text{FF} \cdot \mathbf{\tau})_{L^2(\Gamma)}\\ a_2(\mathbf{u}_\text{PM}, \mathbf{v}_\text{PM}) & := (\mathbf{u}_\text{PM}, \mathbf{v}_\text{PM})_{L^2(\Omega_\text{PM})}\\ b_1(q_\text{FF},\mathbf{v}_\text{FF}) & := -(\mathrm{div} \mathbf{v}_\text{FF}, q_\text{FF})_{L^2(\Omega_\text{FF})}\\ b_2(q_\text{PM},\mathbf{v}_\text{PM}) & := -(\mathrm{div} \mathbf{v}_\text{PM}, q_\text{PM})_{L^2(\Omega_\text{PM})}\\ b_{\Gamma}(\mathbf{v}, \lambda) & := (\mathbf{v} \cdot \mathbf{n}, \lambda)_{L^2(\Gamma)} \end{aligned}\]

Details on the model can be found e.g. in the reference below.

Reference

''Coupling Fluid Flow with Porous Media Flow'' SIAM Journal on Numerical Analysis 2002 40:6, 2195-2218 >Link<

In this example an analytic benchmark problem is solved with the Taylor–Hood FEM in the free flow domain and the Raviart–Thomas FEM in the porous media domain.

The computed solution for the default parameters looks like this:

module Example264_StokesDarcy

using ExtendableFEM
using ExtendableFEMBase
using ExtendableGrids
using GridVisualize
using SimplexGridFactory
using Triangulate
using Metis

# exact solution and data functions
function u!(result, qpinfo)
    x = qpinfo.x
    result[1] = -cos(pi * x[1]) * sin(pi * x[2])
    result[2] = sin(pi * x[1]) * cos(pi * x[2])
    return nothing
end
function f_FF!(result, qpinfo)
    x = qpinfo.x
    result[1] = -(4 * pi^2 + 1) * cos(pi * x[1]) * sin(pi * x[2])
    return result[2] = - sin(pi * x[1]) * cos(pi * x[2])
end
function p!(result, qpinfo)
    x = qpinfo.x
    result[1] = -sin(pi * x[1])sin(pi * x[2]) * (1 / pi + 2 * pi) + 4 / pi
    return nothing
end
function q!(result, qpinfo)
    k = qpinfo.params[1]
    x = qpinfo.x
    result[1] = -sin(pi * x[1])sin(pi * x[2]) / pi + 4 / pi
    return nothing
end
function v!(result, qpinfo) # = -K∇q
    k = qpinfo.params[1]
    x = qpinfo.x
    result[1] = cos(pi * x[1]) * sin(pi * x[2])
    result[2] = sin(pi * x[1]) * cos(pi * x[2])
    return nothing
end
function f_PM!(result, qpinfo) # = div(v)
    x = qpinfo.x
    return result[1] = -2 * pi * sin(pi * x[1]) * sin(pi * x[2])
end

# kernel functions for operators
function kernel_stokes_standard!(result, u_ops, qpinfo)
    ∇u, p = view(u_ops, 1:4), view(u_ops, 5)
    μ = 2 * qpinfo.params[1]
    result[1] = μ * ∇u[1] - p[1]
    result[2] = μ * (∇u[2] + ∇u[3])
    result[3] = μ * (∇u[2] + ∇u[3])
    result[4] = μ * ∇u[4] - p[1]
    result[5] = -(∇u[1] + ∇u[4])
    return nothing
end

function coupling_normal!(result, input, qpinfo)
    return result[1] = input[2] - input[1]
end

function exact_error!(f!::Function)
    return function closure(result, u, qpinfo)
        f!(result, qpinfo)
        result .-= u
        return result .= result .^ 2
    end
end

# everything is wrapped in a main function
function main(;
        μ = 1,                  # viscosity
        k = 1,                  # permeability
        α = 1,                  # parameter in interface condition
        coupled = true,         # solve coupled problem with interface conditions or decoupled problems on subgrids ?
        order_u = 2,            # polynomial order for free flow velocity
        order_p = order_u - 1,  # polynomial order for free flow pressure
        order_v = order_u - 1,  # polynomial order for porous media velocity
        nrefs = 4,              # number of mesh refinements
        Plotter = nothing,      # backend for Plotting (e.g. GLMakie)
        parallel = false,       # do parallel assembly?
        npart = 8,              # number of partitions for grid coloring (if parallel = true)
        kwargs...
    )

    # region numbers
    rFF = 1 # free flow region
    rPM = 2 # porous media region

    # load mesh and refine
    xgrid =
        uniform_refine(
        simplexgrid(
            Triangulate;
            points = [0 0; 0 -1; 1 -1; 1 0; 1 1; 0 1]',
            bfaces = [1 2; 2 3; 3 4; 4 5; 5 6; 6 1; 1 4]',
            bfaceregions = [1, 2, 3, 4, 5, 6, 7],
            regionpoints = [0.5 0.5; 0.5 -0.5]',
            regionnumbers = [1, 2],
            regionvolumes = [1.0, 1.0]
        ), nrefs
    )

    if parallel
        xgrid = partition(xgrid, RecursiveMetisPartitioning(npart = npart))
    end

    # define unknowns
    u = Unknown("u"; name = "velocity FF", dim = 2)
    p = Unknown("p"; name = "pressure FF", dim = 1)
    v = Unknown("q"; name = "velocity PM", dim = 2)
    q = Unknown("q"; name = "pressure PM", dim = 2)
    λ = Unknown("λ"; name = "interface LM", dim = 1)

    # problem description
    PD = ProblemDescription("Stokes-Darcy problem")
    assign_unknown!(PD, u)
    assign_unknown!(PD, p)
    assign_unknown!(PD, v)
    assign_unknown!(PD, q)
    assign_unknown!(PD, λ)
    if coupled
        # when coupled no boundary conditions on boundary region 7
        bnd_stokes = [4, 5, 6]
        bnd_darcy = [1, 2, 3]
    else
        bnd_stokes = [4, 5, 6, 7]
        bnd_darcy = [1, 2, 3, 7]
    end

    # define free flow problem: Stokes equations to solve for velocity u and pressure p
    assign_operator!(PD, BilinearOperator(kernel_stokes_standard!, [grad(u), id(p)]; params = [μ], parallel = parallel, regions = [rFF], kwargs...))
    assign_operator!(PD, LinearOperator(f_FF!, [id(u)]; bonus_quadorder = 4, parallel = parallel, regions = [rFF], kwargs...))
    assign_operator!(PD, InterpolateBoundaryData(u, u!; bonus_quadorder = 4, regions = bnd_stokes, kwargs...))

    # define porous media flow: Darcy problem to solve for pressure q
    assign_operator!(PD, BilinearOperator([id(v)]; parallel = parallel, regions = [rPM], kwargs...))
    assign_operator!(PD, BilinearOperator([div(v)], [id(q)]; factor = -1, transposed_copy = -1, parallel = parallel, regions = [rPM], kwargs...))
    assign_operator!(PD, LinearOperator(f_PM!, [id(q)]; bonus_quadorder = 4, parallel = parallel, regions = [rPM], kwargs...))

    # coupling conditions on interface (bregion 7)
    if coupled
        # interface condition for tangential part
        assign_operator!(PD, BilinearOperator([tangentialflux(u)]; factor = α * μ / sqrt(μ * k), entities = ON_FACES, regions = [7]))
        # interface condition for normal part via Lagrange multiplier
        assign_operator!(PD, BilinearOperator(coupling_normal!, [id(λ)], [normalflux(u), normalflux(v)]; transposed_copy = 1, entities = ON_FACES, regions = [7]))
    else
        # dummy problem for Lagrange multiplier
        assign_operator!(PD, BilinearOperator([id(λ)]; entities = ON_FACES, parallel = parallel, regions = [7]))
        # Dirichlet condition for Darcy problem with different factor (due to different direction of normal)
        assign_operator!(PD, LinearOperator(q!, [normalflux(v)]; bonus_quadorder = 4, factor = 1, entities = ON_FACES, params = [k], regions = [7], kwargs...))
    end
    assign_operator!(PD, LinearOperator(q!, [normalflux(v)]; bonus_quadorder = 4, factor = -1, entities = ON_BFACES, params = [k], regions = setdiff(bnd_darcy, 7), kwargs...))

    # generate FESpaces and a solution vector for all 3 unknowns
    FEType_λ = order_v == 0 ? L2P0{1} : H1Pk{1, 1, min(order_v, order_u)}
    FES = [
        FESpace{H1Pk{2, 2, order_u}}(xgrid; regions = [rFF]),
        FESpace{H1Pk{1, 2, order_p}}(xgrid; regions = [rFF]),
        FESpace{HDIVRTk{2, order_v}}(xgrid; regions = [rPM]),
        FESpace{H1Pk{1, 2, order_v}}(xgrid; regions = [rPM], broken = true),
        FESpace{FEType_λ, ON_FACES}(xgrid; regions = [7], broken = true),
    ]
    sol = FEVector(FES; tags = PD.unknowns)

    # solve the coupled problem
    sol = solve(PD, [FES[1], FES[2], FES[3], FES[4], FES[5]]; kwargs...)

    # correct integral mean of pressures
    pmean_exact = integrate(xgrid, ON_CELLS, p!, 1; quadorder = 10, regions = [rFF])
    qmean_exact = integrate(xgrid, ON_CELLS, q!, 1; params = [k], quadorder = 10, regions = [rPM])
    pintegrate = ItemIntegrator([id(p)]; regions = [rFF])
    qintegrate = ItemIntegrator([id(q)]; regions = [rPM])
    pmean = sum(evaluate(pintegrate, sol))
    qmean = sum(evaluate(qintegrate, sol))
    if coupled
        view(sol[p]) .-= (pmean + qmean)
        view(sol[q]) .-= (pmean + qmean)
    else
        view(sol[p]) .-= pmean - pmean_exact
        view(sol[q]) .-= qmean - qmean_exact
    end

    # error calculation
    ErrorIntegratorExactU = ItemIntegrator(exact_error!(u!), [id(u)]; bonus_quadorder = 4, regions = [rFF], kwargs...)
    ErrorIntegratorExactV = ItemIntegrator(exact_error!(v!), [id(v)]; params = [k], bonus_quadorder = 4, regions = [rPM], kwargs...)
    ErrorIntegratorExactP = ItemIntegrator(exact_error!(p!), [id(p)]; bonus_quadorder = 4, regions = [rFF], kwargs...)
    ErrorIntegratorExactQ = ItemIntegrator(exact_error!(q!), [id(q)]; params = [k], bonus_quadorder = 4, regions = [rPM], kwargs...)
    errorU = evaluate(ErrorIntegratorExactU, sol)
    errorV = evaluate(ErrorIntegratorExactV, sol)
    errorP = evaluate(ErrorIntegratorExactP, sol)
    errorQ = evaluate(ErrorIntegratorExactQ, sol)
    L2errorU = sqrt(sum(view(errorU, 1, :)) + sum(view(errorU, 2, :)))
    L2errorV = sqrt(sum(view(errorV, 1, :)) + sum(view(errorV, 2, :)))
    L2errorP = sqrt(sum(view(errorP, 1, :)))
    L2errorQ = sqrt(sum(view(errorQ, 1, :)))
    @info "L2error(u) = $L2errorU"
    @info "L2error(v) = $L2errorV"
    @info "L2error(p) = $L2errorP"
    @info "L2error(q) = $L2errorQ"

    # plot
    plt = plot([id(u), id(v), id(p), id(q), grid(u)], sol; Plotter = Plotter, ncols = 2, rasterpoints = 12, width = 800, height = 1200)

    return [L2errorU, L2errorV, L2errorP, L2errorQ], plt
end

    @info errors
end # module

This page was generated using Literate.jl.