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 CUDAModel 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.0The 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.
g = Oceananigans.defaults.gravitational_acceleration
amplitude = 0.8 # m
wavelength = 60 # m
wavenumber = 2π / wavelength # m⁻¹
frequency = sqrt(g * 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.06791774197745354The 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{CUDAGPU, 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.Lzbᵢ (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{CUDAGPU, RectilinearGrid}(time = 0 seconds, iteration = 0)
├── Next time step: 45 seconds
├── run_wall_time: 0 seconds
├── run_wall_time / 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 => Callback of stop_time_exceeded on IterationInterval(1)
│ ├── stop_iteration_exceeded => Callback of stop_iteration_exceeded on IterationInterval(1)
│ ├── wall_time_limit_exceeded => Callback of wall_time_limit_exceeded on IterationInterval(1)
│ └── nan_checker => Callback of NaNChecker for u on IterationInterval(100)
└── output_writers: OrderedDict with no entriesWe 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: 0 bytes (file not yet created)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: 0 bytes (file not yet created)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.2e-04, 1.3e-03) ms⁻¹, wall time: 0 seconds
[ Info: ... simulation initialization complete (9.591 seconds)
[ Info: Executing initial time step...
[ Info: ... initial time step complete (2.880 seconds).
[ Info: i: 0020, t: 11.238 minutes, Δt: 18.511 seconds, umax = (3.5e-02, 1.3e-02, 2.5e-02) ms⁻¹, wall time: 13.712 seconds
[ Info: i: 0040, t: 16.873 minutes, Δt: 13.481 seconds, umax = (5.3e-02, 2.1e-02, 2.3e-02) ms⁻¹, wall time: 14.195 seconds
[ Info: i: 0060, t: 20.998 minutes, Δt: 11.158 seconds, umax = (6.3e-02, 3.1e-02, 3.0e-02) ms⁻¹, wall time: 14.855 seconds
[ Info: i: 0080, t: 24.540 minutes, Δt: 10.801 seconds, umax = (6.4e-02, 3.1e-02, 3.3e-02) ms⁻¹, wall time: 15.335 seconds
[ Info: i: 0100, t: 28.150 minutes, Δt: 11.154 seconds, umax = (6.3e-02, 3.2e-02, 3.0e-02) ms⁻¹, wall time: 15.896 seconds
[ Info: i: 0120, t: 31.900 minutes, Δt: 10.363 seconds, umax = (6.0e-02, 3.4e-02, 2.9e-02) ms⁻¹, wall time: 16.461 seconds
[ Info: i: 0140, t: 35.375 minutes, Δt: 10.592 seconds, umax = (6.8e-02, 3.3e-02, 3.2e-02) ms⁻¹, wall time: 17.144 seconds
[ Info: i: 0160, t: 38.906 minutes, Δt: 9.778 seconds, umax = (7.1e-02, 3.4e-02, 3.3e-02) ms⁻¹, wall time: 17.544 seconds
[ Info: i: 0180, t: 42.106 minutes, Δt: 9.664 seconds, umax = (7.2e-02, 3.9e-02, 3.7e-02) ms⁻¹, wall time: 18.099 seconds
[ Info: i: 0200, t: 45.328 minutes, Δt: 9.324 seconds, umax = (7.8e-02, 3.7e-02, 3.6e-02) ms⁻¹, wall time: 18.781 seconds
[ Info: i: 0220, t: 48.441 minutes, Δt: 9.190 seconds, umax = (7.7e-02, 4.2e-02, 3.3e-02) ms⁻¹, wall time: 19.172 seconds
[ Info: i: 0240, t: 51.396 minutes, Δt: 8.615 seconds, umax = (7.6e-02, 4.2e-02, 3.7e-02) ms⁻¹, wall time: 19.731 seconds
[ Info: i: 0260, t: 54.314 minutes, Δt: 8.977 seconds, umax = (7.6e-02, 4.2e-02, 3.7e-02) ms⁻¹, wall time: 20.245 seconds
[ Info: i: 0280, t: 57.244 minutes, Δt: 8.813 seconds, umax = (7.9e-02, 4.5e-02, 3.7e-02) ms⁻¹, wall time: 20.799 seconds
[ Info: i: 0300, t: 1.002 hours, Δt: 8.528 seconds, umax = (8.3e-02, 4.5e-02, 4.3e-02) ms⁻¹, wall time: 21.504 seconds
[ Info: i: 0320, t: 1.051 hours, Δt: 8.864 seconds, umax = (7.9e-02, 4.0e-02, 3.6e-02) ms⁻¹, wall time: 21.873 seconds
[ Info: i: 0340, t: 1.098 hours, Δt: 8.179 seconds, umax = (8.4e-02, 4.5e-02, 3.9e-02) ms⁻¹, wall time: 22.464 seconds
[ Info: i: 0360, t: 1.142 hours, Δt: 8.141 seconds, umax = (8.1e-02, 4.4e-02, 3.8e-02) ms⁻¹, wall time: 22.956 seconds
[ Info: i: 0380, t: 1.185 hours, Δt: 8.434 seconds, umax = (8.5e-02, 4.8e-02, 3.8e-02) ms⁻¹, wall time: 23.533 seconds
[ Info: i: 0400, t: 1.232 hours, Δt: 8.010 seconds, umax = (8.9e-02, 5.1e-02, 4.3e-02) ms⁻¹, wall time: 24.064 seconds
[ Info: i: 0420, t: 1.275 hours, Δt: 7.612 seconds, umax = (9.0e-02, 4.7e-02, 4.4e-02) ms⁻¹, wall time: 24.633 seconds
[ Info: i: 0440, t: 1.317 hours, Δt: 8.070 seconds, umax = (9.0e-02, 4.8e-02, 3.8e-02) ms⁻¹, wall time: 25.166 seconds
[ Info: i: 0460, t: 1.359 hours, Δt: 7.416 seconds, umax = (9.1e-02, 5.3e-02, 3.9e-02) ms⁻¹, wall time: 25.747 seconds
[ Info: i: 0480, t: 1.402 hours, Δt: 7.294 seconds, umax = (9.3e-02, 5.3e-02, 3.9e-02) ms⁻¹, wall time: 26.281 seconds
[ Info: i: 0500, t: 1.441 hours, Δt: 7.572 seconds, umax = (8.9e-02, 5.2e-02, 4.0e-02) ms⁻¹, wall time: 26.859 seconds
[ Info: i: 0520, t: 1.482 hours, Δt: 7.281 seconds, umax = (9.2e-02, 5.9e-02, 4.4e-02) ms⁻¹, wall time: 27.396 seconds
[ Info: i: 0540, t: 1.522 hours, Δt: 6.876 seconds, umax = (9.5e-02, 5.0e-02, 3.9e-02) ms⁻¹, wall time: 27.974 seconds
[ Info: i: 0560, t: 1.562 hours, Δt: 7.106 seconds, umax = (9.4e-02, 4.9e-02, 4.0e-02) ms⁻¹, wall time: 28.515 seconds
[ Info: i: 0580, t: 1.602 hours, Δt: 6.873 seconds, umax = (9.5e-02, 5.6e-02, 4.3e-02) ms⁻¹, wall time: 29.113 seconds
[ Info: i: 0600, t: 1.640 hours, Δt: 6.956 seconds, umax = (9.6e-02, 5.2e-02, 4.3e-02) ms⁻¹, wall time: 29.650 seconds
[ Info: i: 0620, t: 1.679 hours, Δt: 7.277 seconds, umax = (9.4e-02, 5.7e-02, 4.2e-02) ms⁻¹, wall time: 30.285 seconds
[ Info: i: 0640, t: 1.719 hours, Δt: 7.415 seconds, umax = (9.3e-02, 6.0e-02, 4.0e-02) ms⁻¹, wall time: 30.789 seconds
[ Info: i: 0660, t: 1.758 hours, Δt: 6.501 seconds, umax = (9.8e-02, 6.5e-02, 3.9e-02) ms⁻¹, wall time: 31.481 seconds
[ Info: i: 0680, t: 1.794 hours, Δt: 7.326 seconds, umax = (9.7e-02, 5.9e-02, 4.4e-02) ms⁻¹, wall time: 31.950 seconds
[ Info: i: 0700, t: 1.833 hours, Δt: 6.982 seconds, umax = (9.6e-02, 6.7e-02, 4.3e-02) ms⁻¹, wall time: 32.513 seconds
[ Info: i: 0720, t: 1.871 hours, Δt: 6.835 seconds, umax = (1.0e-01, 6.1e-02, 4.2e-02) ms⁻¹, wall time: 33.189 seconds
[ Info: i: 0740, t: 1.909 hours, Δt: 7.090 seconds, umax = (1.0e-01, 5.9e-02, 4.4e-02) ms⁻¹, wall time: 33.757 seconds
[ Info: i: 0760, t: 1.947 hours, Δt: 6.729 seconds, umax = (9.6e-02, 6.3e-02, 4.6e-02) ms⁻¹, wall time: 34.385 seconds
[ Info: i: 0780, t: 1.985 hours, Δt: 6.773 seconds, umax = (1.0e-01, 6.5e-02, 4.0e-02) ms⁻¹, wall time: 34.948 seconds
[ Info: i: 0800, t: 2.020 hours, Δt: 6.721 seconds, umax = (9.7e-02, 6.5e-02, 4.0e-02) ms⁻¹, wall time: 35.566 seconds
[ Info: i: 0820, t: 2.057 hours, Δt: 6.855 seconds, umax = (9.7e-02, 6.0e-02, 4.4e-02) ms⁻¹, wall time: 36.124 seconds
[ Info: i: 0840, t: 2.095 hours, Δt: 6.827 seconds, umax = (9.6e-02, 6.3e-02, 4.7e-02) ms⁻¹, wall time: 36.772 seconds
[ Info: i: 0860, t: 2.133 hours, Δt: 7.034 seconds, umax = (9.9e-02, 6.1e-02, 4.6e-02) ms⁻¹, wall time: 37.299 seconds
[ Info: i: 0880, t: 2.170 hours, Δt: 6.260 seconds, umax = (1.0e-01, 6.4e-02, 4.4e-02) ms⁻¹, wall time: 38.058 seconds
[ Info: i: 0900, t: 2.206 hours, Δt: 6.579 seconds, umax = (1.1e-01, 6.6e-02, 4.4e-02) ms⁻¹, wall time: 38.487 seconds
[ Info: i: 0920, t: 2.243 hours, Δt: 6.391 seconds, umax = (1.1e-01, 6.8e-02, 4.8e-02) ms⁻¹, wall time: 39.063 seconds
[ Info: i: 0940, t: 2.278 hours, Δt: 6.521 seconds, umax = (9.9e-02, 6.3e-02, 4.5e-02) ms⁻¹, wall time: 39.700 seconds
[ Info: i: 0960, t: 2.314 hours, Δt: 6.712 seconds, umax = (9.6e-02, 6.4e-02, 4.6e-02) ms⁻¹, wall time: 40.255 seconds
[ Info: i: 0980, t: 2.350 hours, Δt: 6.594 seconds, umax = (1.1e-01, 6.3e-02, 4.6e-02) ms⁻¹, wall time: 40.870 seconds
[ Info: i: 1000, t: 2.385 hours, Δt: 6.148 seconds, umax = (1.1e-01, 7.8e-02, 4.5e-02) ms⁻¹, wall time: 41.424 seconds
[ Info: i: 1020, t: 2.418 hours, Δt: 6.005 seconds, umax = (1.1e-01, 7.1e-02, 4.4e-02) ms⁻¹, wall time: 42.293 seconds
[ Info: i: 1040, t: 2.452 hours, Δt: 6.295 seconds, umax = (1.1e-01, 6.4e-02, 4.7e-02) ms⁻¹, wall time: 42.699 seconds
[ Info: i: 1060, t: 2.487 hours, Δt: 6.612 seconds, umax = (1.1e-01, 7.0e-02, 4.3e-02) ms⁻¹, wall time: 43.273 seconds
[ Info: i: 1080, t: 2.521 hours, Δt: 6.613 seconds, umax = (1.1e-01, 6.5e-02, 4.2e-02) ms⁻¹, wall time: 44.017 seconds
[ Info: i: 1100, t: 2.556 hours, Δt: 6.473 seconds, umax = (1.0e-01, 6.7e-02, 4.8e-02) ms⁻¹, wall time: 44.578 seconds
[ Info: i: 1120, t: 2.590 hours, Δt: 6.398 seconds, umax = (1.0e-01, 6.4e-02, 4.4e-02) ms⁻¹, wall time: 45.392 seconds
[ Info: i: 1140, t: 2.625 hours, Δt: 6.452 seconds, umax = (1.0e-01, 6.6e-02, 4.2e-02) ms⁻¹, wall time: 45.869 seconds
[ Info: i: 1160, t: 2.661 hours, Δt: 6.284 seconds, umax = (1.0e-01, 6.5e-02, 4.3e-02) ms⁻¹, wall time: 46.441 seconds
[ Info: i: 1180, t: 2.695 hours, Δt: 6.098 seconds, umax = (1.1e-01, 6.5e-02, 5.2e-02) ms⁻¹, wall time: 47.077 seconds
[ Info: i: 1200, t: 2.729 hours, Δt: 6.251 seconds, umax = (1.1e-01, 7.1e-02, 4.6e-02) ms⁻¹, wall time: 47.650 seconds
[ Info: i: 1220, t: 2.762 hours, Δt: 6.341 seconds, umax = (1.1e-01, 7.0e-02, 4.5e-02) ms⁻¹, wall time: 48.289 seconds
[ Info: i: 1240, t: 2.797 hours, Δt: 6.202 seconds, umax = (1.1e-01, 8.0e-02, 4.5e-02) ms⁻¹, wall time: 48.844 seconds
[ Info: i: 1260, t: 2.831 hours, Δt: 5.750 seconds, umax = (1.1e-01, 7.3e-02, 5.1e-02) ms⁻¹, wall time: 49.423 seconds
[ Info: i: 1280, t: 2.863 hours, Δt: 6.102 seconds, umax = (1.1e-01, 7.0e-02, 4.9e-02) ms⁻¹, wall time: 50.064 seconds
[ Info: i: 1300, t: 2.897 hours, Δt: 6.139 seconds, umax = (1.1e-01, 7.1e-02, 5.3e-02) ms⁻¹, wall time: 50.636 seconds
[ Info: i: 1320, t: 2.930 hours, Δt: 6.291 seconds, umax = (1.1e-01, 6.5e-02, 4.7e-02) ms⁻¹, wall time: 51.289 seconds
[ Info: i: 1340, t: 2.964 hours, Δt: 5.840 seconds, umax = (1.1e-01, 7.1e-02, 4.8e-02) ms⁻¹, wall time: 51.837 seconds
[ Info: i: 1360, t: 2.997 hours, Δt: 6.262 seconds, umax = (1.1e-01, 7.2e-02, 5.0e-02) ms⁻¹, wall time: 52.414 seconds
[ Info: i: 1380, t: 3.031 hours, Δt: 5.708 seconds, umax = (1.1e-01, 7.4e-02, 4.7e-02) ms⁻¹, wall time: 53.097 seconds
[ Info: i: 1400, t: 3.063 hours, Δt: 6.361 seconds, umax = (1.1e-01, 7.0e-02, 4.4e-02) ms⁻¹, wall time: 53.674 seconds
[ Info: i: 1420, t: 3.097 hours, Δt: 6.079 seconds, umax = (1.1e-01, 7.4e-02, 4.6e-02) ms⁻¹, wall time: 54.319 seconds
[ Info: i: 1440, t: 3.131 hours, Δt: 6.006 seconds, umax = (1.1e-01, 8.5e-02, 4.3e-02) ms⁻¹, wall time: 54.872 seconds
[ Info: i: 1460, t: 3.163 hours, Δt: 5.619 seconds, umax = (1.1e-01, 7.9e-02, 4.3e-02) ms⁻¹, wall time: 55.454 seconds
[ Info: i: 1480, t: 3.193 hours, Δt: 5.754 seconds, umax = (1.1e-01, 7.8e-02, 4.6e-02) ms⁻¹, wall time: 56.102 seconds
[ Info: i: 1500, t: 3.225 hours, Δt: 5.975 seconds, umax = (1.1e-01, 8.1e-02, 4.8e-02) ms⁻¹, wall time: 56.676 seconds
[ Info: i: 1520, t: 3.257 hours, Δt: 5.836 seconds, umax = (1.1e-01, 8.0e-02, 4.7e-02) ms⁻¹, wall time: 57.388 seconds
[ Info: i: 1540, t: 3.290 hours, Δt: 6.138 seconds, umax = (1.2e-01, 8.9e-02, 4.7e-02) ms⁻¹, wall time: 57.870 seconds
[ Info: i: 1560, t: 3.325 hours, Δt: 5.858 seconds, umax = (1.2e-01, 8.6e-02, 4.5e-02) ms⁻¹, wall time: 58.449 seconds
[ Info: i: 1580, t: 3.356 hours, Δt: 5.842 seconds, umax = (1.1e-01, 7.7e-02, 4.8e-02) ms⁻¹, wall time: 59.112 seconds
[ Info: i: 1600, t: 3.387 hours, Δt: 5.735 seconds, umax = (1.1e-01, 8.1e-02, 5.0e-02) ms⁻¹, wall time: 59.676 seconds
[ Info: i: 1620, t: 3.418 hours, Δt: 6.314 seconds, umax = (1.1e-01, 8.1e-02, 5.0e-02) ms⁻¹, wall time: 1.008 minutes
[ Info: i: 1640, t: 3.451 hours, Δt: 5.399 seconds, umax = (1.0e-01, 8.0e-02, 4.6e-02) ms⁻¹, wall time: 1.015 minutes
[ Info: i: 1660, t: 3.482 hours, Δt: 5.706 seconds, umax = (1.1e-01, 7.8e-02, 5.5e-02) ms⁻¹, wall time: 1.024 minutes
[ Info: i: 1680, t: 3.513 hours, Δt: 5.718 seconds, umax = (1.1e-01, 8.8e-02, 5.0e-02) ms⁻¹, wall time: 1.035 minutes
[ Info: i: 1700, t: 3.544 hours, Δt: 5.750 seconds, umax = (1.0e-01, 8.7e-02, 5.1e-02) ms⁻¹, wall time: 1.044 minutes
[ Info: i: 1720, t: 3.577 hours, Δt: 5.992 seconds, umax = (1.2e-01, 7.8e-02, 5.6e-02) ms⁻¹, wall time: 1.054 minutes
[ Info: i: 1740, t: 3.610 hours, Δt: 5.394 seconds, umax = (1.1e-01, 8.4e-02, 5.2e-02) ms⁻¹, wall time: 1.064 minutes
[ Info: i: 1760, t: 3.641 hours, Δt: 5.724 seconds, umax = (1.1e-01, 8.6e-02, 5.2e-02) ms⁻¹, wall time: 1.074 minutes
[ Info: i: 1780, t: 3.671 hours, Δt: 5.537 seconds, umax = (1.1e-01, 8.5e-02, 5.1e-02) ms⁻¹, wall time: 1.086 minutes
[ Info: i: 1800, t: 3.704 hours, Δt: 6.157 seconds, umax = (1.1e-01, 8.7e-02, 5.2e-02) ms⁻¹, wall time: 1.093 minutes
[ Info: i: 1820, t: 3.738 hours, Δt: 6.015 seconds, umax = (1.1e-01, 9.1e-02, 5.0e-02) ms⁻¹, wall time: 1.103 minutes
[ Info: i: 1840, t: 3.769 hours, Δt: 5.553 seconds, umax = (1.2e-01, 8.2e-02, 4.9e-02) ms⁻¹, wall time: 1.114 minutes
[ Info: i: 1860, t: 3.799 hours, Δt: 5.344 seconds, umax = (1.1e-01, 9.2e-02, 4.8e-02) ms⁻¹, wall time: 1.123 minutes
[ Info: i: 1880, t: 3.830 hours, Δt: 5.644 seconds, umax = (1.2e-01, 8.4e-02, 5.4e-02) ms⁻¹, wall time: 1.133 minutes
[ Info: i: 1900, t: 3.861 hours, Δt: 5.784 seconds, umax = (1.3e-01, 8.7e-02, 5.3e-02) ms⁻¹, wall time: 1.143 minutes
[ Info: i: 1920, t: 3.893 hours, Δt: 5.842 seconds, umax = (1.3e-01, 8.7e-02, 4.8e-02) ms⁻¹, wall time: 1.153 minutes
[ Info: i: 1940, t: 3.923 hours, Δt: 5.206 seconds, umax = (1.2e-01, 9.5e-02, 5.3e-02) ms⁻¹, wall time: 1.165 minutes
[ Info: i: 1960, t: 3.952 hours, Δt: 5.550 seconds, umax = (1.2e-01, 8.5e-02, 5.7e-02) ms⁻¹, wall time: 1.173 minutes
[ Info: i: 1980, t: 3.983 hours, Δt: 5.596 seconds, umax = (1.1e-01, 8.0e-02, 5.4e-02) ms⁻¹, wall time: 1.182 minutes
[ Info: Simulation is stopping after running for 1.190 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.timesWe are now ready to animate using Makie. We use Makie's Observable to animate the data. To dive into how Observables 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⁻¹")
figAnd, finally, we record a movie.
frames = 1:length(times)
CairoMakie.record(fig, "langmuir_turbulence.mp4", frames, framerate=8) do i
n[] = i
endJulia version and environment information
This example was executed with the following version of Julia:
using InteractiveUtils: versioninfo
versioninfo()Julia Version 1.12.4
Commit 01a2eadb047 (2026-01-06 16:56 UTC)
Build Info:
Official https://julialang.org release
Platform Info:
OS: Linux (x86_64-linux-gnu)
CPU: 128 × AMD EPYC 9374F 32-Core Processor
WORD_SIZE: 64
LLVM: libLLVM-18.1.7 (ORCJIT, znver4)
GC: Built with stock GC
Threads: 1 default, 1 interactive, 1 GC (on 128 virtual cores)
Environment:
LD_LIBRARY_PATH =
JULIA_PKG_SERVER_REGISTRY_PREFERENCE = eager
JULIA_DEPOT_PATH = /var/lib/buildkite-agent/.julia-oceananigans
JULIA_PROJECT = /var/lib/buildkite-agent/Oceananigans.jl-29074/docs/
JULIA_VERSION = 1.12.4
JULIA_LOAD_PATH = @:@v#.#:@stdlib
JULIA_VERSION_ENZYME = 1.10.10
JULIA_PYTHONCALL_EXE = /var/lib/buildkite-agent/Oceananigans.jl-29074/docs/.CondaPkg/.pixi/envs/default/bin/python
JULIA_DEBUG = Literate
These were the top-level packages installed in the environment:
import Pkg
Pkg.status()Status `~/Oceananigans.jl-29074/docs/Project.toml`
[79e6a3ab] Adapt v4.4.0
[052768ef] CUDA v5.9.6
[13f3f980] CairoMakie v0.15.8
[e30172f5] Documenter v1.16.1
[daee34ce] DocumenterCitations v1.4.1
[033835bb] JLD2 v0.6.3
[63c18a36] KernelAbstractions v0.9.39
[98b081ad] Literate v2.21.0
[da04e1cc] MPI v0.20.23
[85f8d34a] NCDatasets v0.14.11
[9e8cae18] Oceananigans v0.104.2 `..`
[f27b6e38] Polynomials v4.1.0
[6038ab10] Rotations v1.7.1
[d496a93d] SeawaterPolynomials v0.3.10
[09ab397b] StructArrays v0.7.2
[bdfc003b] TimesDates v0.3.3
[2e0b0046] XESMF v0.1.6
[b77e0a4c] InteractiveUtils v1.11.0
[37e2e46d] LinearAlgebra v1.12.0
[44cfe95a] Pkg v1.12.1
This page was generated using Literate.jl.