Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 59 additions & 26 deletions src/FlowDiagnostics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,21 @@ using DocStringExtensions
export RichardsonNumber, RossbyNumber
export ErtelPotentialVorticity, ThermalWindPotentialVorticity, DirectionalErtelPotentialVorticity
export StrainRateTensorModulus, VorticityTensorModulus, Q, QVelocityGradientTensorInvariant
export MixedLayerDepth, DensityAnomalyCriterion
export MixedLayerDepth, BuoyancyAnomalyCriterion, DensityAnomalyCriterion

using Oceanostics: validate_location,
validate_dissipative_closure,
add_background_fields,
get_coriolis_frequency_components

using Oceananigans: NonhydrostaticModel, FPlane, ConstantCartesianCoriolis, BuoyancyField, BuoyancyTracer
using Oceananigans.BuoyancyFormulations: get_temperature_and_salinity, SeawaterBuoyancy
using Oceananigans.BuoyancyFormulations: get_temperature_and_salinity, SeawaterBuoyancy, g_Earth, buoyancy_perturbationᶜᶜᶜ
using Oceananigans.Operators
using Oceananigans.AbstractOperations
using Oceananigans.AbstractOperations: KernelFunctionOperation
using Oceananigans.BuoyancyFormulations: buoyancy
using Oceananigans.Grids: Center, Face, NegativeZDirection, ZDirection, znode


using SeawaterPolynomials: ρ′, BoussinesqEquationOfState
using SeawaterPolynomials.SecondOrderSeawaterPolynomials: SecondOrderSeawaterPolynomial

Expand Down Expand Up @@ -447,6 +446,7 @@ function QVelocityGradientTensorInvariant(model; location = (Center, Center, Cen
end

const Q = QVelocityGradientTensorInvariant

struct MixedLayerDepth{C}
criterion::C
end
Expand All @@ -460,11 +460,11 @@ Defaults to `DensityAnomalyCriterion` where the depth is that at which the densi
is some threshold (defaults to 0.125kg/m³) higher than the surface density.

When `DensityAnomalyCriterion` is used, the arguments `buoyancy` and `C` should be
supploed where `buoyancy` should be the buoyancy model, and `C` should be a named
supplied where `buoyancy` should be the buoyancy model, and `C` should be a named
tuple of `(; T, S)`, `(; T)` or `(; S)` (the latter two if the buoyancy model
specifies a constant salinity or temperature).
"""
function MixedLayerDepth(grid, args...; criterion = DensityAnomalyCriterion(convert(eltype(grid), 1/8)))
function MixedLayerDepth(grid, args...; criterion = BuoyancyAnomalyCriterion(convert(eltype(grid), -1/8 * 1020 / g_Earth)))
validate_criterion_model(criterion, args...)

MLD = MixedLayerDepth(criterion)
Expand All @@ -486,56 +486,89 @@ function (MLD::MixedLayerDepth)(i, j, k, grid, args...)
return ifelse(kₘₗ == -1, -Inf, zₘₗ)
end

"""
$(SIGNATURES)

An abstract mixed layer depth criterion where the mixed layer is defined to be
`anomaly` + `pertubation` greater than the surface value of `anomaly`.

$(SIGNATURES) types should provide a method for the function `anomaly` in the form
`anomaly(criterion, i, j, k, grid, args...)`, and should have a property `pertubation`.
"""
abstract type AbstractAnomalyCriterion end

@inline function (criterion::AbstractAnomalyCriterion)(i, j, k, grid, args...)
δ = criterion.pertubation

ref = (anomaly(criterion, i, j, grid.Nz, grid, args...) + anomaly(criterion, i, j, grid.Nz+1, grid, args...)) * convert(eltype(grid), 0.5)

val = anomaly(criterion, i, j, k, grid,args...)

return val < ref + δ
end

@inline function interpolate_from_nearest_cell(criterion::AbstractAnomalyCriterion, i, j, k, grid, args...)
δ = criterion.pertubation

ref = (anomaly(criterion, i, j, grid.Nz, grid, args...) + anomaly(criterion, i, j, grid.Nz + 1, grid, args...)) * convert(eltype(grid), 0.5)

k_val = anomaly(criterion, i, j, k, grid, args...)
k⁺_val = anomaly(criterion, i, j, k + 1, grid, args...)

zₖ = znode(i, j, k, grid, Center(), Center(), Center())
z₊ = znode(i, j, k+1, grid, Center(), Center(), Center())

return zₖ + (z₊ - zₖ) * (ref + δ - k_val) / (k⁺_val - k_val)
end

"""
$(SIGNATURES)

Defines the mixed layer to be the depth at which the density is more than `pertubation`
greater than the surface density.

When this model is used, the arguments `buoyancy` and `C` should be
supploed where `buoyancy` should be the buoyancy model, and `C` should be a named
supplied where `buoyancy` should be the buoyancy model, and `C` should be a named
tuple of `(; T, S)`, `(; T)` or `(; S)` (the latter two if the buoyancy model
specifies a constant salinity or temperature).
"""
struct DensityAnomalyCriterion{FT}
struct DensityAnomalyCriterion{FT} <: AbstractAnomalyCriterion
pertubation :: FT
end
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The density anomaly can be expressed in terms of a buoyancy anomaly, where you require additional information about 1) the gravitational acceleration used in the model and 2) the reference density. For example:

struct DensityAnomalyCriterion
    buoyancy_anomaly
    reference_density
    gravitational_acceleration
end

You can then build a constructor that uses some default values for the parameters.

This will help users understand how the parameters they are using affect their results leading to higher-quality science.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this more


validate_criterion_model(::DensityAnomalyCriterion, args...) =
@error "For DensityAnomalyCriterion you must supply the arguments buoyancy and C, where C is a named tuple of (; T, S), (; T) or (; S)"

validate_criterion_model(::DensityAnomalyCriterion, buoyancy, C) =
@error "DensityAnomalyCriterion is not implemented for your buoyancy, model as the density anomaly is not defined"
validate_criterion_model(::DensityAnomalyCriterion, buoyancy, C) = nothing

validate_criterion_model(::DensityAnomalyCriterion, ::SeawaterBuoyancy{<:Any, <:BoussinesqEquationOfState}, C::NamedTuple) = nothing

@inline function density(i, j, k, grid, b::SeawaterBuoyancy{<:Any, <:BoussinesqEquationOfState}, C)
@inline function anomaly(::DensityAnomalyCriterion, i, j, k, grid, b::SeawaterBuoyancy{<:Any, <:BoussinesqEquationOfState}, C)
T, S = get_temperature_and_salinity(b, C)

return ρ′(i, j, k, grid, b.equation_of_state, T, S)
end

@inline function (density_criterion::DensityAnomalyCriterion)(i, j, k, grid, buoyancy, C)
δρ = density_criterion.pertubation

ρᵣ = (density(i, j, grid.Nz, grid, buoyancy, C) + density(i, j, grid.Nz+1, grid, buoyancy, C)) * convert(eltype(grid), 0.5)
"""
$(SIGNATURES)

ρₖ = density(i, j, k, grid, buoyancy, C)
Defines the mixed layer to be the depth at which the buoyancy is more than `pertubation`
greater than the surface buoyancy (but the pertubaton is usually negative).

return ρₖ > ρᵣ + δρ
When this model is used, the arguments `buoyancy` and `C` should be
supplied where `buoyancy` should be the buoyancy model, and `C` should be a named
tuple of `(; T, S)`, `(; T)` or `(; S)` (the latter two if the buoyancy model
specifies a constant salinity or temperature).
"""
@kwdef struct BuoyancyAnomalyCriterion{FT} <: AbstractAnomalyCriterion
pertubation :: FT = - 1/8 * 1020 / g_Earth
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pertubation :: FT = - 1/8 * 1020 / g_Earth
anomaly :: FT = - 1/8 * 1020 / g_Earth

end

@inline function interpolate_from_nearest_cell(density_criterion::DensityAnomalyCriterion, i, j, k, grid, buoyancy, C)
δρ = density_criterion.pertubation

ρᵣ = (density(i, j, grid.Nz, grid, buoyancy, C) + density(i, j, grid.Nz+1, grid, buoyancy, C)) * convert(eltype(grid), 0.5)
validate_criterion_model(::BuoyancyAnomalyCriterion, args...) =
@error "For DensityAnomalyCriterion you must supply the arguments buoyancy and C, where C is a named tuple of (; T, S), (; T) or (; S)"

ρₖ = density(i, j, k, grid, buoyancy, C)
ρ₊ = density(i, j, k+1, grid, buoyancy, C)
validate_criterion_model(::BuoyancyAnomalyCriterion, buoyancy, C) = nothing

zₖ = znode(i, j, k, grid, Center(), Center(), Center())
z₊ = znode(i, j, k+1, grid, Center(), Center(), Center())
@inline anomaly(::BuoyancyAnomalyCriterion, i, j, k, grid, buoyancy, C) = buoyancy_perturbationᶜᶜᶜ(i, j, k, grid, buoyancy, C)

return zₖ + (z₊ - zₖ) * (ρᵣ + δρ - ρₖ) / (ρ₊ - ρₖ)
end
end # module
2 changes: 1 addition & 1 deletion src/Oceanostics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export RichardsonNumber, RossbyNumber
export ErtelPotentialVorticity, ThermalWindPotentialVorticity
export DirectionalErtelPotentialVorticity
export StrainRateTensorModulus, VorticityTensorModulus, Q, QVelocityGradientTensorInvariant
export MixedLayerDepth, DensityAnomalyCriterion
export MixedLayerDepth, BuoyancyAnomalyCriterion, DensityAnomalyCriterion
#---

#+++ PotentialEnergyEquationTerms exports
Expand Down
44 changes: 33 additions & 11 deletions test/test_tracer_diagnostics.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
using Test
using CUDA: has_cuda_gpu, @allowscalar
#using CUDA: has_cuda_gpu, @allowscalar

using Oceananigans
using Oceananigans: fill_halo_regions!
Expand All @@ -16,7 +16,7 @@ using Oceanostics: get_coriolis_frequency_components
LinearBuoyancyForce = Union{BuoyancyTracer, SeawaterBuoyancy{<:Any, <:LinearEquationOfState}}

#+++ Default grids
arch = has_cuda_gpu() ? GPU() : CPU()
arch = CPU()#has_cuda_gpu() ? GPU() : CPU()

N = 6
regular_grid = RectilinearGrid(arch, size=(N, N, N), extent=(1, 1, 1))
Expand Down Expand Up @@ -174,28 +174,49 @@ function test_tracer_diagnostics(model)
return nothing
end

function test_mixed_layer_depth(grid;
buoyancy = SeawaterBuoyancy(equation_of_state=BoussinesqEquationOfState(LinearRoquetSeawaterPolynomial(), 1000),
constant_salinity=0),
zₘₓₗ = 0.5,
δρ = 0.125,
∂z_T = - δρ / zₘₓₗ / buoyancy.equation_of_state.seawater_polynomial.R₀₁₀)
function test_mixed_layer_depth(grid; zₘₓₗ = 0.5)
# buoyancy criterion (default)
buoyancy = BuoyancyTracer()
δb = -1
∂z_b = - δb / zₘₓₗ

boundary_conditions = FieldBoundaryConditions(grid, (Center, Center, Center); top = GradientBoundaryCondition(∂z_b))

b = CenterField(grid; boundary_conditions)
mld = MixedLayerDepth(grid, buoyancy, (; b); criterion = BuoyancyAnomalyCriterion(δb))

@test isinf(mld[1, 1])

set!(b, (x, y, z) -> z * ∂z_b)
fill_halo_regions!(b)

@test mld[1, 1] ≈ -zₘₓₗ

# density criterion
criterion = DensityAnomalyCriterion(convert(eltype(grid), 1/8))

buoyancy = SeawaterBuoyancy(equation_of_state=BoussinesqEquationOfState(LinearRoquetSeawaterPolynomial(), 1000),
constant_salinity=0)

δρ = 0.125
∂z_T = - δρ / zₘₓₗ / buoyancy.equation_of_state.seawater_polynomial.R₀₁₀

boundary_conditions = FieldBoundaryConditions(grid, (Center, Center, Center); top = GradientBoundaryCondition(∂z_T))

T = CenterField(grid; boundary_conditions)
mld = MixedLayerDepth(grid, buoyancy, (; T))
mld = MixedLayerDepth(grid, buoyancy, (; T); criterion)

@test isinf(mld[1, 1])

set!(T, (x, y, z) -> z * ∂z_T+10)
set!(T, (x, y, z) -> z * ∂z_T + 10)
fill_halo_regions!(T)

@test mld[1, 1] ≈ -zₘₓₗ

return nothing
end
#---

#=
@testset "Tracer diagnostics tests" begin
@info " Testing tracer diagnostics"
for (grid_class, grid) in zip(keys(grids), values(grids))
Expand Down Expand Up @@ -229,3 +250,4 @@ end
end
end
end
=#