Tracer equation
The TracerEquation module provides diagnostics corresponding to individual terms in the tracer conservation equation. In Oceananigans, the prognostic equation for a tracer $c$ (e.g. temperature, salinity, or a passive scalar) is
\[\partial_t c = -\partial_j (u_j c) + \partial_j q^c_j + F^c\]
where $u_j$ is the resolved velocity field, $q^c_j$ is the subgrid diffusive flux of $c$ in the $j$-th direction (parameterized by the turbulence closure), and $F^c$ represents any external forcing applied to the tracer.
This module decomposes the right-hand side into its constituent terms so that each can be computed, output, and analyzed independently. This is useful for constructing tracer budgets, diagnosing the relative importance of advection versus diffusion, or verifying that the budget closes numerically (i.e., that the sum of all terms equals the tendency).
Each diagnostic is built on Oceananigans' KernelFunctionOperation and is computed at (Center, Center, Center). Constructors accept either a full Oceananigans model object (with a tracer name) or individual fields for maximum flexibility.
Example
julia> using Oceananigans, Oceanostics
julia> grid = RectilinearGrid(size=(4, 4, 4), extent=(1, 1, 1));
julia> model = NonhydrostaticModel(grid; tracers=:b, closure=ScalarDiffusivity(ν=1e-4, κ=1e-4));
julia> adv = TracerEquation.Advection(model, :b)
TracerAdvection KernelFunctionOperation at (Center, Center, Center)
├── grid: 4×4×4 RectilinearGrid{Float64, Periodic, Periodic, Bounded} on CPU with 3×3×3 halo
├── kernel_function: div_Uc (generic function with 12 methods)
└── arguments: ("Centered", "NamedTuple", "Field")
└── computes: tracer advection ∂ⱼ(uⱼc)
julia> diff = TracerEquation.Diffusion(model, :b)
TracerDiffusion KernelFunctionOperation at (Center, Center, Center)
├── grid: 4×4×4 RectilinearGrid{Float64, Periodic, Periodic, Bounded} on CPU with 3×3×3 halo
├── kernel_function: ∇_dot_qᶜ (generic function with 10 methods)
└── arguments: ("ScalarDiffusivity", "Nothing", "Val", "Field", "Clock", "NamedTuple", "Nothing")
└── computes: tracer diffusion (interior) ∂ⱼqᶜⱼ
julia> forc = TracerEquation.Forcing(model, :b)
KernelFunctionOperation at (Center, Center, Center)
├── grid: 4×4×4 RectilinearGrid{Float64, Periodic, Periodic, Bounded} on CPU with 3×3×3 halo
├── kernel_function: Returns (generic function with 1 method)
└── arguments: ("Clock", "NamedTuple")Advection
Oceanostics.TracerEquation.Advection — Type
Advection(model, u, v, w, c, advection; location)
Calculates the advection of the tracer c as
ADV = ∂ⱼ (uⱼ c)using Oceananigans' kernel div_Uc.
julia> using Oceananigans, Oceanostics
julia> grid = RectilinearGrid(size=(4, 4, 4), extent=(1, 1, 1));
julia> model = NonhydrostaticModel(grid; tracers=:a);
julia> ADV = TracerEquation.Advection(model, :a)
TracerAdvection KernelFunctionOperation at (Center, Center, Center)
├── grid: 4×4×4 RectilinearGrid{Float64, Periodic, Periodic, Bounded} on CPU with 3×3×3 halo
├── kernel_function: div_Uc (generic function with 12 methods)
└── arguments: ("Centered", "NamedTuple", "Field")
└── computes: tracer advection ∂ⱼ(uⱼc)Diffusion
Oceanostics.TracerEquation.Diffusion — Type
Diffusion(
model,
val_tracer_index,
c,
closure,
closure_fields,
clock,
model_fields,
buoyancy;
location
)
Calculates the diffusion term (excluding anything due to the bathymetry) as
DIFF = ∂ⱼ qᶜⱼ,where qᶜⱼ is the diffusion tensor for tracer c, using the Oceananigans' kernel ∇_dot_qᶜ.
julia> using Oceananigans, Oceanostics
julia> grid = RectilinearGrid(size=(4, 4, 4), extent=(1, 1, 1));
julia> model = NonhydrostaticModel(grid; tracers=:a);
julia> DIFF = TracerEquation.Diffusion(model, :a)
TracerDiffusion KernelFunctionOperation at (Center, Center, Center)
├── grid: 4×4×4 RectilinearGrid{Float64, Periodic, Periodic, Bounded} on CPU with 3×3×3 halo
├── kernel_function: ∇_dot_qᶜ (generic function with 10 methods)
└── arguments: ("Nothing", "Nothing", "Val", "Field", "Clock", "NamedTuple", "Nothing")
└── computes: tracer diffusion (interior) ∂ⱼqᶜⱼOceanostics.TracerEquation.ImmersedDiffusion — Type
ImmersedDiffusion(
model,
c,
c_immersed_bc,
closure,
closure_fields,
val_tracer_index,
clock,
model_fields;
location
)
Calculates the diffusion term due to the bathymetry term as
DIFF = ∂ⱼ 𝓆ᶜⱼ,where 𝓆ᶜⱼ is the bathymetry-led diffusion tensor for tracer c, using the Oceananigans' kernel immersed_∇_dot_qᶜ.
julia> using Oceananigans, Oceanostics
julia> grid = RectilinearGrid(size=(4, 4, 4), extent=(1, 1, 1));
julia> model = NonhydrostaticModel(grid; tracers=:a);
julia> DIFF = TracerEquation.ImmersedDiffusion(model, :a)
TracerImmersedDiffusion KernelFunctionOperation at (Center, Center, Center)
├── grid: 4×4×4 RectilinearGrid{Float64, Periodic, Periodic, Bounded} on CPU with 3×3×3 halo
├── kernel_function: immersed_∇_dot_qᶜ (generic function with 2 methods)
└── arguments: ("Field", "Nothing", "Nothing", "Nothing", "Val", "Clock", "NamedTuple")
└── computes: tracer diffusion through immersed boundaries ∂ⱼ𝓆ᶜⱼOceanostics.TracerEquation.TotalDiffusion — Type
TotalDiffusion(
model,
c,
c_immersed_bc,
closure,
closure_fields,
val_tracer_index,
clock,
model_fields,
buoyancy;
location
)
Calculates the total diffusion term as
DIFF = ∂ⱼ qᶜⱼ + ∂ⱼ 𝓆ᶜⱼ,c. The calculation is done using the Oceananigans' kernels ∇_dot_qᶜ and immersed_∇_dot_qᶜ. where qᶜⱼ is the interior diffusion tensor and 𝓆ᶜⱼ is the bathymetry-led diffusion tensor for tracer
julia> using Oceananigans, Oceanostics
julia> grid = RectilinearGrid(size=(4, 4, 4), extent=(1, 1, 1));
julia> model = NonhydrostaticModel(grid; tracers=:a);
julia> DIFF = TracerEquation.TotalDiffusion(model, :a)
TracerTotalDiffusion KernelFunctionOperation at (Center, Center, Center)
├── grid: 4×4×4 RectilinearGrid{Float64, Periodic, Periodic, Bounded} on CPU with 3×3×3 halo
├── kernel_function: total_∇_dot_qᶜ (generic function with 1 method)
└── arguments: ("Field", "Nothing", "Nothing", "Nothing", "Val", "Clock", "NamedTuple", "Nothing")
└── computes: total tracer diffusion (interior + immersed) ∂ⱼqᶜⱼ + ∂ⱼ𝓆ᶜⱼForcing
Oceanostics.TracerEquation.Forcing — Type
Forcing(model, forcing, clock, model_fields; location)
Calculate the forcing term Fᶜ on the equation for tracer c for model.
julia> using Oceananigans, Oceanostics
julia> grid = RectilinearGrid(size=(4, 4, 4), extent=(1, 1, 1));
julia> model = NonhydrostaticModel(grid; tracers=:a);
julia> FORC = TracerEquation.Forcing(model, :a)
KernelFunctionOperation at (Center, Center, Center)
├── grid: 4×4×4 RectilinearGrid{Float64, Periodic, Periodic, Bounded} on CPU with 3×3×3 halo
├── kernel_function: Returns (generic function with 1 method)
└── arguments: ("Clock", "NamedTuple")