Stokes flow example (FE with Taylor-Hood elements)

This example shows how to solve the equations of motion for Stokes flow with $\mathbb{P2}-\mathbb{P1}$ (Taylor-Hood) elements using Bcube.

Steady case - Moffat vortices

Theory

Let's first consider a Stokes flow within a wedge of angle $\theta = 28^\circ$ and height $H=1m$. A velocity of $1m/s$ is imposed on the top boundary (the lid) as well as zero pressure. On the left and right edges a no-slip boundary condition is applied.

The set of equations are:

\[\begin{cases} 0 = \nabla \cdot \left( -p \mathbb{I} + \mu \nabla u \right) + f \textrm{ in } \Omega \\ \nabla \cdot u = 0 \textrm{ in } \Omega \\ u = (0,0) \textrm{ on } \Gamma_L \cup \Gamma_R \\ u = (1,0) \textrm{ and } p=0 \textrm{ on } \Gamma_T \end{cases}\]

Density $\rho$ is taken equal to $1400kg.m^{-3}$ and dynamic viscosity $\mu$ equal to $1.4 Pa.s$. The Reynolds number based on the height of the wedge is therefore equal to $1000$. The weak form of this problem is given by:

\[\begin{cases} \textrm{ Find } u \in V \textrm{ and } p \in Q \textrm{ such that } \forall v \in V, \, \forall q \in Q\\ \int_\Omega \left( \mu \nabla u : \nabla v - p \nabla \cdot v \right) \, dx = \int_\Omega f \cdot v \, dx \\ \int_\Omega q \nabla \cdot u\, dx = 0 \end{cases}\]

where $V$ and $Q$ are suitable function spaces. This can be recast in "mixed" formalism:

\[\begin{cases} \textrm{ Find } (u,p) \in W=V \times Q \textrm{ such that } \forall (v,q) \in W\\ a((u,p),(v,q)) = l((v,q)) \end{cases}\]

where $a((u,p),(v,q)) = \int_\Omega \left( \mu \nabla u : \nabla v - p \nabla \cdot v +q \nabla \cdot u \right)\, dx$ and $l((v,q))=\int_\Omega f \cdot v \, dx$

Taylor-Hood ($\mathbb{P2}-\mathbb{P1}$) elements are used to discretize the weak form of the problem which leads to the linear system:

\[\begin{bmatrix} A & B^T \\ B & 0 \end{bmatrix} \begin{bmatrix} U \\ P \end{bmatrix} = L\]

Commented code

import necessary packages

using Bcube
using BcubeGmsh
using BcubeVTK
using LinearAlgebra
using StaticArrays
using SpecialFunctions

Function space: P2 for velocity and P1 for pressure

const fspace = :Lagrange
const degree_u = 2
const degquad = 2 * degree_u + 1
const degree_p = 1

Material parameters

const ρ = 1400.0
const μ = 14.0
const ν = μ / ρ

Parameters for the oscillating plate case

const f = 1.0
const k = sqrt(π * f / ν)

const Δt = 1.0e-3
const finalTime = 2.0

const outputpath = joinpath(@__DIR__, "..", "..", "..", "myout", "stokes")
mkpath(outputpath)

Function that runs the steady case:

function run_steady()

Read 2D mesh

    mesh_path =
        joinpath(@__DIR__, "..", "..", "..", "input", "mesh", "domainTriangle_tri.msh")
    mesh = read_mesh(mesh_path)

Definition of trial and test function spaces (with associated Dirichlet boundary conditions)

    fsu = FunctionSpace(fspace, degree_u)
    U_vel = TrialFESpace(
        fsu,
        mesh,
        Dict(
            "edge_right" => SA[0.0, 0.0],
            "edge_left" => SA[0.0, 0.0],
            "edge_top" => SA[1.0, 0.0],
        );
        size = 2,
    )
    V_vel = TestFESpace(U_vel)

    fsp = FunctionSpace(fspace, degree_p)
    U_pre = TrialFESpace(fsp, mesh, Dict("edge_top" => SA[0.0]); size = 1)
    V_pre = TestFESpace(U_pre)

Define MultiFESpace (mixed formalism)

    U = MultiFESpace(U_vel, U_pre)
    V = MultiFESpace(V_vel, V_pre)

    velocity = FEFunction(U_vel)
    pressure = FEFunction(U_pre)

Define measures for cell

    dΩ = Measure(CellDomain(mesh), degquad)

volume force is equal to zero

    f = PhysicalFunction(x -> SA[0.0, 0.0])

definition of bilinear and linear forms

    a((u, p), (v, q)) = ∫(μ * ∇(u) ⊡ ∇(v) - tr(∇(v)) * p + tr(∇(u)) * q)dΩ
    l((v, q)) = ∫(f ⋅ v)dΩ

assemble matrix and RHS

    A = assemble_bilinear(a, U, V)
    L = assemble_linear(l, V)

build Dirichlet vector to apply lift

    Wd = Bcube.assemble_dirichlet_vector(U, V, mesh)

Apply lift

    L = L - A * Wd

Apply homogeneous dirichlet condition

    Bcube.apply_homogeneous_dirichlet_to_vector!(L, U, V, mesh)
    Bcube.apply_dirichlet_to_matrix!(A, U, V, mesh)

Solve linear system

    sol = A \ L

    sol = sol .+ Wd

    ϕ = FEFunction(V, sol)

Write solution

    velocity, pressure = ϕ

    vars = Dict("Velocity" => velocity, "Pressure" => pressure)
    write_file(joinpath(outputpath, "output_steady.pvd"), mesh, vars)
end

The obtained solution captures the Moffat vortices topology of the flow

Unsteady case - oscillating plate

Theory

Let's now consider a Stokes flow over an oscillating flat plate of length $0.1m$ and height $0.5m$. An oscillating velocity $(\sin(2\pi f t),0)$ is imposed on the bottom boundary as well as zero pressure. On all the other boundaries a "do nothing" condition is applied: $-pn + \mu \frac{\partial u}{\partial n} = 0$. The set of equations are:

\[\begin{cases} \rho \frac{\partial u}{\partial t} = \nabla \cdot \left( -p \mathbb{I} + \mu \nabla u \right) + f \textrm{ in } \Omega \\ \nabla \cdot u = 0 \textrm{ in } \Omega \\ u = (0,0) \textrm{ on } \Gamma_L \cup \Gamma_R \\ u = (1,0) \textrm{ and } p=0 \textrm{ on } \Gamma_T \end{cases}\]

The weak form of this problem is given by:

\[\begin{cases} \textrm{ Find } (u,p) \in W=V \times Q \textrm{ such that } \forall (v,q) \in W\\ m((u,p),(v,q))+a((u,p),(v,q)) = l((v,q)) \end{cases}\]

where $m((u,p),(v,q)) = \int_\Omega \rho u \cdot v \, dx$ and $l((v,q))=\int_\Omega f \cdot v \, dx$

Taylor-Hood ($\mathbb{P2}-\mathbb{P1}$) elements are used to discretize the weak form of the problem which leads to the linear system:

\[\begin{bmatrix} M & 0 \\ 0 & 0 \end{bmatrix} \begin{bmatrix} \dot{U} \\ \dot{P} \end{bmatrix} + \begin{bmatrix} A & B^T \\ B & 0 \end{bmatrix} \begin{bmatrix} U \\ P \end{bmatrix} = L\]

Commented code

Reference solution for unsteady case (Panton, R. L. (2013). Incompressible Flow, 4th edition, pages 228-236) This solution is valid for a semi-infinite domain.

function reference_solution(t, x)
    Uₛ = exp(-k * x[2]) * sin(2.0 * π * f * t - k * x[2])
    T = 2.0 * π * f * t
    Y = x[2] / sqrt(ν / (2.0 * π * f))
    C = 1.0 - im
    Uₜ = imag(
        0.5 *
        exp(-im * T - C * Y / sqrt(2)) *
        erfc(sqrt(0.5 * T) * (C - Y / (T * sqrt(2)))) -
        0.5 *
        exp(-im * T + C * Y / sqrt(2)) *
        erfc(sqrt(0.5 * T) * (C + Y / (T * sqrt(2)))),
    )
    return Uₛ + Uₜ
end

Function that runs the unsteady case:

function run_unsteady()

Generate 2D mesh

    mesh = rectangle_mesh(
        11,
        51;
        type = :quad,
        xmin = 0.0,
        xmax = 1.0e-1,
        ymin = 0.0,
        ymax = 5.0e-1,
        order = 1,
        bnd_names = ("Left", "Right", "South", "North"),
    )

Definition of trial and test function spaces (with associated Dirichlet boundary conditions)

    fsu = FunctionSpace(fspace, degree_u)
    U_vel = TrialFESpace(
        fsu,
        mesh,
        Dict("South" => (x, t) -> SA[sin(2.0 * π * f * t), 0.0]);
        size = 2,
    )
    V_vel = TestFESpace(U_vel)

    fsp = FunctionSpace(fspace, degree_p)
    U_pre = TrialFESpace(fsp, mesh, Dict("South" => SA[0.0]); size = 1)
    V_pre = TestFESpace(U_pre)

Define MultiFESpace (mixed formalism)

    U = MultiFESpace(U_vel, U_pre)
    V = MultiFESpace(V_vel, V_pre)

    velocity = FEFunction(U_vel)
    pressure = FEFunction(U_pre)

Define measures for cell

    dΩ = Measure(CellDomain(mesh), degquad)

    fv = PhysicalFunction(x -> SA[0.0, 0.0])

definition of bilinear and linear forms

    m((u, p), (v, q)) = ∫(ρ * u ⋅ v)dΩ
    a((u, p), (v, q)) = ∫(μ * ∇(u) ⊡ ∇(v) - tr(∇(v)) * p + tr(∇(u)) * q)dΩ
    l((v, q)) = ∫(fv ⋅ v)dΩ

Assemble matrices and factorize

    M = assemble_bilinear(m, U, V)
    A = assemble_bilinear(a, U, V)
    L = assemble_linear(l, V)

    A0 = copy(A)

    Bcube.apply_dirichlet_to_matrix!(A, U, V, mesh)
    Bcube.apply_dirichlet_to_matrix!(M, U, V, mesh)

    f_Mtime = factorize(M .+ Δt * A)

    ϕ = FEFunction(V)

    velocity_ref(t) = PhysicalFunction(x -> SA[reference_solution(t, x), 0.0])

Write initial solution

    filepath = joinpath(outputpath, "output_unsteady.pvd")
    err = velocity - velocity_ref(0.0)
    vars = Dict(
        "Velocity" => velocity,
        "Velocity_ref" => velocity_ref(0.0),
        "error" => err,
        "Pressure" => pressure,
    )
    write_file(filepath, mesh, vars, 0, 0.0; collection_append = false)

Time loop

    time = 0.0
    itime = 0
    sol = zero(L)

    while time <= finalTime
        time += Δt
        itime += 1

        println("Time stepping : time = ", time, " / ", finalTime)

        Wd = Bcube.assemble_dirichlet_vector(U, V, mesh, time)

        # Apply lift
        L = -A0 * Wd

        # Apply homogeneous dirichlet condition
        Bcube.apply_homogeneous_dirichlet_to_vector!(L, U, V, mesh)

        # Solve time step
        sol = f_Mtime \ (Δt * L + M * sol)

        # Write results
        if itime % 10 == 0
            set_dof_values!(ϕ, sol .+ Wd)
            velocity, pressure = ϕ

            err = velocity - velocity_ref(time)

            vars = Dict(
                "Velocity" => velocity,
                "Velocity_ref" => velocity_ref(time),
                "error" => err,
                "Pressure" => pressure,
            )
            write_file(filepath, mesh, vars, itime, time; collection_append = true)
        end
    end
end

The obtained solution compares well with the reference solution

println("")
println("-------------------------------")
println("----- Running steady case -----")
run_steady()
println("-------------------------------")
println("")
println("---------------------------------")
println("----- Running unsteady case -----")
run_unsteady()
println("---------------------------------")
println("")

This page was generated using Literate.jl.