Skip to content

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

A-priori analysis: Filtered DNS (2D or 3D)

This script is used to generate results for the the paper Agdestein and Sanderse [5].

  • Generate filtered DNS data

  • Compute quantities for different filters

Load packages

julia
using CairoMakie
using FFTW
using GLMakie
using IncompressibleNavierStokes
using IncompressibleNavierStokes: momentum, project, apply_bc_u!, spectral_stuff
using NeuralClosure
using PaperDC
using Printf
using Random

Output directory

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

Hardware selection

For running on CPU

julia
ArrayType = Array
clean() = nothing

For running on GPU

julia
using CUDA;
CUDA.allowscalar(false);
ArrayType = CuArray;
clean() = (GC.gc(); CUDA.reclaim()) # This seems to be needed to free up memory

Setup

2D configuration

julia
D = 2
T = Float64;
ndns = 4096
Re = T(10_000)
kp = 20
Δt = T(5e-5)
filterdefs = [
    (FaceAverage(), 64),
    (FaceAverage(), 128),
    (FaceAverage(), 256),
    (VolumeAverage(), 64),
    (VolumeAverage(), 128),
    (VolumeAverage(), 256),
]

3D configuration

julia
D = 3
T, ndns = Float64, 512 # Works on a 40GB A100 GPU. Use Float32 and smaller n for less memory.
Re = T(2_000)
kp = 5
Δt = T(1e-4)
filterdefs = [
    (FaceAverage(), 32),
    (FaceAverage(), 64),
    (FaceAverage(), 128),
    (VolumeAverage(), 32),
    (VolumeAverage(), 64),
    (VolumeAverage(), 128),
]

Setup

julia
lims = T(0), T(1)
dns = let
    setup = Setup(; x = ntuple-> LinRange(lims..., ndns + 1), D), Re, ArrayType)
    psolver = psolver_spectral(setup)
    (; setup, psolver)
end;
filters = map(filterdefs) do (Φ, nles)
    compression = ndns ÷ nles
    setup = Setup(; x = ntuple-> LinRange(lims..., nles + 1), D), Re, ArrayType)
    psolver = psolver_spectral(setup)
    (; setup, Φ, compression, psolver)
end;
nothing #hide

Create random initial conditions

julia
rng = Random.seed!(Random.default_rng(), 12345)
ustart = random_field(dns.setup, T(0); kp, dns.psolver, rng);
clean()

Solve unsteady problem

julia
@time state, outputs = solve_unsteady(;
    dns.setup,
    ustart,
    tlims = (T(0), T(1e-1)),
    Δt,
    docopy = true, # leave initial conditions unchanged, false to free up memory
    dns.psolver,
    processors = (
        obs = observe_u(dns.setup, dns.psolver, filters; nupdate = 20),
        log = timelogger(; nupdate = 5),
    ),
);
clean()

Plot 2D fields

julia
D == 2 && with_theme(; fontsize = 25) do
    # Compute quantities
    fil = filters[2]
    apply_bc_u!(state.u, T(0), dns.setup)
    v = fil.Φ(state.u, fil.setup, fil.compression)
    apply_bc_u!(v, T(0), fil.setup)
    Fv = momentum(v, nothing, T(0), fil.setup)
    apply_bc_u!(Fv, T(0), fil.setup)
    PFv = project(Fv, fil.setup; psolver = fil.psolver)
    apply_bc_u!(PFv, T(0), fil.setup)
    F = momentum(state.u, nothing, T(0), dns.setup)
    apply_bc_u!(F, T(0), dns.setup)
    PF = project(F, dns.setup; dns.psolver)
    apply_bc_u!(PF, T(0), dns.setup)
    ΦPF = fil.Φ(PF, fil.setup, fil.compression)
    apply_bc_u!(ΦPF, T(0), fil.setup)
    c = ΦPF .- PFv
    apply_bc_u!(c, T(0), fil.setup)

    # Make plots
    path = "$output/priorfields"
    ispath(path) || mkpath(path)
    makeplot(field, setup, title, name) = save(
        "$path/$name.png",
        fieldplot(
            (; u = field, t = T(0));
            setup,
            title,
            docolorbar = false,
            size = (500, 500),
        ),
    )
    makeplot(u₀, dns.setup, "u₀", "ustart")
    makeplot(state.u, dns.setup, "u", "u")
    makeplot(v, fil.setup, "ū", "v")
    makeplot(PF, dns.setup, "PF(u)", "PFu")
    makeplot(PFv, fil.setup, "P̄F̄(ū)", "PFv")
    makeplot(ΦPF, fil.setup, "ΦPF(u)", "PhiPFu")
    makeplot(c, fil.setup, "c(u, ū)", "c")
end

Plot 3D fields

Contour plots in 3D only work with GLMakie. For using GLMakie on headless servers, see https://docs.makie.org/stable/explanations/headless/#glmakie

julia
GLMakie.activate!()

Make plots

julia
D == 3 && with_theme() do
    path = "$output/priorfields/lambda2"
    ispath(path) || mkpath(path)
    function makeplot(field, setup, name)
        name = "$path/$name.png"
        save(
            name,
            fieldplot(
                (; u = field, t = T(0));
                setup,
                fieldname = :eig2field,
                levels = LinRange(T(4), T(12), 10),
                docolorbar = false,
                size = (600, 600),
            ),
        )
        try
            # Trim whitespace with ImageMagick
            run(`convert $name -trim $name`)
        catch e
            @warn """
            ImageMagick not found.
            Skipping image trimming.
            Install from <https://imagemagick.org/>.
            """
        end
    end
    makeplot(u₀, dns.setup, "Re$(Int(Re))_start") # Requires docopy = true in solve
    makeplot(state.u, dns.setup, "Re$(Int(Re))_end")
    i = 3
    makeplot(
        filters[i].Φ(state.u, filters[i].setup, filters[i].compression),
        filters[i].setup,
        "Re$(Int(Re))_end_filtered",
    )
end

Compute average quantities

julia
let
    path = "$output/prioranalysis"
    ispath(path) || mkpath(path)
    open("$path/averages_$(D)D.txt", "w") do io
        println(io, \t\tM\tDu\tPv\tPc\tc")
        for o in outputs.obs
            nt = length(o.t)
            Dv = sum(o.Dv) / nt
            Pc = sum(o.Pc) / nt
            Pv = sum(o.Pv) / nt
            c = sum(o.c) / nt
            @printf(
                io,
                "%s\t%d^%d\t%.2g\t%.2g\t%.2g\t%.2g\n",
                # "%s &\t\$%d^%d\$ &\t\$%.2g\$ &\t\$%.2g\$ &\t\$%.2g\$ &\t\$%.2g\$\n",
                typeof(o.Φ),
                o.Mα,
                D,
                Dv,
                Pv,
                Pc,
                c
            )
        end
    end
end

Plot spectra

To free up memory in 3D (remove psolver_spectral FFT arrays)

julia
dns = (; dns.setup)
filters = map(filters) do f
    (; f.Φ, f.setup, f.compression)
end
fig = lines([1, 2, 3])
clean()

Plot predicted spectra

julia
CairoMakie.activate!()
with_theme(; palette = (; color = ["#3366cc", "#cc0000", "#669900", "#ffcc00"])) do
    fields = [state.u, u₀, (f.Φ(state.u, f.setup, f.compression) for f in filters)...]
    setups = [dns.setup, dns.setup, (f.setup for f in filters)...]
    specs = map(fields, setups) do u, setup
        clean() # Free up memory
        (; dimension, xp, Ip) = setup.grid
        T = eltype(xp[1])
        D = dimension()
        K = size(Ip)  2
        up = u
        e = sum(up) do u
            u = u[Ip]
            uhat = fft(u)[ntuple-> 1:K[α], D)...]
            abs2.(uhat) ./ (2 * prod(size(u))^2)
        end
        (; A, κ, K) = spectral_stuff(setup) # Requires some memory
        e = A * reshape(e, :) # Dyadic binning
        ehat = Array(e) # Store spectrum on CPU
        (; κ, ehat)
    end
    kmax = maximum(specs[1].κ)
    # Build inertial slope above energy
    krange, slope, slopelabel = if D == 2
        [T(16), T(128)], -T(3), L"$\kappa^{-3}"
    elseif D == 3
        [T(16), T(100)], -T(5 / 3), L"$\kappa^{-5/3}"
    end
    slopeconst = maximum(specs[1].ehat ./ specs[1].κ .^ slope)
    offset = D == 2 ? 3 : 2
    inertia = offset .* slopeconst .* krange .^ slope
    # Nice ticks
    logmax = round(Int, log2(kmax + 1))
    xticks = T(2) .^ (0:logmax)
    # Make plot
    fig = Figure(; size = (500, 400))
    ax = Axis(
        fig[1, 1];
        xticks,
        xlabel = "κ",
        xscale = log10,
        yscale = log10,
        limits = (1, kmax, T(1e-8), T(1)),
        title = "Kinetic energy ($(D)D)",
    )
    plotparts(i) = specs[i].κ, specs[i].ehat
    lines!(ax, plotparts(1)...; color = Cycled(1), label = "DNS")
    lines!(ax, plotparts(2)...; color = Cycled(4), label = "DNS, t = 0")
    lines!(ax, plotparts(3)...; color = Cycled(2), label = "Filtered DNS (FA)")
    lines!(ax, plotparts(4)...; color = Cycled(2))
    lines!(ax, plotparts(5)...; color = Cycled(2))
    lines!(ax, plotparts(6)...; color = Cycled(3), label = "Filtered DNS (VA)")
    lines!(ax, plotparts(7)...; color = Cycled(3))
    lines!(ax, plotparts(8)...; color = Cycled(3))
    lines!(ax, krange, inertia; color = Cycled(1), label = slopelabel, linestyle = :dash)
    axislegend(ax; position = :lb)
    autolimits!(ax)
    if D == 2
        limits!(ax, (T(0.8), T(800)), (T(1e-10), T(1)))
    elseif D == 3
        limits!(ax, (T(8e-1), T(200)), (T(4e-5), T(1.5e0)))
    end
    path = "$output/prioranalysis"
    ispath(path) || mkpath(path)
    save("$path/spectra_$(D)D_dyadic_Re$(Int(Re)).pdf", fig)
    fig
end
clean()

This page was generated using Literate.jl.