Analysis
Analysis tools for computing diagnostics, statistics, and derived quantities.
CFL Condition
Compute stable timesteps based on flow velocity.
using Tarang
# Create CFL calculator
cfl = CFL(problem;
safety=0.5, # Safety factor (0.3-0.5 typical)
max_change=1.5, # Max dt increase per step
min_change=0.5 # Max dt decrease per step
)
# Register velocity field
add_velocity!(cfl, u)
# Optional limits
cfl.max_dt = 0.01
cfl.min_dt = 1e-8
# Compute timestep
dt = compute_timestep(cfl)Global Reductions
Compute global statistics across MPI processes.
using MPI
# Create reducer
reducer = GlobalArrayReducer(MPI.COMM_WORLD)
# Maximum value
global_max = reduce_scalar(reducer, local_max, MPI.MAX)
# Sum
global_sum = reduce_scalar(reducer, local_sum, MPI.SUM)
# Mean (requires division by total elements)
global_mean = global_sum / total_elementsFlow Statistics
Kinetic Energy
function compute_kinetic_energy(u, reducer)
local_energy = 0.0
for component in u.components
Tarang.ensure_layout!(component, :g)
local_energy += sum(get_grid_data(component).^2) / 2
end
return reduce_scalar(reducer, local_energy, MPI.SUM)
endEnstrophy
function compute_enstrophy(u, reducer)
# For 2D: ω = ∂v/∂x - ∂u/∂y
# Enstrophy = ∫ ω² dV
ux, uy = u.components[1], u.components[2]
# Compute vorticity (simplified)
# ... derivative calculation ...
return reduce_scalar(reducer, local_enstrophy, MPI.SUM)
endReynolds Number
function compute_reynolds_number(u, nu, L, reducer)
Tarang.ensure_layout!(u.components[1], :g)
# RMS velocity
local_u2 = sum(get_grid_data(u.components[1]).^2)
global_u2 = reduce_scalar(reducer, local_u2, MPI.SUM)
u_rms = sqrt(global_u2 / total_points)
return u_rms * L / nu
endHeat Transfer
Nusselt Number
function compute_nusselt(T, w, L, kappa, reducer)
Tarang.ensure_layout!(T, :g)
Tarang.ensure_layout!(w, :g)
# Convective heat flux
local_flux = sum(get_grid_data(T) .* get_grid_data(w))
global_flux = reduce_scalar(reducer, local_flux, MPI.SUM)
# Normalize
flux_mean = global_flux / total_points
# Nusselt = 1 + convective/conductive
Nu = 1.0 + flux_mean * L / kappa
return Nu
endSpectral Analysis
Energy Spectrum
function compute_spectrum(field, kmax)
Tarang.ensure_layout!(field, :c)
# Initialize spectrum bins
E_k = zeros(kmax)
# Get wavenumbers
k = get_wavenumbers(field.bases[1])
# Bin energy by wavenumber
for (i, ki) in enumerate(k)
k_bin = round(Int, abs(ki))
if 1 <= k_bin <= kmax
E_k[k_bin] += abs2(get_coeff_data(field)[i])
end
end
return E_k
endShell-Averaged 3D Spectrum
function compute_3d_spectrum(u, kmax)
E_k = zeros(kmax)
for component in u.components
Tarang.ensure_layout!(component, :c)
kx = get_wavenumbers(component.bases[1])
ky = get_wavenumbers(component.bases[2])
kz = get_wavenumbers(component.bases[3])
for i in eachindex(kx), j in eachindex(ky), k in eachindex(kz)
k_mag = sqrt(kx[i]^2 + ky[j]^2 + kz[k]^2)
k_bin = round(Int, k_mag)
if 1 <= k_bin <= kmax
E_k[k_bin] += abs2(get_coeff_data(component)[i,j,k])
end
end
end
return E_k
endTime Series
Recording Diagnostics
# Storage
times = Float64[]
energies = Float64[]
nusselts = Float64[]
# During simulation
while solver.sim_time < t_end
step!(solver, dt)
# Record
push!(times, solver.sim_time)
push!(energies, compute_kinetic_energy(u, reducer))
push!(nusselts, compute_nusselt(T, w, L, kappa, reducer))
endSaving Time Series
if MPI.Comm_rank(MPI.COMM_WORLD) == 0
using JLD2
@save "diagnostics.jld2" times energies nusselts
endSpatial Averages
Horizontal Average
function horizontal_average(field)
Tarang.ensure_layout!(field, :g)
# Average over x (first axis)
mean(get_grid_data(field), dims=1)
endVolume Average
function volume_average(field, reducer)
Tarang.ensure_layout!(field, :g)
local_sum = sum(get_grid_data(field))
global_sum = reduce_scalar(reducer, local_sum, MPI.SUM)
return global_sum / total_points
endSee Also
- Output: File output
- Solvers: Time integration
- API: Analysis: Complete reference