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.AdvectionType
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")
source

Diffusion

Oceanostics.TracerEquation.DiffusionType
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")
source
Oceanostics.TracerEquation.ImmersedDiffusionType
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")
source
Oceanostics.TracerEquation.TotalDiffusionType
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")
source

Forcing

Oceanostics.TracerEquation.ForcingType
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")
source