Diagnostics
This page describes diagnostic quantities and analysis tools in QGYBJ+.jl.
Energy Diagnostics
Flow Kinetic Energy
The kinetic energy of the balanced flow:
\[KE = \frac{1}{2}\int (u^2 + v^2) \, dV\]
KE = flow_kinetic_energy(state.u, state.v)Flow Potential Energy (Spectral)
Available potential energy computed in spectral space with proper dealiasing:
\[PE = \frac{1}{2}\int \frac{f_0^2}{N^2}\left(\frac{\partial\psi}{\partial z}\right)^2 dV\]
# Compute buoyancy field first (b = ∂ψ/∂z)
bk = similar(state.psi)
# ... compute vertical derivative ...
PE = flow_potential_energy_spectral(bk, grid, params)Wave Energy
The wave kinetic energy is computed per YBJ+ equation (4.7):
\[\text{WKE} = \frac{1}{2}\int |LA|^2 \, dV\]
where $LA$ is computed directly using the L operator from equation (1.3):
\[L = \partial_z \left( \frac{f^2}{N^2} \partial_z \right)\]
So $LA = \partial_z(a(z) \times C)$ where $a(z) = f^2/N^2$ and $C = \partial A/\partial z$.
# Detailed wave energy components
WKE, WPE, WCE = compute_detailed_wave_energy(state, grid, params)
# Simple wave kinetic energy
WE = compute_wave_energy(state, grid, plans; params=params)
# Vertically-averaged wave kinetic energy using LA = L⁺A + (k_h²/4)A
WE = wave_energy_vavg(state.L⁺A, state.A, grid, plans)WKE uses $|LA|^2$ where LA is computed as $LA = L^+A + (k_h^2/4)A$ in spectral space. This uses the original YBJ $L$ operator (not $L^+$) for physical consistency.
Energy Diagnostics Output Files
QGYBJ+.jl automatically saves energy diagnostics to separate files in a dedicated diagnostic/ folder, following the structure used in the Fortran QG_YBJp code.
Output Folder Structure
output_dir/
├── state0001.nc # Field snapshots
├── state0002.nc
├── diagnostics_0001.nc # Legacy combined diagnostics
└── diagnostic/ # Separate energy files
├── wave_KE.nc # Wave kinetic energy time series
├── wave_PE.nc # Wave potential energy time series
├── wave_CE.nc # Wave correction energy (YBJ+)
├── mean_flow_KE.nc # Mean flow kinetic energy
├── mean_flow_PE.nc # Mean flow potential energy
└── total_energy.nc # Summary file with all energiesWave Kinetic Energy (WKE)
The wave kinetic energy is computed per YBJ+ equation (4.7):
\[\text{WKE} = \frac{1}{2} \sum_{k_x, k_y, z} |LA|^2 - \text{(dealiasing correction)}\]
where $LA = L^+A + (k_h^2/4)A$ in spectral space. This relationship comes from:
- $L^+A = LA - \frac{k_h^2}{4}A$ (definition of L⁺ operator)
- In spectral space: $\Delta \rightarrow -k_h^2$, so $L^+A = LA - (k_h^2/4)A$
- Therefore: $LA = L^+A + (k_h^2/4)A$
Physical interpretation: WKE represents the kinetic energy contained in the near-inertial wave field, computed from the wave velocity amplitude $LA$ using the original YBJ $L$ operator (not the evolved envelope $L^+A$). This ensures consistency with the energy budget in the YBJ+ formulation.
Wave Potential Energy (WPE)
The wave potential energy captures vertical wave structure through $C = \partial A/\partial z$:
\[\text{WPE} = \frac{1}{2} \sum_{k_x, k_y, z} \frac{k_h^2}{2 a_{ell}} \left( |C_R|^2 + |C_I|^2 \right)\]
where:
- $a_{ell} = f_0^2 / N^2$ is the elliptic coefficient
- $C = \partial A / \partial z$ is the vertical derivative of wave amplitude
- $k_h^2 = k_x^2 + k_y^2$ is the horizontal wavenumber squared
Physical interpretation: WPE represents the potential energy from wave-induced isopycnal displacements. It scales with $N^2 \eta^2$ where $\eta$ is the vertical displacement.
Wave Correction Energy (WCE)
The YBJ+ formulation includes a higher-order correction term:
\[\text{WCE} = \frac{1}{2} \sum_{k_x, k_y, z} \frac{k_h^4}{8 a_{ell}^2} \left( |A_R|^2 + |A_I|^2 \right)\]
Physical interpretation: WCE is a higher-order correction from the YBJ+ equation that accounts for horizontal wave dispersion. It becomes important for short horizontal wavelengths.
Mean Flow Kinetic Energy
The balanced flow kinetic energy is computed from the geostrophic velocities:
\[\text{KE}_{flow} = \frac{1}{2} \sum_{k_x, k_y, z} \left( |u_k|^2 + |v_k|^2 \right) - \frac{1}{2} |u(k_h=0)|^2\]
where the velocities are derived from the streamfunction:
\[u = -\frac{\partial \psi}{\partial y} = -ik_y \hat{\psi}, \quad v = \frac{\partial \psi}{\partial x} = ik_x \hat{\psi}\]
This gives:
\[|u|^2 + |v|^2 = k_h^2 |\hat{\psi}|^2\]
Physical interpretation: $\text{KE}_{flow}$ represents the kinetic energy of the large-scale quasi-geostrophic eddies and jets.
Mean Flow Potential Energy
The available potential energy from buoyancy:
\[\text{PE}_{flow} = \frac{1}{2} \sum_{k_x, k_y, z} \frac{f_0^2}{N^2} |b_k|^2\]
where buoyancy $b$ is related to the streamfunction via thermal wind balance:
\[b = \frac{\partial \psi}{\partial z}\]
Physical interpretation: $\text{PE}_{flow}$ represents the energy stored in tilted isopycnals (density surfaces). It can be released through baroclinic instability.
Total Energy Conservation
In the inviscid limit, the total energy is conserved:
\[E_{total} = \underbrace{\text{KE}_{flow} + \text{PE}_{flow}}_{\text{Mean flow}} + \underbrace{\text{WKE} + \text{WPE} + \text{WCE}}_{\text{Waves}} = \text{const}\]
Energy exchange between waves and mean flow occurs via:
- Refraction: Waves gain/lose energy from vorticity gradients
- Wave feedback $q^w$: Waves modify the effective PV
Using the EnergyDiagnosticsManager
The energy diagnostics manager is automatically created during simulation setup:
# Energy diagnostics are computed and saved automatically during simulation
sim = setup_simulation(config)
run_simulation!(sim)
# After simulation, files are in:
# output_dir/diagnostic/wave_KE.nc
# output_dir/diagnostic/wave_PE.nc
# etc.For manual control:
using QGYBJplus
# Create manager manually
energy_manager = EnergyDiagnosticsManager(
"output_dir";
output_interval=1.0 # Time between outputs
)
# Record energies at each diagnostic time
record_energies!(
energy_manager,
current_time,
wave_KE, wave_PE, wave_CE,
mean_flow_KE, mean_flow_PE
)
# Write all files at end
write_all_energy_files!(energy_manager)Reading Energy Output Files
using NCDatasets
# Read wave KE time series
ds = NCDataset("output_dir/diagnostic/wave_KE.nc", "r")
time = ds["time"][:]
wave_KE = ds["wave_KE"][:]
close(ds)
# Read total energy summary
ds = NCDataset("output_dir/diagnostic/total_energy.nc", "r")
time = ds["time"][:]
total_wave = ds["total_wave_energy"][:]
total_flow = ds["total_flow_energy"][:]
total = ds["total_energy"][:]
close(ds)
# Plot energy evolution
using Plots
plot(time, total, label="Total", linewidth=2)
plot!(time, total_flow, label="Mean flow", linestyle=:dash)
plot!(time, total_wave, label="Waves", linestyle=:dot)
xlabel!("Time")
ylabel!("Energy")Energy Budget Verification
Check energy conservation:
# After simulation
ds = NCDataset("output_dir/diagnostic/total_energy.nc", "r")
E = ds["total_energy"][:]
close(ds)
# Relative change
dE_rel = (E[end] - E[1]) / E[1]
println("Relative energy change: $(dE_rel)")
if abs(dE_rel) < 1e-6
println("Energy well conserved")
else
println("Check time step or dissipation settings")
endMPI-Aware Energy Functions
For parallel simulations, use the global reduction versions:
# Physical-space energy with MPI reduction
KE = flow_kinetic_energy_global(state.u, state.v, mpi_config)
WE_B, WE_A = wave_energy_global(state.L⁺A, state.A, mpi_config)
# Spectral energy with MPI reduction
KE_spectral = flow_kinetic_energy_spectral_global(uk, vk, grid, params; mpi_config=mpi_config)
PE_spectral = flow_potential_energy_spectral_global(bk, grid, params; mpi_config=mpi_config)
WKE, WPE, WCE = wave_energy_spectral_global(BR, BI, AR, AI, CR, CI, grid, params; mpi_config=mpi_config)Wave Diagnostics
Wave Amplitude
# |A|² field
A2 = abs2.(state.A)
# Maximum amplitude
A_max = maximum(sqrt.(A2))
# Volume-averaged amplitude
using Statistics
A_rms = sqrt(mean(A2))Wave Velocities
Compute wave-induced velocities (in-place):
# Updates state.u, state.v with wave velocities
compute_wave_velocities!(state, grid; plans=plans, params=params, compute_w=true)Omega Equation
The omega equation computes ageostrophic vertical velocity:
\[\nabla^2 w + \frac{f_0^2}{N^2}\frac{\partial^2 w}{\partial z^2} = \frac{2f_0}{N^2}J\left(\frac{\partial\psi}{\partial z}, \nabla^2\psi\right)\]
Use omega_eqn_rhs! to compute the right-hand side:
# Compute omega equation RHS
omega_eqn_rhs!(rhs, state.psi, grid, params, plans)Time Series Analysis
Recording Diagnostics Manually
# Initialize storage arrays
time_series = Float64[]
KE_series = Float64[]
WKE_series = Float64[]
for step = 1:nsteps
# ... time stepping code ...
# Record diagnostics
push!(time_series, step * dt)
push!(KE_series, flow_kinetic_energy(state.u, state.v))
WKE, WPE, WCE = compute_detailed_wave_energy(state, grid, params)
push!(WKE_series, WKE)
endEnergy Conservation Check
# Total energy should be conserved (inviscid)
E_total = KE_series .+ WKE_series
# Check conservation
dE = (E_total[end] - E_total[1]) / E_total[1]
println("Relative energy change: $dE")
if abs(dE) > 1e-6
@warn "Energy not well conserved!"
endDiagnostic Output Example
function compute_diagnostics(state, grid, params)
diag = Dict{String, Any}()
# Energy
diag["KE"] = flow_kinetic_energy(state.u, state.v)
diag["WKE"], diag["WPE"], diag["WCE"] = compute_detailed_wave_energy(state, grid, params)
diag["WE_B"], diag["WE_A"] = wave_energy(state.L⁺A, state.A)
# Extrema
diag["A_max"] = maximum(abs.(state.A))
diag["psi_max"] = maximum(abs.(state.psi))
return diag
end
function print_diagnostics(diag, step, time)
println("=" ^ 60)
println("Step: $step, Time: $(round(time, digits=4))")
println("-" ^ 60)
println(" KE = $(round(diag["KE"], sigdigits=6))")
println(" WKE = $(round(diag["WKE"], sigdigits=6))")
println(" |A|_max = $(round(diag["A_max"], sigdigits=4))")
println("=" ^ 60)
endAPI Reference
See the Physics API Reference for complete documentation of diagnostic functions:
flow_kinetic_energy- Mean flow kinetic energyflow_kinetic_energy_spectral- Spectral kinetic energy with dealiasingflow_potential_energy_spectral- Spectral potential energywave_energy- Basic wave energy from B and A fieldswave_energy_spectral- Spectral wave energy componentscompute_detailed_wave_energy- Detailed WKE, WPE, WCE computationcompute_wave_energy- Simple wave energy computationflow_kinetic_energy_global/wave_energy_global- MPI-aware global reductionsomega_eqn_rhs!- Omega equation RHS computationEnergyDiagnosticsManager- Automatic energy output to separate files