Skip to content

Note: Output is not generated for this example (to save resources on GitHub).

Tutorial: Lid-Driven Cavity - 2D

In this example we consider a box with a moving lid. The velocity is initially at rest. The solution should reach at steady state equilibrium after a certain time. The same steady state should be obtained when solving a steady state problem.

We start by loading packages. A Makie plotting backend is needed

for plotting. GLMakie creates an interactive window (useful for real-time plotting), but does not work when building this example on GitHub. CairoMakie makes high-quality static vector-graphics plots.

julia
using CairoMakie
using IncompressibleNavierStokes

Case name for saving results

julia
outdir = joinpath(@__DIR__, "output", "LidDrivenCavity2D")

The code allows for using different floating point number types, including single precision (Float32) and double precision (Float64). On the CPU, the speed is not really different, but double precision uses twice as much memory as single precision. When running on the GPU, single precision is preferred. Half precision (Float16) is also an option, but then the values should be scaled judiciously to avoid vanishing digits when applying differential operators of the form "right minus left divided by small distance".

Note how floating point type hygiene is enforced in the following using T to avoid mixing different precisions.

T = Float64

julia
T = Float32
# T = Float16

We can also choose to do the computations on a different device. By default, the computations are performed on the host (CPU). An optional backend allows for moving arrays to a different device such as a GPU.

Note: For GPUs, single precision is preferred.

julia
backend = CPU()
# using CUDA; backend = CUDABackend()

Here we choose a moderate Reynolds number. Note how we pass the floating point type.

julia
Re = T(1_000)

Non-zero Dirichlet boundary conditions are specified as plain Julia functions.

julia
U = (T(1), T(0))
boundary_conditions = (
    # x left, x right
    (DirichletBC(), DirichletBC()),

    # y bottom, y top
    (DirichletBC(), DirichletBC(U)),
)

We create a two-dimensional domain with a box of size [1, 1]. The grid is created as a Cartesian product between two vectors. We add a refinement near the walls.

julia
n = 32
lims = T(0), T(1)
x = cosine_grid(lims..., n), cosine_grid(lims..., n)
plotgrid(x...)

We can now build the setup and assemble operators. A 3D setup is built if we also provide a vector of z-coordinates.

julia
setup = Setup(; x, boundary_conditions, Re, backend);
nothing #hide

The initial conditions are provided in function. The value dim() determines the velocity component.

julia
ustart = velocityfield(setup, (dim, x, y) -> zero(x));
nothing #hide

Iteration processors are called after every nupdate time steps. This can be useful for logging, plotting, or saving results. Their respective outputs are later returned by solve_unsteady.

julia
processors = (
    # rtp = realtimeplotter(; setup, plot = fieldplot, nupdate = 50),
    # ehist = realtimeplotter(; setup, plot = energy_history_plot, nupdate = 10),
    # espec = realtimeplotter(; setup, plot = energy_spectrum_plot, nupdate = 10),
    # anim = animator(; setup, path = "$outdir/solution.mkv", nupdate = 20),
    # vtk = vtk_writer(; setup, nupdate = 100, dir = outdir, filename = "solution"),
    # field = fieldsaver(; setup, nupdate = 10),
    log = timelogger(; nupdate = 1000),
);
nothing #hide

By default, a standard fourth order Runge-Kutta method is used. If we don't provide the time step explicitly, an adaptive time step is used.

julia
tlims = (T(0), T(10))
state, outputs = solve_unsteady(; setup, ustart, tlims, Δt = T(1e-3), processors);
nothing #hide

Post-process

We may visualize or export the computed fields

Export fields to VTK. The file outdir/solution.vti may be opened for visualization in ParaView. This is particularly useful for inspecting results from 3D simulations.

julia
save_vtk(state; setup, filename = joinpath(outdir, "solution"))

Plot pressure

julia
fieldplot(state; setup, fieldname = :pressure)

Plot velocity. Note the time stamp used for computing boundary conditions, if any.

julia
fieldplot(state; setup, fieldname = :velocitynorm)

Plot vorticity

julia
fieldplot(state; setup, fieldname = :vorticity)

In addition, the named tuple outputs contains quantities from our processors. The logger returns nothing.

julia
# outputs.rtp
# outputs.ehist
# outputs.espec
# outputs.anim
# outputs.vtk
# outputs.field
outputs.log

Copy-pasteable code

Below is the full code for this example stripped of comments and output.

julia
using GLMakie
using IncompressibleNavierStokes

outdir = joinpath(@__DIR__, "output", "LidDrivenCavity2D")

T = Float32
# T = Float16

backend = CPU()
# using CUDA; backend = CUDABackend()

Re = T(1_000)

U = (T(1), T(0))
boundary_conditions = (
    # x left, x right
    (DirichletBC(), DirichletBC()),

    # y bottom, y top
    (DirichletBC(), DirichletBC(U)),
)

n = 32
lims = T(0), T(1)
x = cosine_grid(lims..., n), cosine_grid(lims..., n)
plotgrid(x...)

setup = Setup(; x, boundary_conditions, Re, backend);

ustart = velocityfield(setup, (dim, x, y) -> zero(x));

processors = (
    # rtp = realtimeplotter(; setup, plot = fieldplot, nupdate = 50),
    # ehist = realtimeplotter(; setup, plot = energy_history_plot, nupdate = 10),
    # espec = realtimeplotter(; setup, plot = energy_spectrum_plot, nupdate = 10),
    # anim = animator(; setup, path = "$outdir/solution.mkv", nupdate = 20),
    # vtk = vtk_writer(; setup, nupdate = 100, dir = outdir, filename = "solution"),
    # field = fieldsaver(; setup, nupdate = 10),
    log = timelogger(; nupdate = 1000),
);

tlims = (T(0), T(10))
state, outputs = solve_unsteady(; setup, ustart, tlims, Δt = T(1e-3), processors);

save_vtk(state; setup, filename = joinpath(outdir, "solution"))

fieldplot(state; setup, fieldname = :pressure)

fieldplot(state; setup, fieldname = :velocitynorm)

fieldplot(state; setup, fieldname = :vorticity)

# outputs.rtp
# outputs.ehist
# outputs.espec
# outputs.anim
# outputs.vtk
# outputs.field
outputs.log

This page was generated using Literate.jl.