Langmuir turbulence example
This example implements a Langmuir turbulence simulation similar to the one reported in section 4 of
This example demonstrates
How to run large eddy simulations with surface wave effects via the Craik-Leibovich approximation.
How to specify time- and horizontally-averaged output.
Install dependencies
First let's make sure we have all required packages installed.
using Pkg
pkg"add Oceananigans, CairoMakie, CUDA"
using Oceananigans
using Oceananigans.Units: minute, minutes, hours
using CUDA
Model set-up
To build the model, we specify the grid, Stokes drift, boundary conditions, and Coriolis parameter.
Domain and numerical grid specification
We use a modest resolution and the same total extent as Wagner et al. (2021),
grid = RectilinearGrid(GPU(), size=(128, 128, 64), extent=(128, 128, 64))
128×128×64 RectilinearGrid{Float64, Periodic, Periodic, Bounded} on CUDAGPU with 3×3×3 halo
├── Periodic x ∈ [0.0, 128.0) regularly spaced with Δx=1.0
├── Periodic y ∈ [0.0, 128.0) regularly spaced with Δy=1.0
└── Bounded z ∈ [-64.0, 0.0] regularly spaced with Δz=1.0
The Stokes Drift profile
The surface wave Stokes drift profile prescribed in Wagner et al. (2021), corresponds to a 'monochromatic' (that is, single-frequency) wave field.
A monochromatic wave field is characterized by its wavelength and amplitude (half the distance from wave crest to wave trough), which determine the wave frequency and the vertical scale of the Stokes drift profile.
using Oceananigans.BuoyancyFormulations: g_Earth
amplitude = 0.8 # m
wavelength = 60 # m
wavenumber = 2π / wavelength # m⁻¹
frequency = sqrt(g_Earth * wavenumber) # s⁻¹
# The vertical scale over which the Stokes drift of a monochromatic surface wave
# decays away from the surface is `1/2wavenumber`, or
const vertical_scale = wavelength / 4π
# Stokes drift velocity at the surface
const Uˢ = amplitude^2 * wavenumber * frequency # m s⁻¹
0.06791774197745354
The const
declarations ensure that Stokes drift functions compile on the GPU. To run this example on the CPU, replace GPU()
with CPU()
in the RectilinearGrid
constructor above.
The Stokes drift profile is
uˢ(z) = Uˢ * exp(z / vertical_scale)
uˢ (generic function with 1 method)
and its z
-derivative is
∂z_uˢ(z, t) = 1 / vertical_scale * Uˢ * exp(z / vertical_scale)
∂z_uˢ (generic function with 1 method)
Oceananigans implements the Craik-Leibovich approximation for surface wave effects using the Lagrangian-mean velocity field as its prognostic momentum variable. In other words, model.velocities.u
is the Lagrangian-mean $x$-velocity beneath surface waves. This differs from models that use the Eulerian-mean velocity field as a prognostic variable, but has the advantage that $u$ accounts for the total advection of tracers and momentum, and that $u = v = w = 0$ is a steady solution even when Coriolis forces are present. See the physics documentation for more information.
Finally, we note that the time-derivative of the Stokes drift must be provided if the Stokes drift and surface wave field undergoes forced changes in time. In this example, the Stokes drift is constant and thus the time-derivative of the Stokes drift is 0.
Boundary conditions
At the surface $z = 0$, Wagner et al. (2021) impose
τx = -3.72e-5 # m² s⁻², surface kinematic momentum flux
u_boundary_conditions = FieldBoundaryConditions(top = FluxBoundaryCondition(τx))
Oceananigans.FieldBoundaryConditions, with boundary conditions
├── west: DefaultBoundaryCondition (FluxBoundaryCondition: Nothing)
├── east: DefaultBoundaryCondition (FluxBoundaryCondition: Nothing)
├── south: DefaultBoundaryCondition (FluxBoundaryCondition: Nothing)
├── north: DefaultBoundaryCondition (FluxBoundaryCondition: Nothing)
├── bottom: DefaultBoundaryCondition (FluxBoundaryCondition: Nothing)
├── top: FluxBoundaryCondition: -3.72e-5
└── immersed: DefaultBoundaryCondition (FluxBoundaryCondition: Nothing)
Wagner et al. (2021) impose a linear buoyancy gradient N²
at the bottom along with a weak, destabilizing flux of buoyancy at the surface to faciliate spin-up from rest.
Jᵇ = 2.307e-8 # m² s⁻³, surface buoyancy flux
N² = 1.936e-5 # s⁻², initial and bottom buoyancy gradient
b_boundary_conditions = FieldBoundaryConditions(top = FluxBoundaryCondition(Jᵇ),
bottom = GradientBoundaryCondition(N²))
Oceananigans.FieldBoundaryConditions, with boundary conditions
├── west: DefaultBoundaryCondition (FluxBoundaryCondition: Nothing)
├── east: DefaultBoundaryCondition (FluxBoundaryCondition: Nothing)
├── south: DefaultBoundaryCondition (FluxBoundaryCondition: Nothing)
├── north: DefaultBoundaryCondition (FluxBoundaryCondition: Nothing)
├── bottom: GradientBoundaryCondition: 1.936e-5
├── top: FluxBoundaryCondition: 2.307e-8
└── immersed: DefaultBoundaryCondition (FluxBoundaryCondition: Nothing)
Note that Oceananigans uses "positive upward" conventions for all fluxes. In consequence, a negative flux at the surface drives positive velocities, and a positive flux of buoyancy drives cooling.
Coriolis parameter
Wagner et al. (2021) use
coriolis = FPlane(f=1e-4) # s⁻¹
FPlane{Float64}(f=0.0001)
which is typical for mid-latitudes on Earth.
Model instantiation
We are ready to build the model. We use a fifth-order Weighted Essentially Non-Oscillatory (WENO) advection scheme and the AnisotropicMinimumDissipation
model for large eddy simulation. Because our Stokes drift does not vary in $x, y$, we use UniformStokesDrift
, which expects Stokes drift functions of $z, t$ only.
model = NonhydrostaticModel(; grid, coriolis,
advection = WENO(order=9),
tracers = :b,
buoyancy = BuoyancyTracer(),
stokes_drift = UniformStokesDrift(∂z_uˢ=∂z_uˢ),
boundary_conditions = (u=u_boundary_conditions, b=b_boundary_conditions))
NonhydrostaticModel{GPU, RectilinearGrid}(time = 0 seconds, iteration = 0)
├── grid: 128×128×64 RectilinearGrid{Float64, Periodic, Periodic, Bounded} on CUDAGPU with 5×5×5 halo
├── timestepper: RungeKutta3TimeStepper
├── advection scheme: WENO{5, Float64, Float32}(order=9)
├── tracers: b
├── closure: Nothing
├── buoyancy: BuoyancyTracer with ĝ = NegativeZDirection()
└── coriolis: FPlane{Float64}(f=0.0001)
Initial conditions
We make use of random noise concentrated in the upper 4 meters for buoyancy and velocity initial conditions,
Ξ(z) = randn() * exp(z / 4)
Our initial condition for buoyancy consists of a surface mixed layer 33 m deep, a deep linear stratification, plus noise,
initial_mixed_layer_depth = 33 # m
stratification(z) = z < - initial_mixed_layer_depth ? N² * z : N² * (-initial_mixed_layer_depth)
bᵢ(x, y, z) = stratification(z) + 1e-1 * Ξ(z) * N² * model.grid.Lz
bᵢ (generic function with 1 method)
The simulation we reproduce from Wagner et al. (2021) is zero Lagrangian-mean velocity. This initial condition is consistent with a wavy, quiescent ocean suddenly impacted by winds. To this quiescent state we add noise scaled by the friction velocity to $u$ and $w$.
u★ = sqrt(abs(τx))
uᵢ(x, y, z) = u★ * 1e-1 * Ξ(z)
wᵢ(x, y, z) = u★ * 1e-1 * Ξ(z)
set!(model, u=uᵢ, w=wᵢ, b=bᵢ)
Setting up the simulation
simulation = Simulation(model, Δt=45.0, stop_time=4hours)
Simulation of NonhydrostaticModel{GPU, RectilinearGrid}(time = 0 seconds, iteration = 0)
├── Next time step: 45 seconds
├── Elapsed wall time: 0 seconds
├── Wall time per iteration: NaN days
├── Stop time: 4 hours
├── Stop iteration: Inf
├── Wall time limit: Inf
├── Minimum relative step: 0.0
├── Callbacks: OrderedDict with 4 entries:
│ ├── stop_time_exceeded => 4
│ ├── stop_iteration_exceeded => -
│ ├── wall_time_limit_exceeded => e
│ └── nan_checker => }
├── Output writers: OrderedDict with no entries
└── Diagnostics: OrderedDict with no entries
We use the TimeStepWizard
for adaptive time-stepping with a Courant-Freidrichs-Lewy (CFL) number of 1.0,
conjure_time_step_wizard!(simulation, cfl=1.0, max_Δt=1minute)
Nice progress messaging
We define a function that prints a helpful message with maximum absolute value of $u, v, w$ and the current wall clock time.
using Printf
function progress(simulation)
u, v, w = simulation.model.velocities
# Print a progress message
msg = @sprintf("i: %04d, t: %s, Δt: %s, umax = (%.1e, %.1e, %.1e) ms⁻¹, wall time: %s\n",
iteration(simulation),
prettytime(time(simulation)),
prettytime(simulation.Δt),
maximum(abs, u), maximum(abs, v), maximum(abs, w),
prettytime(simulation.run_wall_time))
@info msg
return nothing
end
simulation.callbacks[:progress] = Callback(progress, IterationInterval(20))
Callback of progress on IterationInterval(20)
Output
A field writer
We set up an output writer for the simulation that saves all velocity fields, tracer fields, and the subgrid turbulent diffusivity.
output_interval = 5minutes
fields_to_output = merge(model.velocities, model.tracers)
simulation.output_writers[:fields] =
JLD2Writer(model, fields_to_output,
schedule = TimeInterval(output_interval),
filename = "langmuir_turbulence_fields.jld2",
overwrite_existing = true)
JLD2Writer scheduled on TimeInterval(5 minutes):
├── filepath: langmuir_turbulence_fields.jld2
├── 4 outputs: (u, v, w, b)
├── array_type: Array{Float32}
├── including: [:grid, :coriolis, :buoyancy, :closure]
├── file_splitting: NoFileSplitting
└── file size: 45.0 KiB
An "averages" writer
We also set up output of time- and horizontally-averaged velocity field and momentum fluxes.
u, v, w = model.velocities
b = model.tracers.b
U = Average(u, dims=(1, 2))
V = Average(v, dims=(1, 2))
B = Average(b, dims=(1, 2))
wu = Average(w * u, dims=(1, 2))
wv = Average(w * v, dims=(1, 2))
simulation.output_writers[:averages] =
JLD2Writer(model, (; U, V, B, wu, wv),
schedule = AveragedTimeInterval(output_interval, window=2minutes),
filename = "langmuir_turbulence_averages.jld2",
overwrite_existing = true)
JLD2Writer scheduled on TimeInterval(5 minutes):
├── filepath: langmuir_turbulence_averages.jld2
├── 5 outputs: (U, V, B, wu, wv) averaged on AveragedTimeInterval(window=2 minutes, stride=1, interval=5 minutes)
├── array_type: Array{Float32}
├── including: [:grid, :coriolis, :buoyancy, :closure]
├── file_splitting: NoFileSplitting
└── file size: 44.3 KiB
Running the simulation
This part is easy,
run!(simulation)
[ Info: Initializing simulation...
[ Info: i: 0000, t: 0 seconds, Δt: 49.500 seconds, umax = (1.4e-03, 8.5e-04, 1.3e-03) ms⁻¹, wall time: 0 seconds
[ Info: ... simulation initialization complete (46.755 seconds)
[ Info: Executing initial time step...
[ Info: ... initial time step complete (7.151 seconds).
[ Info: i: 0020, t: 11.965 minutes, Δt: 28.304 seconds, umax = (2.9e-02, 1.2e-02, 2.1e-02) ms⁻¹, wall time: 1.231 minutes
[ Info: i: 0040, t: 20.000 minutes, Δt: 19.870 seconds, umax = (4.1e-02, 1.1e-02, 1.9e-02) ms⁻¹, wall time: 1.241 minutes
[ Info: i: 0060, t: 26.219 minutes, Δt: 17.664 seconds, umax = (4.7e-02, 1.4e-02, 1.9e-02) ms⁻¹, wall time: 1.253 minutes
[ Info: i: 0080, t: 31.582 minutes, Δt: 15.951 seconds, umax = (5.0e-02, 1.7e-02, 2.4e-02) ms⁻¹, wall time: 1.261 minutes
[ Info: i: 0100, t: 36.866 minutes, Δt: 15.888 seconds, umax = (5.0e-02, 1.9e-02, 2.4e-02) ms⁻¹, wall time: 1.271 minutes
[ Info: i: 0120, t: 42.068 minutes, Δt: 15.612 seconds, umax = (5.3e-02, 1.8e-02, 2.6e-02) ms⁻¹, wall time: 1.281 minutes
[ Info: i: 0140, t: 46.978 minutes, Δt: 14.466 seconds, umax = (5.5e-02, 2.0e-02, 2.6e-02) ms⁻¹, wall time: 1.291 minutes
[ Info: i: 0160, t: 51.712 minutes, Δt: 14.354 seconds, umax = (5.8e-02, 2.3e-02, 2.7e-02) ms⁻¹, wall time: 1.300 minutes
[ Info: i: 0180, t: 56.117 minutes, Δt: 13.394 seconds, umax = (6.4e-02, 2.4e-02, 2.8e-02) ms⁻¹, wall time: 1.309 minutes
[ Info: i: 0200, t: 1.007 hours, Δt: 12.085 seconds, umax = (6.6e-02, 2.6e-02, 3.1e-02) ms⁻¹, wall time: 1.319 minutes
[ Info: i: 0220, t: 1.074 hours, Δt: 12.359 seconds, umax = (6.3e-02, 2.9e-02, 3.3e-02) ms⁻¹, wall time: 1.326 minutes
[ Info: i: 0240, t: 1.142 hours, Δt: 12.491 seconds, umax = (6.8e-02, 3.1e-02, 3.6e-02) ms⁻¹, wall time: 1.335 minutes
[ Info: i: 0260, t: 1.208 hours, Δt: 11.891 seconds, umax = (6.5e-02, 3.0e-02, 3.2e-02) ms⁻¹, wall time: 1.343 minutes
[ Info: i: 0280, t: 1.273 hours, Δt: 11.312 seconds, umax = (6.6e-02, 3.5e-02, 3.6e-02) ms⁻¹, wall time: 1.353 minutes
[ Info: i: 0300, t: 1.333 hours, Δt: 11.338 seconds, umax = (7.0e-02, 3.1e-02, 3.4e-02) ms⁻¹, wall time: 1.361 minutes
[ Info: i: 0320, t: 1.395 hours, Δt: 11.023 seconds, umax = (6.8e-02, 3.3e-02, 3.6e-02) ms⁻¹, wall time: 1.370 minutes
[ Info: i: 0340, t: 1.456 hours, Δt: 10.847 seconds, umax = (6.9e-02, 3.5e-02, 3.7e-02) ms⁻¹, wall time: 1.380 minutes
[ Info: i: 0360, t: 1.515 hours, Δt: 10.578 seconds, umax = (6.9e-02, 3.6e-02, 3.9e-02) ms⁻¹, wall time: 1.390 minutes
[ Info: i: 0380, t: 1.574 hours, Δt: 10.521 seconds, umax = (7.1e-02, 3.8e-02, 3.8e-02) ms⁻¹, wall time: 1.400 minutes
[ Info: i: 0400, t: 1.629 hours, Δt: 10.335 seconds, umax = (7.4e-02, 3.7e-02, 3.9e-02) ms⁻¹, wall time: 1.408 minutes
[ Info: i: 0420, t: 1.684 hours, Δt: 9.650 seconds, umax = (7.1e-02, 3.9e-02, 4.0e-02) ms⁻¹, wall time: 1.419 minutes
[ Info: i: 0440, t: 1.737 hours, Δt: 9.963 seconds, umax = (7.2e-02, 4.1e-02, 3.9e-02) ms⁻¹, wall time: 1.427 minutes
[ Info: i: 0460, t: 1.792 hours, Δt: 9.939 seconds, umax = (7.4e-02, 4.3e-02, 3.9e-02) ms⁻¹, wall time: 1.436 minutes
[ Info: i: 0480, t: 1.844 hours, Δt: 9.874 seconds, umax = (7.4e-02, 4.0e-02, 4.1e-02) ms⁻¹, wall time: 1.447 minutes
[ Info: i: 0500, t: 1.899 hours, Δt: 9.745 seconds, umax = (7.5e-02, 4.5e-02, 4.0e-02) ms⁻¹, wall time: 1.454 minutes
[ Info: i: 0520, t: 1.951 hours, Δt: 9.428 seconds, umax = (7.7e-02, 4.7e-02, 4.2e-02) ms⁻¹, wall time: 1.465 minutes
[ Info: i: 0540, t: 2.003 hours, Δt: 8.725 seconds, umax = (7.7e-02, 4.5e-02, 4.1e-02) ms⁻¹, wall time: 1.476 minutes
[ Info: i: 0560, t: 2.050 hours, Δt: 8.520 seconds, umax = (8.0e-02, 5.4e-02, 4.2e-02) ms⁻¹, wall time: 1.483 minutes
[ Info: i: 0580, t: 2.095 hours, Δt: 8.719 seconds, umax = (8.0e-02, 4.8e-02, 4.2e-02) ms⁻¹, wall time: 1.494 minutes
[ Info: i: 0600, t: 2.145 hours, Δt: 8.836 seconds, umax = (7.6e-02, 4.4e-02, 4.2e-02) ms⁻¹, wall time: 1.501 minutes
[ Info: i: 0620, t: 2.193 hours, Δt: 9.116 seconds, umax = (8.0e-02, 4.5e-02, 4.8e-02) ms⁻¹, wall time: 1.512 minutes
[ Info: i: 0640, t: 2.243 hours, Δt: 9.004 seconds, umax = (8.3e-02, 4.6e-02, 4.6e-02) ms⁻¹, wall time: 1.521 minutes
[ Info: i: 0660, t: 2.292 hours, Δt: 8.756 seconds, umax = (8.1e-02, 5.0e-02, 4.2e-02) ms⁻¹, wall time: 1.530 minutes
[ Info: i: 0680, t: 2.341 hours, Δt: 9.187 seconds, umax = (7.8e-02, 4.8e-02, 4.6e-02) ms⁻¹, wall time: 1.542 minutes
[ Info: i: 0700, t: 2.391 hours, Δt: 9.000 seconds, umax = (7.8e-02, 5.3e-02, 4.3e-02) ms⁻¹, wall time: 1.549 minutes
[ Info: i: 0720, t: 2.437 hours, Δt: 8.980 seconds, umax = (8.2e-02, 4.7e-02, 4.2e-02) ms⁻¹, wall time: 1.560 minutes
[ Info: i: 0740, t: 2.487 hours, Δt: 8.868 seconds, umax = (8.2e-02, 4.9e-02, 4.5e-02) ms⁻¹, wall time: 1.568 minutes
[ Info: i: 0760, t: 2.533 hours, Δt: 8.190 seconds, umax = (8.5e-02, 4.7e-02, 5.0e-02) ms⁻¹, wall time: 1.579 minutes
[ Info: i: 0780, t: 2.581 hours, Δt: 8.743 seconds, umax = (9.0e-02, 4.8e-02, 4.8e-02) ms⁻¹, wall time: 1.588 minutes
[ Info: i: 0800, t: 2.627 hours, Δt: 8.929 seconds, umax = (8.4e-02, 4.8e-02, 4.5e-02) ms⁻¹, wall time: 1.599 minutes
[ Info: i: 0820, t: 2.674 hours, Δt: 8.834 seconds, umax = (8.1e-02, 5.0e-02, 4.5e-02) ms⁻¹, wall time: 1.610 minutes
[ Info: i: 0840, t: 2.723 hours, Δt: 9.220 seconds, umax = (8.2e-02, 5.6e-02, 4.5e-02) ms⁻¹, wall time: 1.618 minutes
[ Info: i: 0860, t: 2.772 hours, Δt: 8.821 seconds, umax = (8.3e-02, 5.1e-02, 4.5e-02) ms⁻¹, wall time: 1.628 minutes
[ Info: i: 0880, t: 2.821 hours, Δt: 8.896 seconds, umax = (8.3e-02, 5.1e-02, 4.3e-02) ms⁻¹, wall time: 1.636 minutes
[ Info: i: 0900, t: 2.870 hours, Δt: 8.644 seconds, umax = (8.0e-02, 5.3e-02, 4.2e-02) ms⁻¹, wall time: 1.646 minutes
[ Info: i: 0920, t: 2.917 hours, Δt: 8.671 seconds, umax = (8.4e-02, 5.2e-02, 4.3e-02) ms⁻¹, wall time: 1.655 minutes
[ Info: i: 0940, t: 2.965 hours, Δt: 8.784 seconds, umax = (8.5e-02, 5.1e-02, 4.4e-02) ms⁻¹, wall time: 1.665 minutes
[ Info: i: 0960, t: 3.012 hours, Δt: 8.358 seconds, umax = (8.4e-02, 5.4e-02, 4.5e-02) ms⁻¹, wall time: 1.677 minutes
[ Info: i: 0980, t: 3.059 hours, Δt: 8.491 seconds, umax = (8.4e-02, 5.6e-02, 4.2e-02) ms⁻¹, wall time: 1.684 minutes
[ Info: i: 1000, t: 3.105 hours, Δt: 8.484 seconds, umax = (8.4e-02, 5.9e-02, 4.6e-02) ms⁻¹, wall time: 1.695 minutes
[ Info: i: 1020, t: 3.151 hours, Δt: 8.445 seconds, umax = (8.7e-02, 5.5e-02, 4.7e-02) ms⁻¹, wall time: 1.705 minutes
[ Info: i: 1040, t: 3.197 hours, Δt: 8.246 seconds, umax = (8.4e-02, 5.6e-02, 4.6e-02) ms⁻¹, wall time: 1.715 minutes
[ Info: i: 1060, t: 3.242 hours, Δt: 8.304 seconds, umax = (8.6e-02, 6.1e-02, 4.5e-02) ms⁻¹, wall time: 1.725 minutes
[ Info: i: 1080, t: 3.288 hours, Δt: 8.629 seconds, umax = (8.6e-02, 6.1e-02, 4.3e-02) ms⁻¹, wall time: 1.735 minutes
[ Info: i: 1100, t: 3.333 hours, Δt: 7.932 seconds, umax = (9.1e-02, 5.7e-02, 4.5e-02) ms⁻¹, wall time: 1.744 minutes
[ Info: i: 1120, t: 3.378 hours, Δt: 8.022 seconds, umax = (8.7e-02, 6.1e-02, 4.3e-02) ms⁻¹, wall time: 1.754 minutes
[ Info: i: 1140, t: 3.421 hours, Δt: 8.276 seconds, umax = (8.4e-02, 5.8e-02, 4.4e-02) ms⁻¹, wall time: 1.766 minutes
[ Info: i: 1160, t: 3.467 hours, Δt: 8.239 seconds, umax = (8.5e-02, 5.5e-02, 4.5e-02) ms⁻¹, wall time: 1.774 minutes
[ Info: i: 1180, t: 3.512 hours, Δt: 8.398 seconds, umax = (8.3e-02, 5.7e-02, 4.2e-02) ms⁻¹, wall time: 1.784 minutes
[ Info: i: 1200, t: 3.558 hours, Δt: 8.484 seconds, umax = (8.2e-02, 5.9e-02, 4.9e-02) ms⁻¹, wall time: 1.793 minutes
[ Info: i: 1220, t: 3.604 hours, Δt: 8.152 seconds, umax = (8.7e-02, 5.6e-02, 4.5e-02) ms⁻¹, wall time: 1.803 minutes
[ Info: i: 1240, t: 3.649 hours, Δt: 8.088 seconds, umax = (8.7e-02, 5.6e-02, 4.4e-02) ms⁻¹, wall time: 1.811 minutes
[ Info: i: 1260, t: 3.694 hours, Δt: 8.193 seconds, umax = (8.7e-02, 5.6e-02, 4.6e-02) ms⁻¹, wall time: 1.820 minutes
[ Info: i: 1280, t: 3.740 hours, Δt: 8.072 seconds, umax = (8.8e-02, 6.0e-02, 4.6e-02) ms⁻¹, wall time: 1.831 minutes
[ Info: i: 1300, t: 3.784 hours, Δt: 8.245 seconds, umax = (9.0e-02, 5.6e-02, 4.8e-02) ms⁻¹, wall time: 1.839 minutes
[ Info: i: 1320, t: 3.830 hours, Δt: 8.347 seconds, umax = (8.8e-02, 6.1e-02, 4.4e-02) ms⁻¹, wall time: 1.850 minutes
[ Info: i: 1340, t: 3.875 hours, Δt: 7.982 seconds, umax = (8.5e-02, 6.3e-02, 4.9e-02) ms⁻¹, wall time: 1.860 minutes
[ Info: i: 1360, t: 3.919 hours, Δt: 8.414 seconds, umax = (8.4e-02, 5.8e-02, 4.7e-02) ms⁻¹, wall time: 1.871 minutes
[ Info: i: 1380, t: 3.966 hours, Δt: 8.517 seconds, umax = (8.8e-02, 6.0e-02, 5.5e-02) ms⁻¹, wall time: 1.878 minutes
[ Info: Simulation is stopping after running for 1.885 minutes.
[ Info: Simulation time 4 hours equals or exceeds stop time 4 hours.
Making a neat movie
We look at the results by loading data from file with FieldTimeSeries
, and plotting vertical slices of $u$ and $w$, and a horizontal slice of $w$ to look for Langmuir cells.
using CairoMakie
time_series = (;
w = FieldTimeSeries("langmuir_turbulence_fields.jld2", "w"),
u = FieldTimeSeries("langmuir_turbulence_fields.jld2", "u"),
B = FieldTimeSeries("langmuir_turbulence_averages.jld2", "B"),
U = FieldTimeSeries("langmuir_turbulence_averages.jld2", "U"),
V = FieldTimeSeries("langmuir_turbulence_averages.jld2", "V"),
wu = FieldTimeSeries("langmuir_turbulence_averages.jld2", "wu"),
wv = FieldTimeSeries("langmuir_turbulence_averages.jld2", "wv"))
times = time_series.w.times
We are now ready to animate using Makie. We use Makie's Observable
to animate the data. To dive into how Observable
s work we refer to Makie.jl's Documentation.
n = Observable(1)
wxy_title = @lift string("w(x, y, t) at z=-8 m and t = ", prettytime(times[$n]))
wxz_title = @lift string("w(x, z, t) at y=0 m and t = ", prettytime(times[$n]))
uxz_title = @lift string("u(x, z, t) at y=0 m and t = ", prettytime(times[$n]))
fig = Figure(size = (850, 850))
ax_B = Axis(fig[1, 4];
xlabel = "Buoyancy (m s⁻²)",
ylabel = "z (m)")
ax_U = Axis(fig[2, 4];
xlabel = "Velocities (m s⁻¹)",
ylabel = "z (m)",
limits = ((-0.07, 0.07), nothing))
ax_fluxes = Axis(fig[3, 4];
xlabel = "Momentum fluxes (m² s⁻²)",
ylabel = "z (m)",
limits = ((-3.5e-5, 3.5e-5), nothing))
ax_wxy = Axis(fig[1, 1:2];
xlabel = "x (m)",
ylabel = "y (m)",
aspect = DataAspect(),
limits = ((0, grid.Lx), (0, grid.Ly)),
title = wxy_title)
ax_wxz = Axis(fig[2, 1:2];
xlabel = "x (m)",
ylabel = "z (m)",
aspect = AxisAspect(2),
limits = ((0, grid.Lx), (-grid.Lz, 0)),
title = wxz_title)
ax_uxz = Axis(fig[3, 1:2];
xlabel = "x (m)",
ylabel = "z (m)",
aspect = AxisAspect(2),
limits = ((0, grid.Lx), (-grid.Lz, 0)),
title = uxz_title)
wₙ = @lift time_series.w[$n]
uₙ = @lift time_series.u[$n]
Bₙ = @lift view(time_series.B[$n], 1, 1, :)
Uₙ = @lift view(time_series.U[$n], 1, 1, :)
Vₙ = @lift view(time_series.V[$n], 1, 1, :)
wuₙ = @lift view(time_series.wu[$n], 1, 1, :)
wvₙ = @lift view(time_series.wv[$n], 1, 1, :)
k = searchsortedfirst(znodes(grid, Face(); with_halos=true), -8)
wxyₙ = @lift view(time_series.w[$n], :, :, k)
wxzₙ = @lift view(time_series.w[$n], :, 1, :)
uxzₙ = @lift view(time_series.u[$n], :, 1, :)
wlims = (-0.03, 0.03)
ulims = (-0.05, 0.05)
lines!(ax_B, Bₙ)
lines!(ax_U, Uₙ; label = L"\bar{u}")
lines!(ax_U, Vₙ; label = L"\bar{v}")
axislegend(ax_U; position = :rb)
lines!(ax_fluxes, wuₙ; label = L"mean $wu$")
lines!(ax_fluxes, wvₙ; label = L"mean $wv$")
axislegend(ax_fluxes; position = :rb)
hm_wxy = heatmap!(ax_wxy, wxyₙ;
colorrange = wlims,
colormap = :balance)
Colorbar(fig[1, 3], hm_wxy; label = "m s⁻¹")
hm_wxz = heatmap!(ax_wxz, wxzₙ;
colorrange = wlims,
colormap = :balance)
Colorbar(fig[2, 3], hm_wxz; label = "m s⁻¹")
ax_uxz = heatmap!(ax_uxz, uxzₙ;
colorrange = ulims,
colormap = :balance)
Colorbar(fig[3, 3], ax_uxz; label = "m s⁻¹")
fig
And, finally, we record a movie.
frames = 1:length(times)
CairoMakie.record(fig, "langmuir_turbulence.mp4", frames, framerate=8) do i
n[] = i
end
This page was generated using Literate.jl.