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"
using Oceananigans
using Oceananigans.Units: minute, minutes, hours
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 GPU, include GPU()
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(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 => 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 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, 1.0e-03, 1.4e-03) ms⁻¹, wall time: 0 seconds
[ Info: ... simulation initialization complete (34.935 seconds)
[ Info: Executing initial time step...
[ Info: ... initial time step complete (4.957 seconds).
[ Info: i: 0020, t: 11.864 minutes, Δt: 28.310 seconds, umax = (2.9e-02, 1.1e-02, 2.2e-02) ms⁻¹, wall time: 52.778 seconds
[ Info: i: 0040, t: 20 minutes, Δt: 20.022 seconds, umax = (4.0e-02, 1.0e-02, 1.9e-02) ms⁻¹, wall time: 53.243 seconds
[ Info: i: 0060, t: 26.228 minutes, Δt: 17.256 seconds, umax = (4.7e-02, 1.5e-02, 2.1e-02) ms⁻¹, wall time: 53.945 seconds
[ Info: i: 0080, t: 31.672 minutes, Δt: 16.573 seconds, umax = (5.0e-02, 1.7e-02, 2.3e-02) ms⁻¹, wall time: 54.439 seconds
[ Info: i: 0100, t: 36.887 minutes, Δt: 15.818 seconds, umax = (5.2e-02, 1.8e-02, 2.3e-02) ms⁻¹, wall time: 54.947 seconds
[ Info: i: 0120, t: 42.043 minutes, Δt: 14.996 seconds, umax = (5.4e-02, 1.9e-02, 2.4e-02) ms⁻¹, wall time: 55.455 seconds
[ Info: i: 0140, t: 46.942 minutes, Δt: 14.527 seconds, umax = (5.5e-02, 2.0e-02, 2.6e-02) ms⁻¹, wall time: 55.993 seconds
[ Info: i: 0160, t: 51.724 minutes, Δt: 14.125 seconds, umax = (5.8e-02, 2.1e-02, 2.9e-02) ms⁻¹, wall time: 56.544 seconds
[ Info: i: 0180, t: 56.108 minutes, Δt: 13.361 seconds, umax = (6.2e-02, 2.4e-02, 3.0e-02) ms⁻¹, wall time: 57.097 seconds
[ Info: i: 0200, t: 1.007 hours, Δt: 12.710 seconds, umax = (6.3e-02, 2.6e-02, 2.8e-02) ms⁻¹, wall time: 57.707 seconds
[ Info: i: 0220, t: 1.077 hours, Δt: 12.364 seconds, umax = (6.4e-02, 2.6e-02, 3.2e-02) ms⁻¹, wall time: 58.062 seconds
[ Info: i: 0240, t: 1.141 hours, Δt: 11.907 seconds, umax = (6.8e-02, 3.1e-02, 3.3e-02) ms⁻¹, wall time: 58.561 seconds
[ Info: i: 0260, t: 1.205 hours, Δt: 11.615 seconds, umax = (6.7e-02, 3.1e-02, 3.4e-02) ms⁻¹, wall time: 59.108 seconds
[ Info: i: 0280, t: 1.269 hours, Δt: 11.339 seconds, umax = (7.0e-02, 3.2e-02, 3.5e-02) ms⁻¹, wall time: 59.708 seconds
[ Info: i: 0300, t: 1.332 hours, Δt: 11.014 seconds, umax = (7.0e-02, 3.4e-02, 3.6e-02) ms⁻¹, wall time: 1.003 minutes
[ Info: i: 0320, t: 1.391 hours, Δt: 10.908 seconds, umax = (7.4e-02, 3.4e-02, 3.5e-02) ms⁻¹, wall time: 1.011 minutes
[ Info: i: 0340, t: 1.449 hours, Δt: 10.412 seconds, umax = (7.2e-02, 3.7e-02, 3.9e-02) ms⁻¹, wall time: 1.021 minutes
[ Info: i: 0360, t: 1.506 hours, Δt: 10.247 seconds, umax = (7.5e-02, 3.7e-02, 3.6e-02) ms⁻¹, wall time: 1.032 minutes
[ Info: i: 0380, t: 1.562 hours, Δt: 8.956 seconds, umax = (8.0e-02, 3.7e-02, 3.6e-02) ms⁻¹, wall time: 1.038 minutes
[ Info: i: 0400, t: 1.610 hours, Δt: 9.258 seconds, umax = (7.3e-02, 3.7e-02, 4.2e-02) ms⁻¹, wall time: 1.046 minutes
[ Info: i: 0420, t: 1.662 hours, Δt: 9.862 seconds, umax = (7.3e-02, 4.2e-02, 4.0e-02) ms⁻¹, wall time: 1.055 minutes
[ Info: i: 0440, t: 1.716 hours, Δt: 9.912 seconds, umax = (7.2e-02, 3.9e-02, 4.0e-02) ms⁻¹, wall time: 1.064 minutes
[ Info: i: 0460, t: 1.769 hours, Δt: 10.078 seconds, umax = (7.3e-02, 4.1e-02, 4.2e-02) ms⁻¹, wall time: 1.074 minutes
[ Info: i: 0480, t: 1.826 hours, Δt: 9.823 seconds, umax = (7.4e-02, 4.1e-02, 3.7e-02) ms⁻¹, wall time: 1.082 minutes
[ Info: i: 0500, t: 1.878 hours, Δt: 9.119 seconds, umax = (7.6e-02, 4.3e-02, 4.0e-02) ms⁻¹, wall time: 1.090 minutes
[ Info: i: 0520, t: 1.930 hours, Δt: 9.459 seconds, umax = (7.7e-02, 4.7e-02, 4.1e-02) ms⁻¹, wall time: 1.102 minutes
[ Info: i: 0540, t: 1.981 hours, Δt: 9.381 seconds, umax = (7.8e-02, 4.7e-02, 4.0e-02) ms⁻¹, wall time: 1.109 minutes
[ Info: i: 0560, t: 2.032 hours, Δt: 9.732 seconds, umax = (7.7e-02, 4.5e-02, 4.3e-02) ms⁻¹, wall time: 1.118 minutes
[ Info: i: 0580, t: 2.086 hours, Δt: 9.039 seconds, umax = (8.2e-02, 4.9e-02, 4.3e-02) ms⁻¹, wall time: 1.130 minutes
[ Info: i: 0600, t: 2.136 hours, Δt: 9.252 seconds, umax = (8.2e-02, 4.8e-02, 4.2e-02) ms⁻¹, wall time: 1.136 minutes
[ Info: i: 0620, t: 2.188 hours, Δt: 9.451 seconds, umax = (7.9e-02, 4.8e-02, 4.0e-02) ms⁻¹, wall time: 1.146 minutes
[ Info: i: 0640, t: 2.239 hours, Δt: 8.826 seconds, umax = (8.4e-02, 4.8e-02, 4.5e-02) ms⁻¹, wall time: 1.154 minutes
[ Info: i: 0660, t: 2.287 hours, Δt: 8.860 seconds, umax = (8.8e-02, 4.7e-02, 4.4e-02) ms⁻¹, wall time: 1.163 minutes
[ Info: i: 0680, t: 2.336 hours, Δt: 8.926 seconds, umax = (8.0e-02, 5.1e-02, 4.3e-02) ms⁻¹, wall time: 1.175 minutes
[ Info: i: 0700, t: 2.386 hours, Δt: 9.050 seconds, umax = (7.9e-02, 4.8e-02, 4.2e-02) ms⁻¹, wall time: 1.181 minutes
[ Info: i: 0720, t: 2.434 hours, Δt: 8.845 seconds, umax = (8.0e-02, 4.8e-02, 4.5e-02) ms⁻¹, wall time: 1.192 minutes
[ Info: i: 0740, t: 2.483 hours, Δt: 8.402 seconds, umax = (8.1e-02, 5.1e-02, 4.4e-02) ms⁻¹, wall time: 1.200 minutes
[ Info: i: 0760, t: 2.528 hours, Δt: 8.819 seconds, umax = (8.6e-02, 5.0e-02, 4.5e-02) ms⁻¹, wall time: 1.209 minutes
[ Info: i: 0780, t: 2.578 hours, Δt: 8.732 seconds, umax = (8.2e-02, 5.1e-02, 4.6e-02) ms⁻¹, wall time: 1.218 minutes
[ Info: i: 0800, t: 2.624 hours, Δt: 8.440 seconds, umax = (8.3e-02, 5.1e-02, 4.4e-02) ms⁻¹, wall time: 1.227 minutes
[ Info: i: 0820, t: 2.669 hours, Δt: 8.558 seconds, umax = (8.6e-02, 5.2e-02, 5.0e-02) ms⁻¹, wall time: 1.239 minutes
[ Info: i: 0840, t: 2.717 hours, Δt: 8.672 seconds, umax = (8.4e-02, 5.3e-02, 4.5e-02) ms⁻¹, wall time: 1.245 minutes
[ Info: i: 0860, t: 2.764 hours, Δt: 8.611 seconds, umax = (8.7e-02, 5.2e-02, 4.7e-02) ms⁻¹, wall time: 1.256 minutes
[ Info: i: 0880, t: 2.812 hours, Δt: 8.494 seconds, umax = (8.2e-02, 5.4e-02, 4.9e-02) ms⁻¹, wall time: 1.263 minutes
[ Info: i: 0900, t: 2.857 hours, Δt: 8.656 seconds, umax = (8.5e-02, 5.1e-02, 4.7e-02) ms⁻¹, wall time: 1.273 minutes
[ Info: i: 0920, t: 2.905 hours, Δt: 8.651 seconds, umax = (8.8e-02, 5.4e-02, 4.8e-02) ms⁻¹, wall time: 1.282 minutes
[ Info: i: 0940, t: 2.952 hours, Δt: 8.549 seconds, umax = (8.7e-02, 5.5e-02, 4.6e-02) ms⁻¹, wall time: 1.291 minutes
[ Info: i: 0960, t: 3.000 hours, Δt: 8.089 seconds, umax = (8.3e-02, 5.7e-02, 4.7e-02) ms⁻¹, wall time: 1.300 minutes
[ Info: i: 0980, t: 3.040 hours, Δt: 8.086 seconds, umax = (8.2e-02, 5.5e-02, 4.7e-02) ms⁻¹, wall time: 1.309 minutes
[ Info: i: 1000, t: 3.083 hours, Δt: 8.247 seconds, umax = (8.4e-02, 5.8e-02, 4.8e-02) ms⁻¹, wall time: 1.318 minutes
[ Info: i: 1020, t: 3.129 hours, Δt: 7.951 seconds, umax = (8.4e-02, 5.7e-02, 4.9e-02) ms⁻¹, wall time: 1.327 minutes
[ Info: i: 1040, t: 3.171 hours, Δt: 8.410 seconds, umax = (8.5e-02, 5.5e-02, 4.9e-02) ms⁻¹, wall time: 1.339 minutes
[ Info: i: 1060, t: 3.218 hours, Δt: 8.391 seconds, umax = (8.5e-02, 5.4e-02, 4.9e-02) ms⁻¹, wall time: 1.345 minutes
[ Info: i: 1080, t: 3.264 hours, Δt: 8.116 seconds, umax = (8.7e-02, 5.8e-02, 4.9e-02) ms⁻¹, wall time: 1.358 minutes
[ Info: i: 1100, t: 3.308 hours, Δt: 7.741 seconds, umax = (8.9e-02, 5.8e-02, 4.5e-02) ms⁻¹, wall time: 1.366 minutes
[ Info: i: 1120, t: 3.350 hours, Δt: 7.925 seconds, umax = (8.5e-02, 6.0e-02, 4.3e-02) ms⁻¹, wall time: 1.376 minutes
[ Info: i: 1140, t: 3.394 hours, Δt: 7.609 seconds, umax = (8.7e-02, 6.0e-02, 5.1e-02) ms⁻¹, wall time: 1.386 minutes
[ Info: i: 1160, t: 3.434 hours, Δt: 7.698 seconds, umax = (8.9e-02, 5.8e-02, 4.6e-02) ms⁻¹, wall time: 1.396 minutes
[ Info: i: 1180, t: 3.477 hours, Δt: 8.121 seconds, umax = (8.9e-02, 5.6e-02, 4.6e-02) ms⁻¹, wall time: 1.405 minutes
[ Info: i: 1200, t: 3.520 hours, Δt: 8.057 seconds, umax = (8.9e-02, 5.7e-02, 4.7e-02) ms⁻¹, wall time: 1.415 minutes
[ Info: i: 1220, t: 3.566 hours, Δt: 7.930 seconds, umax = (8.6e-02, 6.2e-02, 4.9e-02) ms⁻¹, wall time: 1.425 minutes
[ Info: i: 1240, t: 3.610 hours, Δt: 8.017 seconds, umax = (8.9e-02, 5.9e-02, 4.6e-02) ms⁻¹, wall time: 1.434 minutes
[ Info: i: 1260, t: 3.655 hours, Δt: 8.090 seconds, umax = (8.6e-02, 6.2e-02, 4.8e-02) ms⁻¹, wall time: 1.444 minutes
[ Info: i: 1280, t: 3.698 hours, Δt: 8.019 seconds, umax = (8.7e-02, 6.4e-02, 4.6e-02) ms⁻¹, wall time: 1.455 minutes
[ Info: i: 1300, t: 3.743 hours, Δt: 7.836 seconds, umax = (8.7e-02, 6.5e-02, 4.6e-02) ms⁻¹, wall time: 1.465 minutes
[ Info: i: 1320, t: 3.784 hours, Δt: 8.188 seconds, umax = (9.0e-02, 6.9e-02, 5.2e-02) ms⁻¹, wall time: 1.474 minutes
[ Info: i: 1340, t: 3.830 hours, Δt: 7.981 seconds, umax = (9.0e-02, 6.2e-02, 4.8e-02) ms⁻¹, wall time: 1.484 minutes
[ Info: i: 1360, t: 3.873 hours, Δt: 8.272 seconds, umax = (9.1e-02, 6.3e-02, 4.8e-02) ms⁻¹, wall time: 1.494 minutes
[ Info: i: 1380, t: 3.919 hours, Δt: 8.111 seconds, umax = (9.3e-02, 5.8e-02, 5.0e-02) ms⁻¹, wall time: 1.507 minutes
[ Info: i: 1400, t: 3.964 hours, Δt: 7.865 seconds, umax = (9.2e-02, 5.7e-02, 5.4e-02) ms⁻¹, wall time: 1.513 minutes
[ Info: Simulation is stopping after running for 1.523 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)
record(fig, "langmuir_turbulence.mp4", frames, framerate=8) do i
n[] = i
end
This page was generated using Literate.jl.