Subfilter available potential energy equation

The SubFilterAvailablePotentialEnergyEquation module provides diagnostics for the available potential energy carried by the scales that a low-pass spatial filter $\overline{(\,\cdot\,)}$ removes from the buoyancy field. It is the potential-energy counterpart of the Subfilter kinetic energy equation: where that module splits the kinetic energy across the filter scale, this one splits the local available potential energy $e_a$ of the available potential energy equation. The other half of the split, the APE of the filtered buoyancy and its dissipation, lives in the Filtered available potential energy equation and is re-exported here.

The subfilter available potential energy

Both the full and the filtered buoyancy are measured against one shared reference profile $(b^\star, z^\star)$, ordinarily the sorted state of the full buoyancy field. The subfilter available potential energy is the filtered full APE minus the APE of the filtered buoyancy $\bar b$,

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

computed by SubFilterAvailablePotentialEnergy; $e_a(\bar b, z)$ is FilteredAvailablePotentialEnergy.

$e_a^s$ is guaranteed to be non-negative only when the filter acts in the horizontal directions.

Looking the filtered buoyancy up in a profile it did not itself produce is exactly what ProfileLookup was built for, so these diagnostics accept only that reference-height method: the default sorts the model's own buoyancy into a VerticalSort column on every compute!, a column you built yourself can be shared across diagnostics, and a profile given as plain arrays holds the reference state fixed in time (which also makes the diagnostics sort-free).

<<<<<<< HEAD Because $e_a$ is convex in buoyancy, a filter with no vertical component keeps $e_a^s \geq 0$ pointwise, by Jensen's inequality; a filter that acts vertically mixes heights as well as buoyancies and can produce locally negative values in the current formulation.

Oceanostics.SubFilterAvailablePotentialEnergyEquation.SubFilterAvailablePotentialEnergyType
SubFilterAvailablePotentialEnergy(
    model,
    filter;
    method,
    geopotential_height
)

Return the subfilter-scale (SFS) available potential energy eₐˢ, the available potential energy carried by the scales that a low-pass filter removes from the buoyancy field — the filtered full APE minus the APE of the filtered buoyancy b̄ = filter(b):

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

where eₐ is the local available potential energy density (AvailablePotentialEnergy), eₐ(b̄, z) is the APE of the filtered buoyancy (FilteredAvailablePotentialEnergy), and both terms are measured against one shared reference profile (b✶, z✶): the filtered buoyancy is looked up in the same profile the full field is measured against, which is exactly what ProfileLookup was built for. It is the potential-energy counterpart of the subfilter kinetic energy eₖˢ (SubFilterKineticEnergy), just as FilteredAvailablePotentialEnergy is that of the filtered kinetic energy eₖˡ.

Because the two states have to share one profile, method must be a ProfileLookup:

  • 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 two binary-search lookups.

eₐ is convex in buoyancy (∂²eₐ/∂b² = ∂z✶/∂b ≥ 0 on a stable profile), so when the filter has no vertical component eₐˢ ≥ 0 pointwise, by Jensen's inequality. A filter that acts vertically mixes heights as well as buoyancies and can produce locally negative values, as can, marginally, filtered buoyancies that fall between the profile's entries (the lookup then takes the nearest class).

filter is any callable mapping a field to its low-pass-filtered counterpart, e.g. a reusable GaussianFilter or BoxFilter. The filtered buoyancy and the filtered APE are materialized as Fields internally (so the separable filter takes its fast staged path), and the returned object is a lazy operation over them, 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)SubFilterAvailablePotentialEnergy(model, filter)# outputSubFilterAvailablePotentialEnergy KernelFunctionOperation at (Center, Center, Center)├── grid: 4×4×4 RectilinearGrid{Float64, Periodic, Periodic, Bounded} on CPU with 3×3×3 halo├── kernel_function: subfilter_ape_ccc (generic function with 1 method)└── arguments: ("Oceananigans.AbstractOperations.BinaryOperation",)└── computes: subfilter available potential energy  filter(eₐ) - eₐˡ

A convenience method SubFilterAvailablePotentialEnergy(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

The subfilter available potential energy dissipation

The diffusive sink of the $e_a^s$ budget is the subfilter APE dissipation rate

\[\varepsilon_a^s = \overline{\varepsilon_a} - \varepsilon_a^l , \qquad \varepsilon_a^l = -\bar q_i \, \partial_i \Upsilon^l ,\]

computed by SubFilterAvailablePotentialEnergyDissipationRate: the filtered full-field dissipation $\varepsilon_a = -q_i \partial_i \Upsilon$ (AvailablePotentialEnergyDissipationRate) minus the same contraction evaluated on the filtered state, $\varepsilon_a^l$ (FilteredAvailablePotentialEnergyDissipationRate), with $\bar q_i$ the closure's diffusive buoyancy flux low-pass filtered and $\Upsilon^l = z^\star(\bar b) - z$ the displacement potential of the filtered buoyancy. Filtering the flux, rather than recomputing it from $\bar b$, is the same choice the Filtered kinetic energy equation makes for the viscous flux; the Filtered available potential energy equation has the details.

The cross-scale APE flux $\Pi_a$, which enters this budget as a source ($+\Pi_a$) and the filtered one as a sink, is AvailablePotentialEnergyCrossScaleFlux, defined in the Filtered available potential energy equation and re-exported here.

Oceanostics.SubFilterAvailablePotentialEnergyEquation.SubFilterAvailablePotentialEnergyDissipationRateType
SubFilterAvailablePotentialEnergyDissipationRate(
    model,
    filter;
    method,
    geopotential_height
)

Return the subfilter-scale (SFS) available potential energy dissipation rate εₐˢ, the APE destruction by diffusion carried by the scales that a low-pass filter removes:

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

where εₐ = -qᵢ∂ᵢΥ is the dissipation rate of the full field (AvailablePotentialEnergyDissipationRate), qᵢ is the closure's own diffusive buoyancy flux, and εₐˡ is the same contraction evaluated on the filtered state (FilteredAvailablePotentialEnergyDissipationRate): the filtered flux q̄ᵢ against the displacement potential Υˡ of the filtered buoyancy b̄ = filter(b). Filtering the flux rather than recomputing it from is what makes εₐˡ the sink of the filtered-state budget when κ varies in space; that docstring has the details. Both states are measured against one shared reference profile, exactly as in SubFilterAvailablePotentialEnergy, whose budget this is the diffusive sink of; it mirrors what SubFilterKineticEnergyDissipationRate is to the subfilter kinetic energy.

method has to be a ProfileLookup, for the reason SubFilterAvailablePotentialEnergy gives, and the lookup also makes each z✶ a function of buoyancy alone — the property that differentiating Υ and Υˡ 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.

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 the filtered full-field dissipation 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)SubFilterAvailablePotentialEnergyDissipationRate(model, filter)# outputSubFilterAvailablePotentialEnergyDissipationRate KernelFunctionOperation at (Center, Center, Center)├── grid: 4×4×4 RectilinearGrid{Float64, Periodic, Periodic, Bounded} on CPU with 3×3×3 halo├── kernel_function: subfilter_ape_dissipation_rate_ccc (generic function with 1 method)└── arguments: ("Oceananigans.AbstractOperations.BinaryOperation",)└── computes: subfilter available potential energy dissipation rate  filter(εₐ) - εₐˡ

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

source

The subfilter conversion to kinetic energy

\[\tau^l(w, b_r) = \overline{w b_r} - \bar w \, b_r^l , \qquad b_r = b - b^\star(z) , \qquad b_r^l = \bar b - b^\star(z)\]

is the rate at which the subfilter scales release their available potential energy to the subfilter flow, computed by SubFilterAvailablePotentialToKineticEnergyConversion. It is the subfilter half of the split whose filtered half is FilteredAvailablePotentialToKineticEnergyConversion, the two summing to $\overline{w b_r}$. It enters this budget as $-\tau^l(w, b_r)$ and the Subfilter kinetic energy equation as $+\tau^l(w, b_r)$, so it is a reversible exchange rather than a source or a sink. The reference profile is not filtered in either half, which is what distinguishes it from a plain subfilter_covariance of $w$ and $b_r$.

Oceanostics.SubFilterAvailablePotentialEnergyEquation.SubFilterAvailablePotentialToKineticEnergyConversionType
SubFilterAvailablePotentialToKineticEnergyConversion(
    model,
    filter;
    method,
    geopotential_height
)

Return the subfilter-scale (SFS) conversion of available potential energy into kinetic energy τˡ(w, bᵣ), the rate at which the scales a low-pass filter removes release their APE to the subfilter flow:

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

It is the subfilter half of the split whose filtered half is FilteredAvailablePotentialToKineticEnergyConversion w̄b_rˡ: the two sum to filter(w bᵣ), so the subfilter and filtered budgets between them exchange exactly what the full field converts. It enters this budget as -τˡ(w, bᵣ) and the subfilter kinetic energy budget (SubFilterKineticEnergy) as +τˡ(w, bᵣ), so it is a reversible exchange rather than a source or a sink.

The reference profile is not filtered in either half — b_rˡ is b̄ - b✶(z), not filter(bᵣ), which would filter the reference along with the buoyancy. That is what makes the two halves an exact decomposition; FilteredAvailablePotentialToKineticEnergyConversion gives the reason. The two choices differ once the filter acts in the vertical and coincide for a purely horizontal one, b✶ being a function of z alone.

method has to be a ProfileLookup, for the reason SubFilterAvailablePotentialEnergy gives, and both halves are built on the one profile it supplies. filter is any callable mapping a field to its low-pass-filtered counterpart, e.g. a reusable GaussianFilter or BoxFilter. The result 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)SubFilterAvailablePotentialToKineticEnergyConversion(model, filter)# outputSubFilterAvailablePotentialToKineticEnergyConversion KernelFunctionOperation at (Center, Center, Center)├── grid: 4×4×4 RectilinearGrid{Float64, Periodic, Periodic, Bounded} on CPU with 3×3×3 halo├── kernel_function: subfilter_ape_to_ke_conversion_ccc (generic function with 1 method)└── arguments: ("Oceananigans.AbstractOperations.BinaryOperation",)└── computes: subfilter APE to KE conversion  filter(wbᵣ) - w̄b_rˡ

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

source

The one term of the $e_a^s$ budget still without a diagnostic is the reference-tendency correction that appears when the reference profile evolves in time; with a fixed reference profile it vanishes identically.