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{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.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{GPU, 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: 44.8 KiBAn "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.4 KiBRunning the simulation
This part is easy,
run!(simulation)[ Info: Initializing simulation...
[ Info: i: 0000, t: 0 seconds, Δt: 49.500 seconds, umax = (1.5e-03, 8.6e-04, 1.5e-03) ms⁻¹, wall time: 0 seconds
[ Info: ... simulation initialization complete (18.214 seconds)
[ Info: Executing initial time step...
[ Info: ... initial time step complete (2.830 seconds).
[ Info: i: 0020, t: 11.907 minutes, Δt: 30.159 seconds, umax = (2.8e-02, 1.1e-02, 2.2e-02) ms⁻¹, wall time: 22.186 seconds
[ Info: i: 0040, t: 20.408 minutes, Δt: 20.023 seconds, umax = (4.1e-02, 1.0e-02, 1.8e-02) ms⁻¹, wall time: 22.736 seconds
[ Info: i: 0060, t: 26.485 minutes, Δt: 15.791 seconds, umax = (4.9e-02, 1.5e-02, 2.0e-02) ms⁻¹, wall time: 23.118 seconds
[ Info: i: 0080, t: 31.655 minutes, Δt: 15.693 seconds, umax = (4.9e-02, 1.6e-02, 2.3e-02) ms⁻¹, wall time: 23.574 seconds
[ Info: i: 0100, t: 36.885 minutes, Δt: 14.518 seconds, umax = (5.0e-02, 2.1e-02, 2.3e-02) ms⁻¹, wall time: 24.018 seconds
[ Info: i: 0120, t: 41.715 minutes, Δt: 15.133 seconds, umax = (5.3e-02, 2.0e-02, 2.5e-02) ms⁻¹, wall time: 24.476 seconds
[ Info: i: 0140, t: 46.461 minutes, Δt: 14.578 seconds, umax = (5.4e-02, 2.1e-02, 2.6e-02) ms⁻¹, wall time: 24.954 seconds
[ Info: i: 0160, t: 51.184 minutes, Δt: 14.282 seconds, umax = (5.8e-02, 2.1e-02, 2.8e-02) ms⁻¹, wall time: 25.418 seconds
[ Info: i: 0180, t: 55.670 minutes, Δt: 13.498 seconds, umax = (6.0e-02, 2.6e-02, 2.9e-02) ms⁻¹, wall time: 25.922 seconds
[ Info: i: 0200, t: 1 hour, Δt: 13.122 seconds, umax = (6.1e-02, 2.5e-02, 2.9e-02) ms⁻¹, wall time: 26.266 seconds
[ Info: i: 0220, t: 1.070 hours, Δt: 11.446 seconds, umax = (6.4e-02, 2.8e-02, 3.2e-02) ms⁻¹, wall time: 26.735 seconds
[ Info: i: 0240, t: 1.132 hours, Δt: 12.115 seconds, umax = (6.4e-02, 2.8e-02, 3.2e-02) ms⁻¹, wall time: 28.271 seconds
[ Info: i: 0260, t: 1.196 hours, Δt: 11.704 seconds, umax = (6.7e-02, 2.9e-02, 3.3e-02) ms⁻¹, wall time: 28.724 seconds
[ Info: i: 0280, t: 1.260 hours, Δt: 11.415 seconds, umax = (7.0e-02, 3.1e-02, 3.6e-02) ms⁻¹, wall time: 29.280 seconds
[ Info: i: 0300, t: 1.323 hours, Δt: 11.369 seconds, umax = (7.0e-02, 3.1e-02, 3.6e-02) ms⁻¹, wall time: 29.631 seconds
[ Info: i: 0320, t: 1.383 hours, Δt: 11.161 seconds, umax = (7.5e-02, 3.4e-02, 3.4e-02) ms⁻¹, wall time: 30.090 seconds
[ Info: i: 0340, t: 1.444 hours, Δt: 10.986 seconds, umax = (7.2e-02, 3.6e-02, 3.7e-02) ms⁻¹, wall time: 30.665 seconds
[ Info: i: 0360, t: 1.503 hours, Δt: 10.498 seconds, umax = (7.3e-02, 3.5e-02, 3.8e-02) ms⁻¹, wall time: 31.276 seconds
[ Info: i: 0380, t: 1.560 hours, Δt: 9.465 seconds, umax = (7.5e-02, 4.4e-02, 4.2e-02) ms⁻¹, wall time: 31.592 seconds
[ Info: i: 0400, t: 1.612 hours, Δt: 9.294 seconds, umax = (7.4e-02, 4.3e-02, 4.0e-02) ms⁻¹, wall time: 32.062 seconds
[ Info: i: 0420, t: 1.665 hours, Δt: 10.291 seconds, umax = (7.5e-02, 4.1e-02, 4.5e-02) ms⁻¹, wall time: 32.510 seconds
[ Info: i: 0440, t: 1.721 hours, Δt: 10.163 seconds, umax = (7.3e-02, 3.9e-02, 5.0e-02) ms⁻¹, wall time: 32.973 seconds
[ Info: i: 0460, t: 1.775 hours, Δt: 9.929 seconds, umax = (7.5e-02, 3.8e-02, 4.3e-02) ms⁻¹, wall time: 33.466 seconds
[ Info: i: 0480, t: 1.830 hours, Δt: 9.995 seconds, umax = (7.3e-02, 4.4e-02, 4.0e-02) ms⁻¹, wall time: 33.944 seconds
[ Info: i: 0500, t: 1.883 hours, Δt: 9.749 seconds, umax = (7.8e-02, 4.5e-02, 4.0e-02) ms⁻¹, wall time: 34.424 seconds
[ Info: i: 0520, t: 1.935 hours, Δt: 9.252 seconds, umax = (7.7e-02, 4.7e-02, 4.2e-02) ms⁻¹, wall time: 34.927 seconds
[ Info: i: 0540, t: 1.988 hours, Δt: 9.618 seconds, umax = (7.9e-02, 4.5e-02, 4.3e-02) ms⁻¹, wall time: 35.369 seconds
[ Info: i: 0560, t: 2.040 hours, Δt: 9.456 seconds, umax = (7.8e-02, 4.3e-02, 3.9e-02) ms⁻¹, wall time: 35.851 seconds
[ Info: i: 0580, t: 2.091 hours, Δt: 9.108 seconds, umax = (8.0e-02, 4.5e-02, 4.1e-02) ms⁻¹, wall time: 36.433 seconds
[ Info: i: 0600, t: 2.141 hours, Δt: 9.131 seconds, umax = (7.9e-02, 4.7e-02, 4.3e-02) ms⁻¹, wall time: 36.791 seconds
[ Info: i: 0620, t: 2.189 hours, Δt: 9.014 seconds, umax = (8.0e-02, 5.0e-02, 4.1e-02) ms⁻¹, wall time: 37.281 seconds
[ Info: i: 0640, t: 2.239 hours, Δt: 8.991 seconds, umax = (7.8e-02, 5.0e-02, 4.2e-02) ms⁻¹, wall time: 37.740 seconds
[ Info: i: 0660, t: 2.288 hours, Δt: 8.939 seconds, umax = (8.1e-02, 5.0e-02, 4.3e-02) ms⁻¹, wall time: 38.228 seconds
[ Info: i: 0680, t: 2.336 hours, Δt: 8.407 seconds, umax = (8.0e-02, 5.1e-02, 4.2e-02) ms⁻¹, wall time: 38.886 seconds
[ Info: i: 0700, t: 2.382 hours, Δt: 8.516 seconds, umax = (8.3e-02, 4.9e-02, 4.6e-02) ms⁻¹, wall time: 39.219 seconds
[ Info: i: 0720, t: 2.428 hours, Δt: 8.480 seconds, umax = (8.3e-02, 4.9e-02, 4.0e-02) ms⁻¹, wall time: 39.852 seconds
[ Info: i: 0740, t: 2.476 hours, Δt: 8.687 seconds, umax = (8.5e-02, 5.0e-02, 4.1e-02) ms⁻¹, wall time: 40.321 seconds
[ Info: i: 0760, t: 2.524 hours, Δt: 8.725 seconds, umax = (8.3e-02, 5.0e-02, 4.2e-02) ms⁻¹, wall time: 40.877 seconds
[ Info: i: 0780, t: 2.571 hours, Δt: 8.263 seconds, umax = (8.1e-02, 5.0e-02, 4.3e-02) ms⁻¹, wall time: 41.422 seconds
[ Info: i: 0800, t: 2.615 hours, Δt: 8.429 seconds, umax = (8.3e-02, 5.3e-02, 4.6e-02) ms⁻¹, wall time: 41.974 seconds
[ Info: i: 0820, t: 2.662 hours, Δt: 8.624 seconds, umax = (8.5e-02, 5.4e-02, 4.4e-02) ms⁻¹, wall time: 42.452 seconds
[ Info: i: 0840, t: 2.709 hours, Δt: 8.279 seconds, umax = (8.1e-02, 5.4e-02, 4.5e-02) ms⁻¹, wall time: 42.934 seconds
[ Info: i: 0860, t: 2.755 hours, Δt: 8.720 seconds, umax = (8.4e-02, 5.5e-02, 4.7e-02) ms⁻¹, wall time: 43.544 seconds
[ Info: i: 0880, t: 2.803 hours, Δt: 8.757 seconds, umax = (8.4e-02, 5.5e-02, 4.3e-02) ms⁻¹, wall time: 43.885 seconds
[ Info: i: 0900, t: 2.850 hours, Δt: 8.165 seconds, umax = (8.2e-02, 5.7e-02, 4.2e-02) ms⁻¹, wall time: 44.392 seconds
[ Info: i: 0920, t: 2.895 hours, Δt: 8.501 seconds, umax = (8.4e-02, 5.9e-02, 4.5e-02) ms⁻¹, wall time: 44.847 seconds
[ Info: i: 0940, t: 2.942 hours, Δt: 8.275 seconds, umax = (8.2e-02, 5.6e-02, 4.6e-02) ms⁻¹, wall time: 45.350 seconds
[ Info: i: 0960, t: 2.988 hours, Δt: 7.893 seconds, umax = (8.5e-02, 5.9e-02, 4.5e-02) ms⁻¹, wall time: 45.827 seconds
[ Info: i: 0980, t: 3.030 hours, Δt: 7.749 seconds, umax = (8.6e-02, 6.0e-02, 4.9e-02) ms⁻¹, wall time: 46.307 seconds
[ Info: i: 1000, t: 3.075 hours, Δt: 8.348 seconds, umax = (8.5e-02, 5.7e-02, 4.8e-02) ms⁻¹, wall time: 46.775 seconds
[ Info: i: 1020, t: 3.121 hours, Δt: 8.387 seconds, umax = (8.4e-02, 6.0e-02, 4.7e-02) ms⁻¹, wall time: 47.280 seconds
[ Info: i: 1040, t: 3.167 hours, Δt: 8.371 seconds, umax = (8.5e-02, 5.8e-02, 5.1e-02) ms⁻¹, wall time: 47.747 seconds
[ Info: i: 1060, t: 3.213 hours, Δt: 8.176 seconds, umax = (8.7e-02, 6.0e-02, 5.0e-02) ms⁻¹, wall time: 48.231 seconds
[ Info: i: 1080, t: 3.259 hours, Δt: 8.084 seconds, umax = (9.4e-02, 6.0e-02, 4.5e-02) ms⁻¹, wall time: 48.814 seconds
[ Info: i: 1100, t: 3.305 hours, Δt: 8.407 seconds, umax = (8.8e-02, 6.1e-02, 4.2e-02) ms⁻¹, wall time: 49.196 seconds
[ Info: i: 1120, t: 3.349 hours, Δt: 7.953 seconds, umax = (8.6e-02, 6.5e-02, 4.9e-02) ms⁻¹, wall time: 49.716 seconds
[ Info: i: 1140, t: 3.393 hours, Δt: 8.042 seconds, umax = (8.7e-02, 6.3e-02, 4.2e-02) ms⁻¹, wall time: 50.172 seconds
[ Info: i: 1160, t: 3.437 hours, Δt: 8.227 seconds, umax = (8.7e-02, 5.8e-02, 5.4e-02) ms⁻¹, wall time: 50.674 seconds
[ Info: i: 1180, t: 3.484 hours, Δt: 8.359 seconds, umax = (8.5e-02, 6.2e-02, 4.7e-02) ms⁻¹, wall time: 51.144 seconds
[ Info: i: 1200, t: 3.527 hours, Δt: 8.084 seconds, umax = (8.4e-02, 6.1e-02, 4.8e-02) ms⁻¹, wall time: 51.633 seconds
[ Info: i: 1220, t: 3.573 hours, Δt: 8.114 seconds, umax = (8.9e-02, 6.2e-02, 4.9e-02) ms⁻¹, wall time: 52.105 seconds
[ Info: i: 1240, t: 3.617 hours, Δt: 7.961 seconds, umax = (8.9e-02, 5.6e-02, 4.6e-02) ms⁻¹, wall time: 52.593 seconds
[ Info: i: 1260, t: 3.661 hours, Δt: 8.085 seconds, umax = (8.9e-02, 5.9e-02, 5.0e-02) ms⁻¹, wall time: 53.068 seconds
[ Info: i: 1280, t: 3.705 hours, Δt: 7.987 seconds, umax = (9.1e-02, 6.0e-02, 4.7e-02) ms⁻¹, wall time: 53.562 seconds
[ Info: i: 1300, t: 3.749 hours, Δt: 7.814 seconds, umax = (8.9e-02, 6.2e-02, 4.8e-02) ms⁻¹, wall time: 54.048 seconds
[ Info: i: 1320, t: 3.792 hours, Δt: 7.771 seconds, umax = (9.0e-02, 6.4e-02, 4.8e-02) ms⁻¹, wall time: 54.538 seconds
[ Info: i: 1340, t: 3.833 hours, Δt: 7.949 seconds, umax = (9.3e-02, 6.6e-02, 5.0e-02) ms⁻¹, wall time: 55.008 seconds
[ Info: i: 1360, t: 3.877 hours, Δt: 7.875 seconds, umax = (9.4e-02, 6.6e-02, 4.6e-02) ms⁻¹, wall time: 55.530 seconds
[ Info: i: 1380, t: 3.921 hours, Δt: 8.123 seconds, umax = (8.8e-02, 6.4e-02, 5.0e-02) ms⁻¹, wall time: 56.233 seconds
[ Info: i: 1400, t: 3.966 hours, Δt: 7.930 seconds, umax = (8.8e-02, 6.5e-02, 4.8e-02) ms⁻¹, wall time: 56.565 seconds
[ Info: Simulation is stopping after running for 56.960 seconds.
[ 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
endThis page was generated using Literate.jl.