CoarseGrainingEnergyFluxes.jl
Spatial coarse-graining analysis of energy fluxes in geophysical fluid dynamics.
Overview
This package implements the coarse-graining (spatial filtering) framework. The core quantity is the cross-scale energy flux Π(x, ℓ), the local rate of kinetic-energy transfer across scale ℓ. Around it:
| Function | |
|---|---|
| Cumulative coarse energy and the filtering spectral density Ẽ(k_ℓ) | Diagnostics.cumulative_energy, Diagnostics.filtering_spectrum |
| Strain/convergence split of Π | Diagnostics.compute_Π_strain_convergence |
| Rotational/divergent (Helmholtz) split, with the interaction channel | Diagnostics.compute_Π_decomposed |
| Leonard/Cross/Reynolds stress decomposition | Diagnostics.tau_decomposition |
| Tracer / buoyancy-variance flux | Diagnostics.tracer_variance_flux |
| Enstrophy flux, in the same gauge as Π | Diagnostics.enstrophy_flux |
| Energy per scale band | Diagnostics.band_energies |
| Variable-density (Favre) budget: Π, baropycnal work Λ, pressure dilatation | Diagnostics.compressible_flux |
Six filter kernels are available — top-hat, Gaussian, sharp-spectral, smooth-hat, hyper-Gaussian and the high-order M^I/M^II pair — and they are not interchangeable: only some have a spectral transfer function, only some can carry a filtering spectrum, and only some are valid for Π. check_setup reports which, for your grid and scale, without running anything.
The approach follows Aluie (2011, 2019) and Aluie, Hecht, & Vallis (2018), using real-space convolution kernels to separate large-scale (ū) and sub-scale (u') motions at each point in space.
RealSpace() names that operator: a local space average against the compact kernel. Which engine evaluates it is chosen from the grid and kernel — exact prefix sums for a top-hat in 2-D and 3-D, separable passes for the factorizing kernels, a vectorized banded footprint otherwise — and check_setup reports the choice and its cost before anything runs. Two further engines evaluate the same convolution by transform under method = AutoMethod(): a padded FFT of the sampled kernel on a uniform Cartesian lattice, and a transform along the longitude ring on a global sphere, where a great-circle kernel depends on the longitude difference alone. See Architecture for the full table.
Sweeping scales, the parts of a plan that the scale does not change — transform objects, measure prefix scans, point sorts — are built once by Filtering.plan_filter_sweep rather than once per scale, and every diagnostic has an in-place form whose workspace holds only the buffers its configuration can reach.
Every diagnostic works across the full grid×dimensionality matrix — StructuredGrid (1D, 2D, and true 3D Cartesian or spherical-volumetric), CurvilinearGrid (model-native orthogonal curvilinear meshes), and UnstructuredGrid (scattered points, via k-d tree neighbors, Voronoi cell areas, and non-uniform spectral transforms) — see Architecture for the full capability matrix.
Installation
using Pkg
Pkg.add(url="https://github.com/jbphyswx/CoarseGrainingEnergyFluxes.jl")Key Concepts
Coarse-Graining vs Fourier Spectra
Traditional Fourier spectral analysis provides wavenumber spectra E(k) but:
- Requires periodicity or windowing
- Cannot localize energy transfer in physical space
- Poorly suited to irregular domains and boundaries
Coarse-graining provides:
- Local energy flux Π(x, ℓ) at every grid point
- Works on arbitrary domains with masked (excluded) regions
- No periodicity assumption
- Direct physical-space interpretation
The Energy Flux Π
The cross-scale energy flux at position x and scale ℓ is:
Π(x, ℓ) = −τ_ℓ : S̄_ℓwhere:
- S̄ℓ = ½(∇ūℓ + (∇ū_ℓ)ᵀ) is the filtered strain rate
- τℓ = (u⊗u)̄ℓ − ūℓ⊗ūℓ is the sub-scale stress
When Π > 0, energy flows from large to small scales (forward cascade). When Π < 0, energy flows from small to large scales (inverse cascade).
The Filtering Spectrum
The cumulative coarse-grained kinetic energy (cumulative_energy; Sadek & Aluie 2018, Eq. 15) is the domain average of the filtered KE:
E(ℓ) = ½ ⟨|ū_ℓ|²⟩This is a cumulative quantity, not a spectral density. The filtering spectral density (filtering_spectrum; their Eq. 14 — comparable to a Fourier energy spectrum) is its derivative with respect to the filtering wavenumber k_ℓ = L/ℓ:
Ẽ(k_ℓ) = d/dk_ℓ [ ½ ⟨|ū_ℓ|²⟩ ]coarse_grain returns both (result.cumulative_energy, result.filtering_spectrum, result.wavenumber).
Ẽ(k_ℓ) ≥ 0 is guaranteed only for a kernel whose |Ĝ(k)|² is monotone decreasing, which the default TopHatKernel is not. By default coarse_grain therefore throws rather than return a density it cannot vouch for — but the condition is sufficient, not necessary, so all three readings are reachable: kernel = GaussianKernel() for a guaranteed-non-negative spectrum, spectrum = CGEF.Diagnostics.ForceSpectrum() to compute the top-hat's anyway and check its sign yourself, or spectrum = CGEF.Diagnostics.NoSpectrum() for Π and the cumulative energy alone. Note also that a p = 1 kernel — top-hat and Gaussian both — saturates the recovered slope at k⁻³; see Diagnostics.AbstractSpectrumPolicy and Diagnostics.filtering_spectrum.