Filtered available potential energy equation

The FilteredAvailablePotentialEnergyEquation module provides diagnostics for the available potential energy of the filtered buoyancy field. It is the potential-energy counterpart of the Filtered kinetic energy equation. We define the APE of the filtered flow as

\[e_a^l = e_a(\bar b, z, t) = \int_{z^\star(\bar b, t)}^{z} \left[b^\star(\tilde z, t) - \bar b\right] \mathrm{d}\tilde z ,\]

computed by FilteredAvailablePotentialEnergy. Note that the reference profile $b^\star(z^\star)$ is the reference state of the full buoyancy $b$, rather than sorted from $\bar b$. Because of this, it is necessary to look up the position of a buoyancy parcel in the filtered fields in the full reference profile $b^\star(z^\star)$ in calculating some terms, which makes it necessary to use ProfileLookup, since it's the only method of obtaining the reference height that has this capability.

Deriving the filtered available potential energy equation

The budget of $e_a^l$ retraces the full-APE derivation with the filtered buoyancy taking the place of $b$ and the material derivative taken along the filtered flow, $D^l/Dt = \partial_t + \bar u_i \partial_i$:

\[\frac{D^l e_a^l}{D t} = \left.\frac{\partial e_a^l}{\partial \bar b}\right|_{z,t} \frac{D^l \bar b}{D t} + \left.\frac{\partial e_a^l}{\partial z}\right|_{\bar b,t} \frac{D^l z}{D t} + \left.\frac{\partial e_a^l}{\partial t}\right|_{z,\bar b} \frac{D^l t}{D t} ,\]

which we can simplify to

\[\frac{D^l e_a^l}{D t} = \left.\frac{\partial e_a^l}{\partial \bar b}\right|_{z,t} \frac{D^l \bar b}{D t} + \left.\frac{\partial e_a^l}{\partial z}\right|_{\bar b,t} \bar w + R^l , \qquad R^l = \int_{z^\star(\bar b)}^{z} \partial_t b^\star(\tilde z, t) \, \mathrm{d}\tilde z .\]

We can then evaluate the partial derivatives above from the definition of $e_a^l$, similarly to the $e_a$ derivation. In the first we get an "empty" integral from $z^\star$ to $z$, which produces the displacement potential of the filtered buoyancy $\Upsilon^l$. The second simplifies to the integrand of $e_a^l$, producing the buoyancy anomaly $b_r^l$:

\[\left.\frac{\partial e_a^l}{\partial \bar b}\right|_{z} = z^\star(\bar b, t) - z = \Upsilon^l , \qquad \left.\frac{\partial e_a^l}{\partial z}\right|_{\bar b} = b^\star(z, t) - \bar b = -b_r^l .\]

Note that $\Upsilon^l$ is different from $\Upsilon$ since it includes $z^\star(\bar b, t)$ and not $z^\star(b, t)$. The former is the reference height of the filtered buoyancy: the height at which a parcel of the filtered buoyancy would sit in the reference profile unfiltered buoyancy. Similarly, the buoyancy anomaly $b_r^l$ is measured against the reference profile of the full, unfiltered buoyancy $b^\star(z, t)$ – the reference profile of the filtered buoyancy does not appear anywhere in this equation.

The material derivative of the filtered buoyancy comes from filtering the tracer equation of $b$ (the filter commutes with derivatives) and splitting the filtered advective flux into a resolved and a subfilter part, $\overline{u_i b} = \bar u_i \, \bar b + \tau(u_i, b)$:

\[\frac{D^l \bar b}{D t} = -\partial_i \tau(u_i, b) - \partial_i \bar q_i , \qquad \tau(u_i, b) = \overline{b u_i} - \bar b \, \bar u_i ,\]

where $\tau(u_i, b)$ is the subfilter buoyancy flux and $\bar q_i$ is the filtered closure's diffusive flux low-pass filtered. The term $\Upsilon^l \, D^l \bar b / D t$ splits into two transport divergences plus the contractions $\tau(u_i, b) \, \partial_i \Upsilon^l$ and $\bar q_i \, \partial_i \Upsilon^l$. Writing the advective part as a divergence as well ($\partial_i \bar u_i = 0$, since filtering preserves incompressibility), the filtered APE budget is

\[\partial_t e_a^l = \underbrace{-\partial_i(\bar u_i e_a^l)}_{\text{advection}} \underbrace{-\,\bar w \, b_r^l}_{\text{APE to KE conversion}} \underbrace{-\,\Pi_a}_{\text{cross-scale flux}} \underbrace{-\,\partial_i\!\left[\Upsilon^l \left(\tau(u_i, b) + \bar q_i\right)\right]}_{\text{subfilter and diffusive transport}} \underbrace{-\,\varepsilon_a^l}_{\text{dissipation}} + \underbrace{R^l}_{\text{reference tendency}} ,\]

with

\[\Pi_a = -\tau(u_i, b) \, \partial_i \Upsilon^l , \qquad \varepsilon_a^l = -\bar q_i \, \partial_i \Upsilon^l .\]

Term by term this is the full-field budget evaluated on the filtered state plus extra terms related to the filter ($\Pi_a$ and $-\partial_j\Upsilon^l \tau(u_i, b)$). $\Pi_a$ reappears with the opposite sign in the Subfilter available potential energy equation, which makes it a transfer across the filter scale rather than a source or a sink, and the conversion $\bar w \, b_r^l$ likewise reappears with the opposite sign in the filtered kinetic energy budget.

Importantly, while $R$ integrates to zero in a closed domain (Winters et al., 1995), $R^l$ does not inherit that property. Given its subfilter counterpart $R^s = \overline{R} - R^l$ appears in the Subfilter available potential energy equation, we get that $\int R^l \, \mathrm{d}V = -\int R^s \, \mathrm{d}V$. Thus, interestingly, an evolving reference profile redistributes APE across the filter scale as well as in space. With a reference profile held fixed in time (implemented here with a ProfileLookup holding plain arrays) $R^l$ vanishes identically.

Terms and diagnostics

Five of the quantities above have diagnostics; the two transport divergences, the anomaly $b_r^l$, and the reference tendency have none.

QuantityExpressionDiagnostic
Filtered available potential energy$e_a^l = e_a(\bar b, z)$FilteredAvailablePotentialEnergy
Displacement potential$\Upsilon^l = z^\star(\bar b) - z$FilteredAvailablePotentialEnergyDisplacementPotential
Advection$\partial_i(\bar u_i e_a^l)$not implemented
Buoyancy anomaly$b_r^l = \bar b - b^\star(z, t)$not implemented
APE to KE conversion$\bar w \, b_r^l$FilteredAvailablePotentialToKineticEnergyConversion
Cross-scale flux$\Pi_a = -\tau(u_i, b) \, \partial_i \Upsilon^l$AvailablePotentialEnergyCrossScaleFlux
Subfilter and diffusive transport$\partial_i\left[\Upsilon^l (\tau(u_i, b) + \bar q_i)\right]$not implemented
Dissipation$\varepsilon_a^l = -\bar q_i \, \partial_i \Upsilon^l$FilteredAvailablePotentialEnergyDissipationRate
Reference tendency$R^l = \int_{z^\star(\bar b)}^{z} \partial_t b^\star(\tilde z, t) \, \mathrm{d}\tilde z$not implemented

$\Upsilon^l$ also answers to DisplacementPotential, and $\varepsilon_a^l$ to DissipationRate. Both aliases are scoped to this module, as their full-field namesakes are to the available potential energy equation: using Oceanostics.FilteredAvailablePotentialEnergyEquation brings them in, using Oceanostics does not, since unprefixed neither name says which budget it belongs to nor at which scale.

Summary of $e_a^l$ equation terms

Oceanostics.FilteredAvailablePotentialEnergyEquation.FilteredAvailablePotentialEnergyType
FilteredAvailablePotentialEnergy(
    model,
    filter;
    method,
    geopotential_height
)

Return the available potential energy of the filtered buoyancy field eₐˡ, the local APE that the scales a low-pass filter keeps would carry on their own:

    eₐˡ = eₐ(b̄, z) = ∫_{z✶(b̄)}^{z} [b✶(z̃) - b̄] dz̃ ,   b̄ = filter(b)

where eₐ is the local available potential energy density (AvailablePotentialEnergy) and b✶(z̃) is a reference profile the filtered buoyancy is looked up in. It is the potential-energy counterpart of the kinetic energy of the filtered flow eₖˡ (FilteredKineticEnergy), and the filter splits the APE into it and the subfilter remainder eₐˢ = filter(eₐ) - eₐˡ (SubFilterAvailablePotentialEnergy).

For that split to mean anything the filtered buoyancy has to be measured against the same reference profile as the full one, ordinarily the sorted state of the full buoyancy b, rather than against a profile sorted from itself. Looking a field up in a profile it did not produce is exactly what ProfileLookup was built for, so method must be one:

  • ProfileLookup() (the default) sorts the profile from the model's own buoyancy — a VerticalSort column built internally and re-sorted on every compute!, so the reference state tracks the flow.
  • ProfileLookup(z✶_column) borrows a column you already built with reference_height(model, method=VerticalSort()), sharing its sort across several diagnostics.
  • ProfileLookup(b✶, z✶) with plain arrays holds the reference profile fixed in time, which also makes the diagnostic sort-free: each compute! is then a filter plus a binary-search lookup.

A second method, FilteredAvailablePotentialEnergy(model, z✶ˡ), takes a reference height you built yourself from a filtered buoyancy, z✶ˡ = reference_height(Field(filter(b)); method=ProfileLookup(…)), which is how a single lookup is shared with FilteredAvailablePotentialEnergyDissipationRate. The filtered buoyancy is read off z✶ˡ itself, so no filter is needed there.

filter is any callable mapping a field to its low-pass-filtered counterpart, e.g. a reusable GaussianFilter or BoxFilter. The filtered buoyancy is materialized as a Field internally (so the separable filter takes its fast staged path), and the returned object is a lazy operation over it and the reference height, ready for Field, Integral and OutputWriters. It lives at (Center, Center, Center), per unit mass (units m² s⁻²):

using Oceananigans, Oceanosticsgrid = RectilinearGrid(size=(4, 4, 4), extent=(1, 1, 1), topology=(Periodic, Periodic, Bounded))model = NonhydrostaticModel(grid; buoyancy=BuoyancyTracer(), tracers=:b)filter = GaussianFilter(; dims=(1, 2, 3), σ=0.1)FilteredAvailablePotentialEnergy(model, filter)# outputFilteredAvailablePotentialEnergy KernelFunctionOperation at (Center, Center, Center)├── grid: 4×4×4 RectilinearGrid{Float64, Periodic, Periodic, Bounded} on CPU with 3×3×3 halo├── kernel_function: filtered_ape_ccc (generic function with 1 method)└── arguments: ("Field", "Field", "Field")└── computes: available potential energy of the filtered buoyancy  eₐ(b̄, z)

A convenience method FilteredAvailablePotentialEnergy(model; σ, dims, boundary, N) builds the Gaussian filter for you from a standard deviation σ (with σ = ℓ / (2√(2 ln 2)) for a FWHM ). geopotential_height enters the buoyancy construction exactly as in reference_height.

source
Oceanostics.FilteredAvailablePotentialEnergyEquation.FilteredAvailablePotentialEnergyDisplacementPotentialType
FilteredAvailablePotentialEnergyDisplacementPotential(
    model,
    filter;
    method,
    geopotential_height
)

Return the displacement potential of the filtered buoyancy field Υˡ, how far below its actual height the reference height of the filtered buoyancy sits:

    Υˡ = z✶(b̄) - z ,   b̄ = filter(b)

It is the full-field displacement potential Υ = z✶ - z (AvailablePotentialEnergyDisplacementPotential) evaluated on the filtered state, and it is the derivative of the filtered available potential energy with respect to the filtered buoyancy, Υˡ = ∂eₐˡ/∂b̄ (FilteredAvailablePotentialEnergy). That is what makes it the field the filtered APE budget contracts its fluxes against: against the filtered diffusive flux it gives the dissipation FilteredAvailablePotentialEnergyDissipationRate εₐˡ = -q̄ᵢ∂ᵢΥˡ, and against the subfilter buoyancy flux the cross-scale flux AvailablePotentialEnergyCrossScaleFlux Πₐ = -τ(uᵢ, b)∂ᵢΥˡ.

As with every diagnostic here the filtered buoyancy is measured against the reference profile of the full field, so method has to be a ProfileLookup, for the reason FilteredAvailablePotentialEnergy gives. The lookup also makes z✶ˡ a function of buoyancy alone, which is what differentiating Υˡ needs (see AvailablePotentialEnergyDisplacementPotential).

A second method, FilteredAvailablePotentialEnergyDisplacementPotential(model, z✶ˡ), takes a reference height you built yourself from a filtered buoyancy, z✶ˡ = reference_height(Field(filter(b)); method=ProfileLookup(…)), which is how one lookup is shared with the other filtered diagnostics. The filtered buoyancy is read off z✶ˡ itself, so no filter is needed there. Materialize the result with Field and hand it to those diagnostics through their upsilon keyword to compute a single Υˡ per output rather than one apiece.

filter is any callable mapping a field to its low-pass-filtered counterpart, e.g. a reusable GaussianFilter or BoxFilter. The filtered buoyancy is materialized as a Field internally (so the separable filter takes its fast staged path), and the returned object is a lazy operation over the reference height, ready for Field, Integral and OutputWriters, re-filtering and re-sorting as the simulation evolves. It lives at (Center, Center, Center) and is a length (units m):

using Oceananigans, Oceanosticsgrid = RectilinearGrid(size=(4, 4, 4), extent=(1, 1, 1), topology=(Periodic, Periodic, Bounded))model = NonhydrostaticModel(grid; buoyancy=BuoyancyTracer(), tracers=:b)filter = GaussianFilter(; dims=(1, 2, 3), σ=0.1)FilteredAvailablePotentialEnergyDisplacementPotential(model, filter)# outputFilteredAvailablePotentialEnergyDisplacementPotential KernelFunctionOperation at (Center, Center, Center)├── grid: 4×4×4 RectilinearGrid{Float64, Periodic, Periodic, Bounded} on CPU with 3×3×3 halo├── kernel_function: filtered_upsilon_ccc (generic function with 1 method)└── arguments: ("Field",)└── computes: displacement potential of the filtered buoyancy  z✶(b̄) - z

using Oceanostics.FilteredAvailablePotentialEnergyEquation additionally brings in DisplacementPotential, short enough to read beside the other terms of an eₐˡ budget. That alias is scoped to this module, as DissipationRate is: using Oceanostics does not bring it in, since unprefixed it says neither which budget's displacement it names nor at which scale.

A convenience method FilteredAvailablePotentialEnergyDisplacementPotential(model; σ, dims, boundary, N) builds the Gaussian filter for you from a standard deviation σ (with σ = ℓ / (2√(2 ln 2)) for a FWHM ). geopotential_height enters the buoyancy construction exactly as in reference_height.

source
Oceanostics.FilteredAvailablePotentialEnergyEquation.FilteredAvailablePotentialToKineticEnergyConversionType
FilteredAvailablePotentialToKineticEnergyConversion(
    model,
    filter;
    method,
    geopotential_height
)

Return the conversion of filtered available potential energy into filtered kinetic energy w̄b_rˡ, the rate at which the scales a low-pass filter keeps release their APE to the filtered flow:

    w̄ b_rˡ ,   b_rˡ =- b✶(z) ,   b̄ = filter(b)

Here b_rˡ is the buoyancy anomaly of the filtered field against the reference state, and b✶(z) is the reference profile read at the parcel's own height z rather than at the reference height z✶ its buoyancy would take it to (reference_buoyancy_at_height). It is the term the filtered APE budget carries as -w̄b_rˡ and the filtered kinetic energy budget as +w̄b_rˡ, which is what makes it a reversible exchange rather than a source or a sink; w̄b_rˡ > 0 converts filtered APE into filtered KE.

Note the reference profile is not filtered: b_rˡ = b̄ - b✶(z), not filter(b_r) = b̄ - filter(b✶(z)). The two differ once the filter acts in the vertical, and only the first is the conversion the filtered budget carries, since it comes from differentiating eₐˡ = eₐ(b̄, z) — which is itself measured against the full field's reference state (FilteredAvailablePotentialEnergy) — with respect to z. For a purely horizontal filter the two coincide, b✶ being a function of z alone.

method has to be a ProfileLookup, for the reason FilteredAvailablePotentialEnergy gives, and it supplies the profile b✶(z) is read from. Unlike the other diagnostics here this one needs no reference height of its own, so it takes no z✶ˡ; share a profile by passing the same ProfileLookup(z✶_column) the others get.

filter is any callable mapping a field to its low-pass-filtered counterpart, e.g. a reusable GaussianFilter or BoxFilter. The filtered buoyancy, the filtered vertical velocity and b✶(z) are materialized as Fields internally, so the returned object is a lazy operation ready for Field, Integral and OutputWriters, re-filtering and re-sorting as the simulation evolves. It lives at (Center, Center, Center), per unit mass (units m² s⁻³):

using Oceananigans, Oceanosticsgrid = RectilinearGrid(size=(4, 4, 4), extent=(1, 1, 1), topology=(Periodic, Periodic, Bounded))model = NonhydrostaticModel(grid; buoyancy=BuoyancyTracer(), tracers=:b)filter = GaussianFilter(; dims=(1, 2, 3), σ=0.1)FilteredAvailablePotentialToKineticEnergyConversion(model, filter)# outputFilteredAvailablePotentialToKineticEnergyConversion KernelFunctionOperation at (Center, Center, Center)├── grid: 4×4×4 RectilinearGrid{Float64, Periodic, Periodic, Bounded} on CPU with 3×3×3 halo├── kernel_function: filtered_ape_to_ke_conversion_ccc (generic function with 1 method)└── arguments: ("Field", "Field", "Field")└── computes: filtered APE to filtered KE conversion  w̄(b̄ - b✶(z))

A convenience method FilteredAvailablePotentialToKineticEnergyConversion(model; σ, dims, boundary, N) builds the Gaussian filter for you from a standard deviation σ (with σ = ℓ / (2√(2 ln 2)) for a FWHM ).

source
Oceanostics.FilteredAvailablePotentialEnergyEquation.AvailablePotentialEnergyCrossScaleFluxType
AvailablePotentialEnergyCrossScaleFlux(
    model,
    filter;
    dims,
    method,
    geopotential_height
)

Return the cross-scale (scale-to-scale) available-potential-energy flux Πₐ, the rate at which a low-pass filter transfers available potential energy from the filtered to the subfilter scales:

    Πₐ = -τ(uᵢ, b) ∂ᵢΥˡ ,   τ(uᵢ, b) = filter(b uᵢ) - b̄ ūᵢ ,   Υˡ = z✶(b̄) - z

where τ(uᵢ, b) is the subfilter buoyancy flux (the subfilter_covariance of uᵢ and b) and Υˡ is the FilteredAvailablePotentialEnergyDisplacementPotential. It is the APE analogue of KineticEnergyCrossScaleFlux, with the subfilter buoyancy flux in place of the subfilter stress and ∇Υˡ in place of the resolved strain; Πₐ > 0 is forward (downscale, filtered → subfilter) transfer. It appears as -Πₐ in the filtered APE budget and as +Πₐ in the subfilter one, which is what makes it a transfer rather than a source or a sink. The result lives at (Center, Center, Center), per unit mass (units m² s⁻³).

method has to be a ProfileLookup, for the reason FilteredAvailablePotentialEnergy gives: Υˡ measures the filtered buoyancy against a profile it did not itself produce, ordinarily the sorted state of the full buoyancy. The lookup also makes z✶ a function of buoyancy alone, which is what differentiating Υˡ needs.

dims selects which directions are summed over and which velocities are filtered: the default dims = (1, 2, 3) gives the full flux, while dims = (1, 3) gives the 2D xz flux Πₐ = -(τ₁∂₁Υˡ + τ₃∂₃Υˡ). The filter's own directions are set independently inside filter.

filter is any callable mapping a field to its low-pass-filtered counterpart, e.g. a reusable GaussianFilter or BoxFilter. The filtered fields and Υˡ are materialized internally, so the returned object is a lazy operation ready for Field, Integral and OutputWriters, re-filtering and re-sorting as the simulation evolves:

using Oceananigans, Oceanosticsgrid = RectilinearGrid(size=(4, 4, 4), extent=(1, 1, 1), topology=(Periodic, Periodic, Bounded))model = NonhydrostaticModel(grid; buoyancy=BuoyancyTracer(), tracers=:b)filter = GaussianFilter(; dims=(1, 2, 3), σ=0.1)AvailablePotentialEnergyCrossScaleFlux(model, filter)# outputAvailablePotentialEnergyCrossScaleFlux KernelFunctionOperation at (Center, Center, Center)├── grid: 4×4×4 RectilinearGrid{Float64, Periodic, Periodic, Bounded} on CPU with 3×3×3 halo├── kernel_function: cross_scale_ape_flux_ccc (generic function with 1 method)└── arguments: ("Oceananigans.AbstractOperations.UnaryOperation",)└── computes: cross-scale available potential energy flux  -τ(uᵢ, b)∂ᵢΥˡ

A second method, AvailablePotentialEnergyCrossScaleFlux(model, filter, z✶ˡ; upsilon, dims), takes a reference height you built from the filtered buoyancy (see FilteredAvailablePotentialEnergy), so one lookup — and, through upsilon, one Υˡ (FilteredAvailablePotentialEnergyDisplacementPotential) — can serve this flux and the other filtered-state diagnostics. The filtered buoyancy is read off z✶ˡ itself; filter is still needed there, to build the subfilter buoyancy flux.

A convenience method AvailablePotentialEnergyCrossScaleFlux(model; σ, dims, boundary, N) builds the Gaussian filter for you from a standard deviation σ (with σ = ℓ / (2√(2 ln 2)) for a FWHM ).

source
Oceanostics.FilteredAvailablePotentialEnergyEquation.FilteredAvailablePotentialEnergyDissipationRateType
FilteredAvailablePotentialEnergyDissipationRate(
    model,
    filter;
    method,
    geopotential_height
)

Return the available potential energy dissipation rate of the filtered buoyancy field εₐˡ, the rate at which diffusion removes APE from the scales a low-pass filter keeps:

    εₐˡ = -q̄ᵢ ∂ᵢΥˡ ,   q̄ᵢ = filter(qᵢ) ,   Υˡ = z✶(b̄) - z ,   b̄ = filter(b)

It is the full-field contraction εₐ = -qᵢ∂ᵢΥ (AvailablePotentialEnergyDissipationRate) evaluated on the filtered state: the closure's diffusive buoyancy flux qᵢ low-pass filtered, against the displacement potential of the filtered buoyancy Υˡ (FilteredAvailablePotentialEnergyDisplacementPotential). It is the diffusive sink of the budget of FilteredAvailablePotentialEnergy, and it mirrors what FilteredKineticEnergyDissipationRate is to the filtered kinetic energy. The subfilter remainder εₐˢ = filter(εₐ) - εₐˡ is SubFilterAvailablePotentialEnergyDissipationRate.

The flux is filtered, q̄ᵢ = filter(qᵢ(b)), not recomputed from the filtered buoyancy: the filtered buoyancy equation carries the divergence of the filtered flux, so -q̄ᵢ∂ᵢΥˡ is the dissipation that appears in the filtered-state budget. The two forms agree for a constant diffusivity, where the filter commutes with the flux, and differ once κ varies in space — the same distinction FilteredKineticEnergyDissipationRate draws for the viscous flux.

method has to be a ProfileLookup, for the reason FilteredAvailablePotentialEnergy gives, and the lookup also makes z✶ˡ a function of buoyancy alone — the property that differentiating Υˡ needs (see AvailablePotentialEnergyDisplacementPotential). Like AvailablePotentialEnergyDissipationRate, this diagnostic needs the buoyancy to be a tracer the closure diffuses (BuoyancyTracer only) and a closure that supplies a diffusive flux.

A second method, FilteredAvailablePotentialEnergyDissipationRate(model, filter, z✶ˡ; upsilon), takes a reference height you built from the filtered buoyancy (see FilteredAvailablePotentialEnergy), so one lookup can serve both diagnostics; upsilon takes a Υˡ you already have (a Field of FilteredAvailablePotentialEnergyDisplacementPotential), so writing both out costs one Υˡ rather than two. filter is still needed there, to filter the fluxes.

filter is any callable mapping a field to its low-pass-filtered counterpart, e.g. a reusable GaussianFilter or BoxFilter. The filtered fluxes, the filtered buoyancy and Υˡ are materialized as Fields internally, so the returned object is a lazy operation ready for Field, Integral and OutputWriters, recomputing (re-sorting included) as the simulation evolves. It lives at (Center, Center, Center), per unit mass (units m² s⁻³):

using Oceananigans, Oceanosticsgrid = RectilinearGrid(size=(4, 4, 4), extent=(1, 1, 1), topology=(Periodic, Periodic, Bounded))model = NonhydrostaticModel(grid; buoyancy=BuoyancyTracer(), tracers=:b, closure=ScalarDiffusivity=1e-4))filter = GaussianFilter(; dims=(1, 2, 3), σ=0.1)FilteredAvailablePotentialEnergyDissipationRate(model, filter)# outputFilteredAvailablePotentialEnergyDissipationRate KernelFunctionOperation at (Center, Center, Center)├── grid: 4×4×4 RectilinearGrid{Float64, Periodic, Periodic, Bounded} on CPU with 3×3×3 halo├── kernel_function: filtered_ape_dissipation_rate_ccc (generic function with 1 method)└── arguments: ("Field", "Field", "Field", "Field")└── computes: available potential energy dissipation rate of the filtered buoyancy  -q̄ᵢ∂ᵢΥˡ

A convenience method FilteredAvailablePotentialEnergyDissipationRate(model; σ, dims, boundary, N) builds the Gaussian filter for you from a standard deviation σ (with σ = ℓ / (2√(2 ln 2)) for a FWHM ).

source