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)
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 10 methods)
└── arguments: ("Centered", "NamedTuple", "Field")
julia> diff = TracerEquation.Diffusion(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: ∇_dot_qᶜ (generic function with 10 methods)
└── arguments: ("ScalarDiffusivity", "Nothing", "Val", "Field", "Clock", "NamedTuple", "Nothing")
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)
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 10 methods)
└── arguments: ("Centered", "NamedTuple", "Field")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)
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")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)
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")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)
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")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")