Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add background potential energy computation #172

Open
wants to merge 41 commits into
base: main
Choose a base branch
from
Open
Changes from 24 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
5efa6a6
Try some sorting idea for background state
jbisits Mar 28, 2024
7113697
First attempt at bpe implementation
jbisits Apr 1, 2024
1590116
Change docstring example
jbisits Apr 2, 2024
8ace5a8
Add space
jbisits Apr 2, 2024
5d2eee1
`sort` reverse so that matches `z`
jbisits Apr 3, 2024
84265a8
`sort(rev=true)` for density only (not buoyancy)
jbisits Apr 3, 2024
44d81c5
Removed wrong thing
jbisits Apr 3, 2024
9362a27
Correction in docstring
jbisits Apr 3, 2024
fbe8c02
Cleanup function definitions
jbisits Apr 22, 2024
e59fbad
Remove bad Type design
jbisits Apr 22, 2024
1ac15f3
Using more kfo's
jbisits Apr 22, 2024
6d3d7da
Update PotentialEnergyEquationTerms.jl
jbisits Apr 22, 2024
62b0a47
Update BPE docstring
jbisits Apr 22, 2024
1c73077
Update src/PotentialEnergyEquationTerms.jl
jbisits Apr 23, 2024
2c4faad
Try `OneDReferenceField`
jbisits May 8, 2024
0a87fbb
Export the function
jbisits May 8, 2024
73479d5
Correct BPE function, still need to check sorting
jbisits May 8, 2024
dfbbcd4
Merge branch 'main' into jib-background-potential-energy
jbisits May 9, 2024
f28a26a
Suppress some docstring output
jbisits May 9, 2024
aad59a9
Docstring updates
jbisits May 10, 2024
6251dc8
Validate model grid + docstring updates
jbisits May 10, 2024
2e98f34
Update src/PotentialEnergyEquationTerms.jl
jbisits May 10, 2024
38b9a83
Update PotentialEnergyEquationTerms.jl
jbisits May 10, 2024
24b658c
Merge branch 'jib-background-potential-energy' of https://github.com/…
jbisits May 10, 2024
3107ec6
Update src/PotentialEnergyEquationTerms.jl
jbisits May 11, 2024
23d095d
Update src/PotentialEnergyEquationTerms.jl
jbisits May 11, 2024
769ecc0
Wrong function calls
jbisits May 11, 2024
44c0206
Reduce number of methods + update packages
jbisits May 14, 2024
ac0ed37
Default to `geopotential_height = 0`
jbisits May 16, 2024
8689673
Simplify testing
jbisits May 16, 2024
8219b78
Correct misaligned brackets
jbisits May 16, 2024
71b5537
Add tests for `OneDReferenceField` + bpe
jbisits May 17, 2024
82c2421
Test z*
jbisits May 17, 2024
d4d396e
Docstring updates
jbisits May 20, 2024
63a58b2
Align `z` and `z✶`
jbisits May 22, 2024
3f752bf
Missed the ✶
jbisits May 22, 2024
1fd9d64
Correct `BuoyancyTracer()` and linear eos sorting
jbisits May 22, 2024
7514adf
Update `Type` input for `OneDReferenceField`
jbisits May 23, 2024
7cbc052
Correct function call
jbisits May 23, 2024
2ec9e41
Merge branch 'main' into jib-background-potential-energy
jbisits May 23, 2024
08832ae
Merge branch 'main' into jib-background-potential-energy
tomchor Nov 8, 2024
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
194 changes: 151 additions & 43 deletions src/PotentialEnergyEquationTerms.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@ module PotentialEnergyEquationTerms

using DocStringExtensions

export PotentialEnergy
export PotentialEnergy, BackgroundPotentialEnergy, OneDReferenceField

using Oceananigans.AbstractOperations: KernelFunctionOperation
using Oceananigans.AbstractOperations: KernelFunctionOperation, volume, Az, GridMetricOperation
using Oceananigans.Models: seawater_density
using Oceananigans.Models: model_geopotential_height
using Oceananigans.Grids: Center, Face
using Oceananigans.Grids: NegativeZDirection
using Oceananigans.ImmersedBoundaries: ImmersedBoundaryGrid
using Oceananigans.Grids
using Oceananigans.Grids: Center, NegativeZDirection, interior, CenterField
using Oceananigans.BuoyancyModels: Buoyancy, BuoyancyTracer, SeawaterBuoyancy, LinearEquationOfState
using Oceananigans.BuoyancyModels: buoyancy_perturbationᶜᶜᶜ, Zᶜᶜᶜ
using Oceananigans.Models: ShallowWaterModel
using Oceananigans.Fields: Field, compute!, field, set!
using Oceanostics: validate_location
using SeawaterPolynomials: BoussinesqEquationOfState

Expand All @@ -22,7 +24,10 @@ const BuoyancyBoussinesqEOSModel = Buoyancy{<:SeawaterBuoyancy{FT, <:BoussinesqE

validate_gravity_unit_vector(gravity_unit_vector::NegativeZDirection) = nothing
validate_gravity_unit_vector(gravity_unit_vector) =
throw(ArgumentError("`PotentialEnergy` is curently only defined for models that have a `NegativeZDirection` gravity unit vector."))
throw(ArgumentError("`PotentialEnergy` and `BackgroundPotentialEnergy` are curently only defined for models that have a `NegativeZDirection` gravity unit vector."))
validate_grid(grid) = nothing
validate_grid(grid::ImmersedBoundaryGrid) =
throw(ArgumentError("`PotentialEnergy` and `BackgroundPotentialEnergy` are not currently defined for $(grid)."))

"""
$(SIGNATURES)
Expand All @@ -49,21 +54,9 @@ julia> using Oceananigans

julia> using Oceanostics.PotentialEnergyEquationTerms: PotentialEnergy

julia> grid = RectilinearGrid(size=100, z=(-1000, 0), topology=(Flat, Flat, Bounded))
1×1×100 RectilinearGrid{Float64, Flat, Flat, Bounded} on CPU with 0×0×3 halo
├── Flat x
├── Flat y
└── Bounded z ∈ [-1000.0, 0.0] regularly spaced with Δz=10.0
julia> grid = RectilinearGrid(size=100, z=(-1000, 0), topology=(Flat, Flat, Bounded));

julia> model = NonhydrostaticModel(; grid, buoyancy=BuoyancyTracer(), tracers=(:b,))
NonhydrostaticModel{CPU, RectilinearGrid}(time = 0 seconds, iteration = 0)
├── grid: 1×1×100 RectilinearGrid{Float64, Flat, Flat, Bounded} on CPU with 0×0×3 halo
├── timestepper: QuasiAdamsBashforth2TimeStepper
├── advection scheme: Centered reconstruction order 2
├── tracers: b
├── closure: Nothing
├── buoyancy: BuoyancyTracer with ĝ = NegativeZDirection()
└── coriolis: Nothing
julia> model = NonhydrostaticModel(; grid, buoyancy=BuoyancyTracer(), tracers=:b);

julia> PotentialEnergy(model)
KernelFunctionOperation at (Center, Center, Center)
Expand All @@ -79,34 +72,15 @@ julia> using Oceananigans, SeawaterPolynomials.TEOS10

julia> using Oceanostics.PotentialEnergyEquationTerms: PotentialEnergy

julia> grid = RectilinearGrid(size=100, z=(-1000, 0), topology=(Flat, Flat, Bounded))
1×1×100 RectilinearGrid{Float64, Flat, Flat, Bounded} on CPU with 0×0×3 halo
├── Flat x
├── Flat y
└── Bounded z ∈ [-1000.0, 0.0] regularly spaced with Δz=10.0
julia> grid = RectilinearGrid(size=100, z=(-1000, 0), topology=(Flat, Flat, Bounded));

julia> tracers = (:T, :S)
(:T, :S)
julia> tracers = (:T, :S);

julia> eos = TEOS10EquationOfState()
BoussinesqEquationOfState{Float64}:
├── seawater_polynomial: TEOS10SeawaterPolynomial{Float64}
└── reference_density: 1020.0
julia> eos = TEOS10EquationOfState();

julia> buoyancy = SeawaterBuoyancy(equation_of_state=eos)
SeawaterBuoyancy{Float64}:
├── gravitational_acceleration: 9.80665
└── equation_of_state: BoussinesqEquationOfState{Float64}
julia> buoyancy = SeawaterBuoyancy(equation_of_state=eos);

julia> model = NonhydrostaticModel(; grid, buoyancy, tracers)
NonhydrostaticModel{CPU, RectilinearGrid}(time = 0 seconds, iteration = 0)
├── grid: 1×1×100 RectilinearGrid{Float64, Flat, Flat, Bounded} on CPU with 0×0×3 halo
├── timestepper: QuasiAdamsBashforth2TimeStepper
├── advection scheme: Centered reconstruction order 2
├── tracers: (T, S)
├── closure: Nothing
├── buoyancy: SeawaterBuoyancy with g=9.80665 and BoussinesqEquationOfState{Float64} with ĝ = NegativeZDirection()
└── coriolis: Nothing
julia> model = NonhydrostaticModel(; grid, buoyancy, tracers);

julia> PotentialEnergy(model)
KernelFunctionOperation at (Center, Center, Center)
Expand Down Expand Up @@ -146,6 +120,7 @@ KernelFunctionOperation at (Center, Center, Center)

validate_location(location, "PotentialEnergy")
isnothing(model.buoyancy) ? nothing : validate_gravity_unit_vector(model.buoyancy.gravity_unit_vector)
validate_grid(model.grid)

return PotentialEnergy(model, model.buoyancy, geopotential_height)
end
Expand Down Expand Up @@ -186,4 +161,137 @@ end

@inline g′z_ccc(i, j, k, grid, ρ, p) = (p.g / p.ρ₀) * ρ[i, j, k] * Zᶜᶜᶜ(i, j, k, grid)

## Grid metrics from https://github.com/tomchor/Oceanostics.jl/issues/163#issuecomment-2012623824

function MetricField(loc, grid, metric)

metric_operation = GridMetricOperation(loc, metric, grid)
metric_field = Field(metric_operation)

return compute!(metric_field)
end
jbisits marked this conversation as resolved.
Show resolved Hide resolved

VolumeField(grid, loc=(Center, Center, Center)) = MetricField(loc, grid, volume)
AreaField(grid, loc=(Center, Center, Nothing)) = MetricField(loc, grid, Az)

"""
function OneDReferenceField(f::Field)
Return a `OneDReferenceField` of the data in `f` and the corresponding `z✶` coordinate.
The gridded data is first reshaped into a 1D `Array` then sorted. Returned is a new `Field`
of this sorted data as well as the `z✶` `Field` that are both on a grid that has the same vertical extent
as the original field (`f.grid.Lz`) but with the resolution of the whole domain (`Nx * Ny * Nz`).
jbisits marked this conversation as resolved.
Show resolved Hide resolved
The `z✶` `Field` is defined as
```math
\\frac{1}{A}\\int_{f\\mathrm{min}}^{f\\mathrm{max}} \\mathrm{d}V.
```
and is computed by cumulatively summing the 1D `Array` of grid volumes `ΔV`.
**Note:** `OneDReferenceField` is currently only appropriate for grids that have uniform
horizontal area.
"""
function OneDReferenceField(f::Field; rev = false)

area = sum(AreaField(f.grid))
volume_field = VolumeField(f.grid)
v = reshape(interior(volume_field), :)
field_data = reshape(interior(f), :)

p = sortperm(field_data; rev)
sorted_field_data = field_data[p]
z✶ = cumsum(v[p]) / area

grid_arch = f.grid.architecture
grid_size = prod(size(f.grid))
new_grid = RectilinearGrid(grid_arch, size = grid_size, z = (-f.grid.Lz, 0), topology=(Flat, Flat, Bounded))

sorted_field = CenterField(new_grid)
set!(sorted_field, reshape(sorted_field_data, size(new_grid)))
z✶_field = CenterField(new_grid)
set!(z✶_field, reshape(z✶, size(new_grid)))

return sorted_field, z✶_field

end

"""
$(SIGNATURES)

Return a `kernelFunctionOperation` to compute an approximation to the `BackgroundPotentialEnergy`
jbisits marked this conversation as resolved.
Show resolved Hide resolved
per unit volume,
```math
E_{b} = \\frac{gρz✶}{ρ₀}.
```
The `BackgroundPotentialEnergy` is the potential energy computed by adiabatically resorting
the buoyancy or density field into a reference state of minimal potential energy.
The reference state, and corresponding `z✶` cooridinate, is approximated using [OneDReferenceField](@ref).
For more informaion about the background potential energy state and its implementation see
[Winters et al. (1995)](https://www.cambridge.org/core/journals/journal-of-fluid-mechanics/article/available-potential-energy-and-mixing-in-densitystratified-fluids/A45F1A40521FF0A0DC82BC705AD398DA)
and [Carpenter et al. (2012)](https://www.cambridge.org/core/journals/journal-of-fluid-mechanics/article/simulations-of-a-doublediffusive-interface-in-the-diffusive-convection-regime/63D2ECE2AA41439E01A01F9A0D76F2E2).

Examples
========

```jldoctest
julia> using Oceananigans

julia> using Oceanostics.PotentialEnergyEquationTerms: BackgroundPotentialEnergy

julia> grid = RectilinearGrid(size=100, z=(-1000, 0), topology=(Flat, Flat, Bounded));

julia> model = NonhydrostaticModel(; grid, buoyancy=BuoyancyTracer(), tracers=(:b,));

julia> bpe = BackgroundPotentialEnergy(model)
KernelFunctionOperation at (Center, Center, Center)
├── grid: 1×1×100 RectilinearGrid{Float64, Flat, Flat, Bounded} on CPU with 0×0×3 halo
├── kernel_function: bz✶_ccc (generic function with 1 method)
└── arguments: ("1×1×100 Field{Center, Center, Center} on RectilinearGrid on CPU", "1×1×100 Field{Center, Center, Center} on RectilinearGrid on CPU")
```
"""
@inline function BackgroundPotentialEnergy(model; location = (Center, Center, Center),
geopotential_height = model_geopotential_height(model))

validate_location(location, "BackgroundPotentialEnergy")
isnothing(model.buoyancy) ? nothing : validate_gravity_unit_vector(model.buoyancy.gravity_unit_vector)
validate_grid(model.grid)

return BackgroundPotentialEnergy(model, model.buoyancy, geopotential_height)
end

@inline function BackgroundPotentialEnergy(model, buoyancy_model::BuoyancyTracerModel, geopotential_height)

grid = model.grid
sorted_buoyancy, z✶ = OneDReferenceField(model.tracers.b)

return KernelFunctionOperation{Center, Center, Center}(bz✶_ccc, grid, sorted_buoyancy, z✶)
end

@inline bz✶_ccc(i, j, k, grid, b, z✶) = b[i, j, k] * z✶[i, j, k]

linear_eos_buoyancy(grid, buoyancy, tracers) = KernelFunctionOperation{Center, Center, Center}(buoyancy_perturbationᶜᶜᶜ, grid, buoyancy, tracers)

@inline function BackgroundPotentialEnergy(model, buoyancy_model::BuoyancyLinearEOSModel, geopotential_height)

grid = model.grid
buoyancy = model.buoyancy.model
tracers = model.tracers
b = linear_eos_buoyancy(grid, buoyancy, tracers)
compute!(b)
sorted_buoyancy, z✶ = OneDReferenceField(b)

return KernelFunctionOperation{Center, Center, Center}(bz✶_ccc, grid, sorted_buoyancy, z✶)
end

@inline function BackgroundPotentialEnergy(model, buoyancy_model::BuoyancyBoussinesqEOSModel, geopotential_height)

grid = model.grid
ρ = seawater_density(model; geopotential_height)
compute!(ρ)
sorted_density, z✶ = OneDReferenceField(ρ)
parameters = (g = model.buoyancy.model.gravitational_acceleration,
ρ₀ = model.buoyancy.model.equation_of_state.reference_density)

return KernelFunctionOperation{Center, Center, Center}(g′z_ccc, grid, sorted_density, z✶, parameters)
end
Copy link
Owner

Choose a reason for hiding this comment

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

I know we usually prefer multiple dispatch but there's a lot of overlap between these four functions. Should we put everything into one or two functions and just use plain old if/else statements?

Copy link
Collaborator

@glwagner glwagner May 10, 2024

Choose a reason for hiding this comment

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

"multiple dispatch" is not the reason why you can't achieve code re-use here. if statements are fine too though, I just think you want to think clearly about the changes that are needed. Re-using code is about observing patterns and creating abstractions that generalize concepts. You can achieve that in code that uses if statements or in code that uses multiple dispatch.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah sure! I did it like this because I thought it would make it easy to see how the computations are done for the different Buoyancy models. I will try and simplify.


@inline g′z✶_ccc(i, j, k, grid, ρ, z✶, p) = (p.g / p.ρ₀) * ρ[i, j, k] * z✶[i, j, k]

end # module
Loading