Kelvin-Helmholtz instability
This example simulates a simple 2D Kelvin-Helmholtz instability and is based on the similar Oceananigans example.
Before starting, make sure you have the required packages installed for this example, which can be done with
using Pkg
pkg"add Oceananigans, Oceanostics, CairoMakie, Rasters"
Model and simulation setup
We begin by creating a model with an isotropic diffusivity and fifth-order advection on a xz
128² grid using a buoyancy b
as the active scalar. We'll work here with nondimensional quantities.
using Oceananigans
N = 128
L = 10
grid = RectilinearGrid(size=(N, N), x=(-L/2, +L/2), z=(-L/2, +L/2), topology=(Periodic, Flat, Bounded))
model = NonhydrostaticModel(; grid, timestepper = :RungeKutta3,
advection = UpwindBiased(order=5),
closure = ScalarDiffusivity(ν=2e-5, κ=2e-5),
buoyancy = BuoyancyTracer(), tracers = :b)
NonhydrostaticModel{CPU, RectilinearGrid}(time = 0 seconds, iteration = 0)
├── grid: 128×1×128 RectilinearGrid{Float64, Periodic, Flat, Bounded} on CPU with 3×0×3 halo
├── timestepper: RungeKutta3TimeStepper
├── advection scheme: UpwindBiased(order=5)
├── tracers: b
├── closure: ScalarDiffusivity{ExplicitTimeDiscretization}(ν=2.0e-5, κ=(b=2.0e-5,))
├── buoyancy: BuoyancyTracer with ĝ = NegativeZDirection()
└── coriolis: Nothing
We use hyperbolic tangent functions for the initial conditions and set the maximum Richardson number below the threshold of 1/4. We also add some grid-scale small-amplitude noise to u
to kick the instability off:
noise(x, z) = 2e-2 * randn()
shear_flow(x, z) = tanh(z) + noise(x, z)
Ri₀ = 0.1; h = 1/4
stratification(x, z) = h * Ri₀ * tanh(z / h)
set!(model, u=shear_flow, b=stratification)
Next create an adaptive-time-step simulation using the model above:
simulation = Simulation(model, Δt=0.1, stop_time=100)
wizard = TimeStepWizard(cfl=0.8, max_Δt=1)
simulation.callbacks[:wizard] = Callback(wizard, IterationInterval(2))
Callback of TimeStepWizard(cfl=0.8, max_Δt=1.0, min_Δt=0.0) on IterationInterval(2)
Model diagnostics
We set-up a progress messenger using the TimedMessenger
, which displays, among other information, the time step duration
using Oceanostics
progress = ProgressMessengers.TimedMessenger()
simulation.callbacks[:progress] = Callback(progress, IterationInterval(200))
Callback of Oceanostics.ProgressMessengers.TimedMessenger{Oceanostics.ProgressMessengers.AbstractProgressMessenger} on IterationInterval(200)
We can also define some useful diagnostics for of the flow, starting with the RichardsonNumber
Ri = RichardsonNumber(model)
KernelFunctionOperation at (Center, Center, Face)
├── grid: 128×1×128 RectilinearGrid{Float64, Periodic, Flat, Bounded} on CPU with 3×0×3 halo
├── kernel_function: richardson_number_ccf (generic function with 1 method)
└── arguments: ("128×1×128 Field{Face, Center, Center} on RectilinearGrid on CPU", "128×1×128 Field{Center, Face, Center} on RectilinearGrid on CPU", "128×1×129 Field{Center, Center, Face} on RectilinearGrid on CPU", "128×1×128 Field{Center, Center, Center} on RectilinearGrid on CPU", "Tuple{Int64, Int64, Int64}")
We also set-up the QVelocityGradientTensorInvariant
, which is usually used for visualizing vortices in the flow:
Q = QVelocityGradientTensorInvariant(model)
KernelFunctionOperation at (Center, Center, Center)
├── grid: 128×1×128 RectilinearGrid{Float64, Periodic, Flat, Bounded} on CPU with 3×0×3 halo
├── kernel_function: Q_velocity_gradient_tensor_invariant_ccc (generic function with 1 method)
└── arguments: ("128×1×128 Field{Face, Center, Center} on RectilinearGrid on CPU", "128×1×128 Field{Center, Face, Center} on RectilinearGrid on CPU", "128×1×129 Field{Center, Center, Face} on RectilinearGrid on CPU")
Q is one of the velocity gradient tensor invariants and it measures the amount of vorticity versus the strain in the flow and, when it's positive, indicates a vortex. This method of vortex visualization is called the Q-criterion.
Let's also keep track of the amount of buoyancy mixing by measuring the buoyancy variance dissipation rate and diffusive term. When volume-integrated, these two quantities should be equal.
∫χᴰ = Integral(TracerVarianceDissipationRate(model, :b))
∫χ = Integral(TracerVarianceDiffusiveTerm(model, :b))
Integral of BinaryOperation at (Center, Center, Center) over dims (1, 2, 3)
└── operand: BinaryOperation at (Center, Center, Center)
└── grid: 128×1×128 RectilinearGrid{Float64, Periodic, Flat, Bounded} on CPU with 3×0×3 halo
Now we write these quantities, along with b
, to a NetCDF:
output_fields = (; Ri, Q, model.tracers.b, ∫χ, ∫χᴰ)
using NCDatasets
filename = "kelvin_helmholtz"
simulation.output_writers[:nc] = NetCDFWriter(model, output_fields,
filename = joinpath(@__DIR__, filename),
schedule = TimeInterval(1),
overwrite_existing = true)
NetCDFWriter scheduled on TimeInterval(1 second):
├── filepath: build/generated/kelvin_helmholtz.nc
├── dimensions: time(0), x_faa(128), x_caa(128), z_aaf(129), z_aac(128)
├── 5 outputs: (Q, ∫χᴰ, Ri, b, ∫χ)
└── array type: Array{Float32}
├── file_splitting: NoFileSplitting
└── file size: 24.7 KiB
Run the simulation and process results
To run the simulation:
run!(simulation)
[ Info: Initializing simulation...
┌ Info: iter = 0, [000.00%] time = 0 seconds, Δt = 58.982 ms, walltime = 27.396 seconds, walltime / timestep = 0 seconds
└ |u⃗|ₘₐₓ = [1.04e+00, 0.00e+00, 3.08e-02] m/s, advective CFL = 0.8, diffusive CFL = 0.00019, νₘₐₓ = 2e-05 m²/s
[ Info: ... simulation initialization complete (13.286 seconds)
[ Info: Executing initial time step...
[ Info: ... initial time step complete (4.831 seconds).
┌ Info: iter = 200, [011.79%] time = 11.790 seconds, Δt = 60.778 ms, walltime = 43.030 seconds, walltime / timestep = 78.168 ms
└ |u⃗|ₘₐₓ = [1.03e+00, 0.00e+00, 3.26e-02] m/s, advective CFL = 0.8, diffusive CFL = 0.0002, νₘₐₓ = 2e-05 m²/s
┌ Info: iter = 400, [023.55%] time = 23.548 seconds, Δt = 60.895 ms, walltime = 45.827 seconds, walltime / timestep = 13.985 ms
└ |u⃗|ₘₐₓ = [1.02e+00, 0.00e+00, 3.03e-02] m/s, advective CFL = 0.8, diffusive CFL = 0.0002, νₘₐₓ = 2e-05 m²/s
┌ Info: iter = 600, [035.30%] time = 35.303 seconds, Δt = 60.466 ms, walltime = 48.548 seconds, walltime / timestep = 13.608 ms
└ |u⃗|ₘₐₓ = [1.03e+00, 0.00e+00, 5.55e-02] m/s, advective CFL = 0.8, diffusive CFL = 0.0002, νₘₐₓ = 2e-05 m²/s
┌ Info: iter = 800, [046.86%] time = 46.862 seconds, Δt = 57.287 ms, walltime = 51.168 seconds, walltime / timestep = 13.096 ms
└ |u⃗|ₘₐₓ = [1.06e+00, 0.00e+00, 1.47e-01] m/s, advective CFL = 0.8, diffusive CFL = 0.00019, νₘₐₓ = 2e-05 m²/s
┌ Info: iter = 1000, [057.30%] time = 57.302 seconds, Δt = 50.083 ms, walltime = 53.777 seconds, walltime / timestep = 13.045 ms
└ |u⃗|ₘₐₓ = [1.15e+00, 0.00e+00, 3.55e-01] m/s, advective CFL = 0.8, diffusive CFL = 0.00016, νₘₐₓ = 2e-05 m²/s
┌ Info: iter = 1200, [066.83%] time = 1.114 minutes, Δt = 48.915 ms, walltime = 56.211 seconds, walltime / timestep = 12.170 ms
└ |u⃗|ₘₐₓ = [1.17e+00, 0.00e+00, 3.89e-01] m/s, advective CFL = 0.8, diffusive CFL = 0.00016, νₘₐₓ = 2e-05 m²/s
┌ Info: iter = 1400, [076.63%] time = 1.277 minutes, Δt = 52.275 ms, walltime = 58.723 seconds, walltime / timestep = 12.560 ms
└ |u⃗|ₘₐₓ = [1.12e+00, 0.00e+00, 3.07e-01] m/s, advective CFL = 0.8, diffusive CFL = 0.00017, νₘₐₓ = 2e-05 m²/s
┌ Info: iter = 1600, [087.05%] time = 1.451 minutes, Δt = 52.588 ms, walltime = 1.022 minutes, walltime / timestep = 13.067 ms
└ |u⃗|ₘₐₓ = [1.12e+00, 0.00e+00, 3.01e-01] m/s, advective CFL = 0.8, diffusive CFL = 0.00017, νₘₐₓ = 2e-05 m²/s
┌ Info: iter = 1800, [096.84%] time = 1.614 minutes, Δt = 49.267 ms, walltime = 1.064 minutes, walltime / timestep = 12.404 ms
└ |u⃗|ₘₐₓ = [1.17e+00, 0.00e+00, 3.76e-01] m/s, advective CFL = 0.8, diffusive CFL = 0.00016, νₘₐₓ = 2e-05 m²/s
[ Info: Simulation is stopping after running for 0 seconds.
[ Info: Simulation time 1.667 minutes equals or exceeds stop time 1.667 minutes.
Now we'll read the results using Rasters.jl, which works somewhat similarly to Python's Xarray and can speed-up the work the workflow
using Rasters
ds = RasterStack(simulation.output_writers[:nc].filepath)
┌ 128×128×129×101×128 RasterStack ┐
├─────────────────────────────────┴────────────────────────────────────── dims ┐
↓ x_caa Sampled{Float32} [-4.9609375, -4.8828125, …, 4.8828125, 4.9609375] ForwardOrdered Regular Points,
→ z_aac Sampled{Float32} [-4.9609375, -4.8828125, …, 4.8828125, 4.9609375] ForwardOrdered Regular Points,
↗ z_aaf Sampled{Float32} [-5.0, -4.921875, …, 4.921875, 5.0] ForwardOrdered Regular Points,
⬔ Ti Sampled{Float64} [0.0, 1.0, …, 99.0, 100.0] ForwardOrdered Regular Points,
◩ x_faa Sampled{Float32} [-5.0, -4.921875, …, 4.84375, 4.921875] ForwardOrdered Regular Points
├────────────────────────────────────────────────────────────────────── layers ┤
:Δx_caa eltype: Union{Missing, Float32} dims: x_caa size: 128
:Δx_faa eltype: Union{Missing, Float32} dims: x_faa size: 128
:Δz_aac eltype: Union{Missing, Float32} dims: z_aac size: 128
:Δz_aaf eltype: Union{Missing, Float32} dims: z_aaf size: 129
:Q eltype: Union{Missing, Float32} dims: x_caa, z_aac, Ti size: 128×128×101
:Ri eltype: Union{Missing, Float32} dims: x_caa, z_aaf, Ti size: 128×129×101
:b eltype: Union{Missing, Float32} dims: x_caa, z_aac, Ti size: 128×128×101
:∫χ eltype: Union{Missing, Float32} dims: Ti size: 101
:∫χᴰ eltype: Union{Missing, Float32} dims: Ti size: 101
├──────────────────────────────────────────────────────────────────── metadata ┤
Metadata{Rasters.NCDsource} of Dict{String, Any} with 6 entries:
"interval" => 1.0
"Oceananigans" => "This file was generated using "
"Julia" => "This file was generated using Julia Version 1.10.9…
"output time interval" => "Output was saved every 1 second."
"date" => "This file was generated on 2025-03-19T23:08:24.592…
"schedule" => "TimeInterval"
├────────────────────────────────────────────────────────────────────── raster ┤
missingval: missing
extent: Extent(x_caa = (-4.9609375f0, 4.9609375f0), z_aac = (-4.9609375f0, 4.9609375f0), z_aaf = (-5.0f0, 5.0f0), Ti = (0.0, 100.0), x_faa = (-5.0f0, 4.921875f0))
└──────────────────────────────────────────────────────────────────────────────┘
We now use Makie to create the figure and its axes
using CairoMakie
set_theme!(Theme(fontsize = 24))
fig = Figure()
kwargs = (xlabel="x", ylabel="z", height=150, width=250)
ax1 = Axis(fig[2, 1]; title = "Ri", kwargs...)
ax2 = Axis(fig[2, 2]; title = "Q", kwargs...)
ax3 = Axis(fig[2, 3]; title = "b", kwargs...);
ERROR: LoadError: UndefVarError: `IntervalBox` not defined
Stacktrace:
[1] top-level scope
@ ~/.julia/packages/TaylorSeries/O28jb/ext/TaylorSeriesIAExt.jl:191
[2] include
@ ./Base.jl:495 [inlined]
[3] include_package_for_output(pkg::Base.PkgId, input::String, depot_path::Vector{String}, dl_load_path::Vector{String}, load_path::Vector{String}, concrete_deps::Vector{Pair{Base.PkgId, UInt128}}, source::String)
@ Base ./loading.jl:2292
[4] top-level scope
@ stdin:4
in expression starting at /home/runner/.julia/packages/TaylorSeries/O28jb/ext/TaylorSeriesIAExt.jl:1
in expression starting at stdin:4
┌ Error: Error during loading of extension TaylorSeriesIAExt of TaylorSeries, use `Base.retry_load_extensions()` to retry.
│ exception =
│ 1-element ExceptionStack:
│ Failed to precompile TaylorSeriesIAExt [ed7ef945-33a4-511e-97fe-2b89c7a130ca] to "/home/runner/.julia/compiled/v1.10/TaylorSeriesIAExt/jl_i2yLst".
│ Stacktrace:
│ [1] error(s::String)
│ @ Base ./error.jl:35
│ [2] compilecache(pkg::Base.PkgId, path::String, internal_stderr::IO, internal_stdout::IO, keep_loaded_modules::Bool)
│ @ Base ./loading.jl:2539
│ [3] compilecache
│ @ ./loading.jl:2411 [inlined]
│ [4] (::Base.var"#969#970"{Base.PkgId})()
│ @ Base ./loading.jl:2044
│ [5] mkpidlock(f::Base.var"#969#970"{Base.PkgId}, at::String, pid::Int32; kwopts::@Kwargs{stale_age::Int64, wait::Bool})
│ @ FileWatching.Pidfile /opt/hostedtoolcache/julia/1.10.9/x64/share/julia/stdlib/v1.10/FileWatching/src/pidfile.jl:93
│ [6] #mkpidlock#6
│ @ /opt/hostedtoolcache/julia/1.10.9/x64/share/julia/stdlib/v1.10/FileWatching/src/pidfile.jl:88 [inlined]
│ [7] trymkpidlock(::Function, ::Vararg{Any}; kwargs::@Kwargs{stale_age::Int64})
│ @ FileWatching.Pidfile /opt/hostedtoolcache/julia/1.10.9/x64/share/julia/stdlib/v1.10/FileWatching/src/pidfile.jl:111
│ [8] #invokelatest#2
│ @ ./essentials.jl:894 [inlined]
│ [9] invokelatest
│ @ ./essentials.jl:889 [inlined]
│ [10] maybe_cachefile_lock(f::Base.var"#969#970"{Base.PkgId}, pkg::Base.PkgId, srcpath::String; stale_age::Int64)
│ @ Base ./loading.jl:3054
│ [11] maybe_cachefile_lock
│ @ ./loading.jl:3051 [inlined]
│ [12] _require(pkg::Base.PkgId, env::Nothing)
│ @ Base ./loading.jl:2040
│ [13] __require_prelocked(uuidkey::Base.PkgId, env::Nothing)
│ @ Base ./loading.jl:1882
│ [14] #invoke_in_world#3
│ @ ./essentials.jl:926 [inlined]
│ [15] invoke_in_world
│ @ ./essentials.jl:923 [inlined]
│ [16] _require_prelocked
│ @ ./loading.jl:1873 [inlined]
│ [17] _require_prelocked
│ @ ./loading.jl:1872 [inlined]
│ [18] run_extension_callbacks(extid::Base.ExtensionId)
│ @ Base ./loading.jl:1365
│ [19] run_extension_callbacks(pkgid::Base.PkgId)
│ @ Base ./loading.jl:1400
│ [20] run_package_callbacks(modkey::Base.PkgId)
│ @ Base ./loading.jl:1224
│ [21] _tryrequire_from_serialized(modkey::Base.PkgId, path::String, ocachepath::String, sourcepath::String, depmods::Vector{Any})
│ @ Base ./loading.jl:1557
│ [22] _require_search_from_serialized(pkg::Base.PkgId, sourcepath::String, build_id::UInt128)
│ @ Base ./loading.jl:1644
│ [23] _require(pkg::Base.PkgId, env::String)
│ @ Base ./loading.jl:2008
│ [24] __require_prelocked(uuidkey::Base.PkgId, env::String)
│ @ Base ./loading.jl:1882
│ [25] #invoke_in_world#3
│ @ ./essentials.jl:926 [inlined]
│ [26] invoke_in_world
│ @ ./essentials.jl:923 [inlined]
│ [27] _require_prelocked(uuidkey::Base.PkgId, env::String)
│ @ Base ./loading.jl:1873
│ [28] macro expansion
│ @ ./loading.jl:1860 [inlined]
│ [29] macro expansion
│ @ ./lock.jl:267 [inlined]
│ [30] __require(into::Module, mod::Symbol)
│ @ Base ./loading.jl:1823
│ [31] #invoke_in_world#3
│ @ ./essentials.jl:926 [inlined]
│ [32] invoke_in_world
│ @ ./essentials.jl:923 [inlined]
│ [33] require(into::Module, mod::Symbol)
│ @ Base ./loading.jl:1816
│ [34] eval
│ @ ./boot.jl:385 [inlined]
│ [35] #61
│ @ ~/.julia/packages/Documenter/QLA7E/src/expander_pipeline.jl:806 [inlined]
│ [36] cd(f::Documenter.var"#61#63"{Module, Expr}, dir::String)
│ @ Base.Filesystem ./file.jl:112
│ [37] (::Documenter.var"#60#62"{Documenter.Page, Module, Expr})()
│ @ Documenter ~/.julia/packages/Documenter/QLA7E/src/expander_pipeline.jl:805
│ [38] (::IOCapture.var"#5#9"{DataType, Documenter.var"#60#62"{Documenter.Page, Module, Expr}, IOContext{Base.PipeEndpoint}, IOContext{Base.PipeEndpoint}, IOContext{Base.PipeEndpoint}, IOContext{Base.PipeEndpoint}})()
│ @ IOCapture ~/.julia/packages/IOCapture/Y5rEA/src/IOCapture.jl:170
│ [39] with_logstate(f::Function, logstate::Any)
│ @ Base.CoreLogging ./logging.jl:515
│ [40] with_logger
│ @ ./logging.jl:627 [inlined]
│ [41] capture(f::Documenter.var"#60#62"{Documenter.Page, Module, Expr}; rethrow::Type, color::Bool, passthrough::Bool, capture_buffer::IOBuffer, io_context::Vector{Any})
│ @ IOCapture ~/.julia/packages/IOCapture/Y5rEA/src/IOCapture.jl:167
│ [42] runner(::Type{Documenter.Expanders.ExampleBlocks}, node::MarkdownAST.Node{Nothing}, page::Documenter.Page, doc::Documenter.Document)
│ @ Documenter ~/.julia/packages/Documenter/QLA7E/src/expander_pipeline.jl:804
│ [43] dispatch(::Type{Documenter.Expanders.ExpanderPipeline}, ::MarkdownAST.Node{Nothing}, ::Vararg{Any})
│ @ Documenter.Selectors ~/.julia/packages/Documenter/QLA7E/src/utilities/Selectors.jl:170
│ [44] expand(doc::Documenter.Document)
│ @ Documenter ~/.julia/packages/Documenter/QLA7E/src/expander_pipeline.jl:22
│ [45] runner(::Type{Documenter.Builder.ExpandTemplates}, doc::Documenter.Document)
│ @ Documenter ~/.julia/packages/Documenter/QLA7E/src/builder_pipeline.jl:224
│ [46] dispatch(::Type{Documenter.Builder.DocumentPipeline}, x::Documenter.Document)
│ @ Documenter.Selectors ~/.julia/packages/Documenter/QLA7E/src/utilities/Selectors.jl:170
│ [47] #90
│ @ ~/.julia/packages/Documenter/QLA7E/src/makedocs.jl:274 [inlined]
│ [48] withenv(::Documenter.var"#90#92"{Documenter.Document}, ::Pair{String, Nothing}, ::Vararg{Pair{String, Nothing}})
│ @ Base ./env.jl:257
│ [49] #89
│ @ ~/.julia/packages/Documenter/QLA7E/src/makedocs.jl:273 [inlined]
│ [50] cd(f::Documenter.var"#89#91"{Documenter.Document}, dir::String)
│ @ Base.Filesystem ./file.jl:112
│ [51] #makedocs#88
│ @ ~/.julia/packages/Documenter/QLA7E/src/makedocs.jl:272 [inlined]
│ [52] top-level scope
│ @ ~/work/Oceanostics.jl/Oceanostics.jl/docs/make.jl:45
│ [53] include(mod::Module, _path::String)
│ @ Base ./Base.jl:495
│ [54] exec_options(opts::Base.JLOptions)
│ @ Base ./client.jl:323
└ @ Base loading.jl:1371
ERROR: LoadError: UndefVarError: `IntervalBox` not defined
Stacktrace:
[1] top-level scope
@ ~/.julia/packages/TaylorSeries/O28jb/ext/TaylorSeriesIAExt.jl:191
[2] include
@ ./Base.jl:495 [inlined]
[3] include_package_for_output(pkg::Base.PkgId, input::String, depot_path::Vector{String}, dl_load_path::Vector{String}, load_path::Vector{String}, concrete_deps::Vector{Pair{Base.PkgId, UInt128}}, source::String)
@ Base ./loading.jl:2292
[4] top-level scope
@ stdin:4
in expression starting at /home/runner/.julia/packages/TaylorSeries/O28jb/ext/TaylorSeriesIAExt.jl:1
in expression starting at stdin:4
┌ Error: Error during loading of extension TaylorSeriesIAExt of TaylorSeries, use `Base.retry_load_extensions()` to retry.
│ exception =
│ 1-element ExceptionStack:
│ Failed to precompile TaylorSeriesIAExt [ed7ef945-33a4-511e-97fe-2b89c7a130ca] to "/home/runner/.julia/compiled/v1.10/TaylorSeriesIAExt/jl_ymKMYL".
│ Stacktrace:
│ [1] error(s::String)
│ @ Base ./error.jl:35
│ [2] compilecache(pkg::Base.PkgId, path::String, internal_stderr::IO, internal_stdout::IO, keep_loaded_modules::Bool)
│ @ Base ./loading.jl:2539
│ [3] compilecache
│ @ ./loading.jl:2411 [inlined]
│ [4] (::Base.var"#969#970"{Base.PkgId})()
│ @ Base ./loading.jl:2044
│ [5] mkpidlock(f::Base.var"#969#970"{Base.PkgId}, at::String, pid::Int32; kwopts::@Kwargs{stale_age::Int64, wait::Bool})
│ @ FileWatching.Pidfile /opt/hostedtoolcache/julia/1.10.9/x64/share/julia/stdlib/v1.10/FileWatching/src/pidfile.jl:93
│ [6] #mkpidlock#6
│ @ /opt/hostedtoolcache/julia/1.10.9/x64/share/julia/stdlib/v1.10/FileWatching/src/pidfile.jl:88 [inlined]
│ [7] trymkpidlock(::Function, ::Vararg{Any}; kwargs::@Kwargs{stale_age::Int64})
│ @ FileWatching.Pidfile /opt/hostedtoolcache/julia/1.10.9/x64/share/julia/stdlib/v1.10/FileWatching/src/pidfile.jl:111
│ [8] #invokelatest#2
│ @ ./essentials.jl:894 [inlined]
│ [9] invokelatest
│ @ ./essentials.jl:889 [inlined]
│ [10] maybe_cachefile_lock(f::Base.var"#969#970"{Base.PkgId}, pkg::Base.PkgId, srcpath::String; stale_age::Int64)
│ @ Base ./loading.jl:3054
│ [11] maybe_cachefile_lock
│ @ ./loading.jl:3051 [inlined]
│ [12] _require(pkg::Base.PkgId, env::Nothing)
│ @ Base ./loading.jl:2040
│ [13] __require_prelocked(uuidkey::Base.PkgId, env::Nothing)
│ @ Base ./loading.jl:1882
│ [14] #invoke_in_world#3
│ @ ./essentials.jl:926 [inlined]
│ [15] invoke_in_world
│ @ ./essentials.jl:923 [inlined]
│ [16] _require_prelocked
│ @ ./loading.jl:1873 [inlined]
│ [17] _require_prelocked
│ @ ./loading.jl:1872 [inlined]
│ [18] run_extension_callbacks(extid::Base.ExtensionId)
│ @ Base ./loading.jl:1365
│ [19] run_extension_callbacks(pkgid::Base.PkgId)
│ @ Base ./loading.jl:1400
│ [20] run_package_callbacks(modkey::Base.PkgId)
│ @ Base ./loading.jl:1224
│ [21] _tryrequire_from_serialized(modkey::Base.PkgId, path::String, ocachepath::String, sourcepath::String, depmods::Vector{Any})
│ @ Base ./loading.jl:1557
│ [22] _require_search_from_serialized(pkg::Base.PkgId, sourcepath::String, build_id::UInt128)
│ @ Base ./loading.jl:1644
│ [23] _require(pkg::Base.PkgId, env::String)
│ @ Base ./loading.jl:2008
│ [24] __require_prelocked(uuidkey::Base.PkgId, env::String)
│ @ Base ./loading.jl:1882
│ [25] #invoke_in_world#3
│ @ ./essentials.jl:926 [inlined]
│ [26] invoke_in_world
│ @ ./essentials.jl:923 [inlined]
│ [27] _require_prelocked(uuidkey::Base.PkgId, env::String)
│ @ Base ./loading.jl:1873
│ [28] macro expansion
│ @ ./loading.jl:1860 [inlined]
│ [29] macro expansion
│ @ ./lock.jl:267 [inlined]
│ [30] __require(into::Module, mod::Symbol)
│ @ Base ./loading.jl:1823
│ [31] #invoke_in_world#3
│ @ ./essentials.jl:926 [inlined]
│ [32] invoke_in_world
│ @ ./essentials.jl:923 [inlined]
│ [33] require(into::Module, mod::Symbol)
│ @ Base ./loading.jl:1816
│ [34] include
│ @ ./Base.jl:495 [inlined]
│ [35] include_package_for_output(pkg::Base.PkgId, input::String, depot_path::Vector{String}, dl_load_path::Vector{String}, load_path::Vector{String}, concrete_deps::Vector{Pair{Base.PkgId, UInt128}}, source::String)
│ @ Base ./loading.jl:2292
│ [36] top-level scope
│ @ stdin:4
│ [37] eval
│ @ ./boot.jl:385 [inlined]
│ [38] include_string(mapexpr::typeof(identity), mod::Module, code::String, filename::String)
│ @ Base ./loading.jl:2146
│ [39] include_string
│ @ ./loading.jl:2156 [inlined]
│ [40] exec_options(opts::Base.JLOptions)
│ @ Base ./client.jl:321
│ [41] _start()
│ @ Base ./client.jl:557
└ @ Base loading.jl:1371
Next we use Observable
s to lift the values and plot heatmaps and their colorbars
n = Observable(1)
Riₙ = @lift set(ds.Ri[Ti=$n, y_aca=Near(0)], :x_caa => X, :z_aaf => Z)
hm1 = heatmap!(ax1, Riₙ; colormap = :bwr, colorrange = (-1, +1))
Colorbar(fig[3, 1], hm1, vertical=false, height=8)
Qₙ = @lift set(ds.Q[Ti=$n, y_aca=Near(0)], :x_caa => X, :z_aac => Z)
hm2 = heatmap!(ax2, Qₙ; colormap = :inferno, colorrange = (0, 0.2))
Colorbar(fig[3, 2], hm2, vertical=false, height=8)
bₙ = @lift set(ds.b[Ti=$n, y_aca=Near(0)], :x_caa => X, :z_aac => Z)
hm3 = heatmap!(ax3, bₙ; colormap = :balance, colorrange = (-2.5e-2, +2.5e-2))
Colorbar(fig[3, 3], hm3, vertical=false, height=8);
┌ Warning: (DimensionalData.Dimensions.Dim{:y_aca},) dims were not found in object.
└ @ DimensionalData.Dimensions ~/.julia/packages/DimensionalData/a0k29/src/Dimensions/primitives.jl:849
┌ Warning: (DimensionalData.Dimensions.Dim{:y_aca},) dims were not found in object.
└ @ DimensionalData.Dimensions ~/.julia/packages/DimensionalData/a0k29/src/Dimensions/primitives.jl:849
┌ Warning: (DimensionalData.Dimensions.Dim{:y_aca},) dims were not found in object.
└ @ DimensionalData.Dimensions ~/.julia/packages/DimensionalData/a0k29/src/Dimensions/primitives.jl:849
We now plot the time evolution of our integrated quantities
axb = Axis(fig[4, 1:3]; xlabel="Time", height=100)
times = dims(ds, :Ti)
lines!(axb, Array(times), Array(ds.∫χ), label = "∫χdV")
lines!(axb, Array(times), Array(ds.∫χᴰ), label = "∫χᴰdV", linestyle=:dash)
axislegend(position=:lb, labelsize=14)
Makie.Legend()
Now we mark the time by placing a vertical line in the bottom panel and adding a helpful title
tₙ = @lift times[$n]
vlines!(axb, tₙ, color=:black, linestyle=:dash)
title = @lift "Time = " * string(round(times[$n], digits=2))
fig[1, 1:3] = Label(fig, title, fontsize=24, tellwidth=false);
Finally, we adjust the figure dimensions to fit all the panels and record a movie
resize_to_layout!(fig)
@info "Animating..."
record(fig, filename * ".mp4", 1:length(times), framerate=10) do i
n[] = i
end
"kelvin_helmholtz.mp4"
Similarly to the kinetic energy dissipation rate (see the Two-dimensional turbulence example), TracerVarianceDissipationRate
and TracerVarianceDiffusiveTerm
are implemented with a energy-conserving formulation, which means that (for NoFlux
boundary conditions) their volume-integral should be exactly (up to machine precision) the same.
This page was generated using Literate.jl.