Skip to content

fjp/spherical coriolis #4695

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

Draft
wants to merge 20 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
2d8308e
starting to add the complete Coriolis for the nonhydrostatic model on…
francispoulin Jul 29, 2025
5d95832
First attempt to include the complete Coriolis force
francispoulin Jul 29, 2025
01fdbc8
Starting to build SphericalCoriolis
francispoulin Jul 31, 2025
5b275e9
fix silly typo
francispoulin Aug 2, 2025
d4ad5bc
first draft of SphericalCoriolis
francispoulin Aug 2, 2025
90fa952
Merge branch 'main' into fjp/SphericalCoriolis
francispoulin Aug 5, 2025
fe1044d
Update src/Coriolis/spherical_coriolis.jl
francispoulin Aug 5, 2025
d4fc356
Trying to create a dispatch between hydrostatic and nonhydrostatic
francispoulin Aug 5, 2025
960cf68
fix the coriolis term in the z equation
francispoulin Aug 5, 2025
de89b49
removed unnecessary file
francispoulin Aug 6, 2025
43d400d
move definitions to the correct file
francispoulin Aug 6, 2025
8707be5
move where const are defined
francispoulin Aug 6, 2025
0117481
fix small error with i
francispoulin Aug 6, 2025
cb1fa35
trying to set up the structure as suggested
francispoulin Aug 14, 2025
a76f84b
Merge branch 'main' into fjp/SphericalCoriolis
francispoulin Aug 14, 2025
d2a7829
Update spherical_coriolis.jl
simone-silvestri Aug 18, 2025
c7e4b31
Update src/Coriolis/spherical_coriolis.jl
francispoulin Aug 20, 2025
e96172e
Update src/Coriolis/spherical_coriolis.jl
francispoulin Aug 20, 2025
1b65af7
Update src/Coriolis/spherical_coriolis.jl
francispoulin Aug 20, 2025
c7d7a3d
Merge branch 'main' into fjp/SphericalCoriolis
francispoulin Aug 20, 2025
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
11 changes: 9 additions & 2 deletions src/Coriolis/Coriolis.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module Coriolis

export
FPlane, ConstantCartesianCoriolis, BetaPlane, NonTraditionalBetaPlane,
HydrostaticSphericalCoriolis, ActiveCellEnstrophyConserving,
SphericalCoriolis, ActiveCellEnstrophyConserving,
x_f_cross_U, y_f_cross_U, z_f_cross_U

using Printf
Expand All @@ -25,14 +25,21 @@ Abstract supertype for parameters related to background rotation rates.
"""
abstract type AbstractRotation end

abstract type HydrostaticCoriolis end

abstract type NonhydrostaticCoriolis end
Copy link
Member

Choose a reason for hiding this comment

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

these need to be type aliases, not abstract types

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'm happy to change this but maybe best after we decide on how to swtich from one to the other.

Copy link
Member

Choose a reason for hiding this comment

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

The user will decide which one to use in their script.


const face = Face()
const center = Center()

const HydrostaticSphericalCoriolis{S, FT} = SphericalCoriolis{HydrostaticCoriolis, S, FT}
const NonhydrostaticSphericalCoriolis{S, FT} = SphericalCoriolis{NonhydrostaticCoriolis, S, FT}
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this should go in the spherical_coriolis.jl file

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 just moved those as well.


include("no_rotation.jl")
include("f_plane.jl")
include("constant_cartesian_coriolis.jl")
include("beta_plane.jl")
include("non_traditional_beta_plane.jl")
include("hydrostatic_spherical_coriolis.jl")
include("spherical_coriolis.jl")

end # module
71 changes: 71 additions & 0 deletions src/Coriolis/nonhydrostatic_spherical_coriolis.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using Oceananigans.Grids: LatitudeLongitudeGrid, OrthogonalSphericalShellGrid, peripheral_node, φnode
using Oceananigans.Operators: Δx_qᶜᶠᶜ, Δy_qᶠᶜᶜ, Δxᶠᶜᶜ, Δyᶜᶠᶜ, hack_sind, hack_cosd
using Oceananigans.Advection: EnergyConserving, EnstrophyConserving
Copy link
Collaborator

Choose a reason for hiding this comment

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

This file can be deleted right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, and it is now deleted. Thanks.

using Oceananigans.BoundaryConditions
using Oceananigans.Fields
using Oceananigans.AbstractOperations: KernelFunctionOperation
using Oceananigans.ImmersedBoundaries

"""
struct NonhydrostaticSphericalCoriolis{S, FT} <: AbstractRotation

A parameter object for constant rotation around a vertical axis on the sphere.

This is only designed to work for a LatitudeLongitudeGrid.

There is no attempt to conserve energy or enstrophy at this early stage.
"""
struct NonhydrostaticSphericalCoriolis{S, FT} <: AbstractRotation
rotation_rate :: FT
end

"""
NonhydrostaticSphericalCoriolis([FT=Float64;]
rotation_rate = Ω_Earth,

Return a parameter object for Coriolis forces on a sphere rotating at `rotation_rate`.

Keyword arguments
=================

- `rotation_rate`: Sphere's rotation rate; default: [`Ω_Earth`](@ref).
"""
function NonhydrostaticSphericalCoriolis(FT::DataType=Oceananigans.defaults.FloatType;
rotation_rate = Ω_Earth,

return NonhydrostaticSphericalCoriolis{FT}(rotation_rate)
end

Adapt.adapt_structure(to, coriolis::NonhydrostaticSphericalCoriolis) =
NonhydrostaticSphericalCoriolis(Adapt.adapt(to))

@inline φᶠᶠᵃ(i, j, k, grid::LatitudeLongitudeGrid) = φnode(j, grid, face)

@inline fᶠᶠᵃ(i, j, k, grid) = 2 * coriolis.rotation_rate * hack_sind(φᶠᶠᵃ(i, j, k, grid))
@inline f̃ᶠᶠᵃ(i, j, k, grid) = 2 * coriolis.rotation_rate * hack_cosd(φᶠᶠᵃ(i, j, k, grid))

@inline f_ℑx_vᶠᶠᵃ(i, j, k, grid, coriolis, v) = fᶠᶠᵃ(i, j, k, grid, coriolis) * ℑxᶠᵃᵃ(i, j, k, grid, Δx_qᶜᶠᶜ, v)
@inline f̃_ℑx_wᶠᶠᵃ(i, j, k, grid, coriolis, v) = f̃ᶠᶠᵃ(i, j, k, grid, coriolis) * ℑxᶠᵃᵃ(i, j, k, grid, Δx_qᶜᶠᶜ, w)

@inline f_ℑy_uᶠᶠᵃ(i, j, k, grid, coriolis, u) = fᶠᶠᵃ(i, j, k, grid, coriolis) * ℑyᵃᶠᵃ(i, j, k, grid, Δy_qᶠᶜᶜ, u)

@inline f̃_ℑz_uᶠᶠᵃ(i, j, k, grid, coriolis, u) = f̃ᶠᶠᵃ(i, j, k, grid, coriolis) * ℑzᵃᶠᵃ(i, j, k, grid, Δz_qᶜᶜᶠ, u)

@inline x_f_cross_U(i, j, k, grid, U) =
@inbounds - ℑyᵃᶜᵃ(i, j, k, grid, f_ℑx_vᶠᶠᵃ, U[2]) * Δx⁻¹ᶠᶜᶜ(i, j, k, grid)
+ ℑzᵃᶜᵃ(i, j, k, grid, f̃_ℑx_wᶠᶠᵃ, U[3]) * Δx⁻¹ᶠᶜᶜ(i, j, k, grid)

@inline y_f_cross_U(i, j, k, grid, U) =
@inbounds + ℑxᶜᵃᵃ(i, j, k, grid, f_ℑy_uᶠᶠᵃ, U[1]) * Δy⁻¹ᶜᶠᶜ(i, j, k, grid)

@inline z_f_cross_U(i, j, k, grid, U) =
@inbounds - ℑxᶜᵃᵃ(i, j, k, grid, f̃_ℑz_uᶠᶠᵃ, U[1]) * Δz⁻¹ᶜᶜᶠ(i, j, k, grid)

function Base.show(io::IO, nonhydrostatic_spherical_coriolis::NonhydrostaticSphericalCoriolis)
rotation_rate = hydrostatic_spherical_coriolis.rotation_rate
rotation_rate_Earth = rotation_rate / Ω_Earth
rotation_rate_str = @sprintf("%.2e s⁻¹ = %.2e Ω_Earth", rotation_rate, rotation_rate_Earth)

return print(io, "NonhydrostaticSphericalCoriolis", '\n',
"├─ rotation rate: ", rotation_rate_str, '\n')
end
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
using Oceananigans.Grids: LatitudeLongitudeGrid, OrthogonalSphericalShellGrid, peripheral_node, φnode
using Oceananigans.Operators: Δx_qᶜᶠᶜ, Δy_qᶠᶜᶜ, Δxᶠᶜᶜ, Δyᶜᶠᶜ, hack_sind
using Oceananigans.Operators: Δx_qᶜᶠᶜ, Δy_qᶠᶜᶜ, Δxᶠᶜᶜ, Δyᶜᶠᶜ, hack_sind, hack_cosd
using Oceananigans.Advection: EnergyConserving, EnstrophyConserving
using Oceananigans.BoundaryConditions
using Oceananigans.Fields
using Oceananigans.AbstractOperations: KernelFunctionOperation
using Oceananigans.ImmersedBoundaries

"""
struct HydrostaticSphericalCoriolis{S, FT} <: AbstractRotation
struct SphericalCoriolis{S, FT} <: AbstractRotation

A parameter object for constant rotation around a vertical axis on the sphere.
"""
struct HydrostaticSphericalCoriolis{S, FT} <: AbstractRotation
struct SphericalCoriolis{H, S, FT} <: AbstractRotation
rotation_rate :: FT
scheme :: S
HydrostaticCoriolis :: H
Copy link
Member

Choose a reason for hiding this comment

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

Will this work? Whether or not we are using the hydrostatic approximation has to be encoded in a type, which is known at compile time.

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 can't say that it will work. This was a suggestion that I was trying out.

I think that we need a flag to specify before we define Coriolis or model, that says whether we want hydrostatic or non-hydrostatic, which is not ideal as that is one more thing the user needs to speicfy.

Do you have a different idea on how to do this?

Copy link
Member

@glwagner glwagner Aug 6, 2025

Choose a reason for hiding this comment

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

i propose this API:

hydrostatic_coriolis = HydrostaticSphericalCoriolis()
full_coriolis = SphericalCoriolis()

for implementation, we need a type such that

const HydrostaticSphericalCoriolis{S, FT} = SphericalCoriolis{S, FT, <:HydrostaticFormulation} where {S, FT}

note this preserves the type structure on main so it is not a breaking PR.

one can introduce the concept of the "formulation" like this

struct HydrostaticFormulation end
struct NonhydrostaticFormulation end

struct SphericalCoriolis{S, FT, F}
    rotation_rate :: FT
    scheme :: S
    formulation :: F
end

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks @glwagner . I'll give this a try and get back to you when I have something together.

end

"""
HydrostaticSphericalCoriolis([FT=Float64;]
SphericalCoriolis([FT=Float64;]
rotation_rate = Ω_Earth,
scheme = EnstrophyConserving())
HydrostaticCoriolis =

Return a parameter object for Coriolis forces on a sphere rotating at `rotation_rate`.

Expand All @@ -28,71 +30,60 @@ Keyword arguments

- `rotation_rate`: Sphere's rotation rate; default: [`Ω_Earth`](@ref).
- `scheme`: Either `EnergyConserving()`, `EnstrophyConserving()`, or `EnstrophyConserving()` (default).
- 'HydrostaticCoriolis`: `HydrostaticCoriolis` or `NonhydrostaticCoriolis`.
"""
function HydrostaticSphericalCoriolis(FT::DataType=Oceananigans.defaults.FloatType;

function SphericalCoriolis(FT::DataType=Oceananigans.defaults.FloatType;
rotation_rate = Ω_Earth,
scheme :: S = EnstrophyConserving(FT)) where S

return HydrostaticSphericalCoriolis{S, FT}(rotation_rate, scheme)
return SphericalCoriolis{S, FT}(rotation_rate, scheme)
end

Adapt.adapt_structure(to, coriolis::HydrostaticSphericalCoriolis) =
HydrostaticSphericalCoriolis(Adapt.adapt(to, coriolis.rotation_rate),
Adapt.adapt_structure(to, coriolis::SphericalCoriolis) =
SphericalCoriolis(Adapt.adapt(to, coriolis.rotation_rate),
Adapt.adapt(to, coriolis.scheme))

@inline φᶠᶠᵃ(i, j, k, grid::LatitudeLongitudeGrid) = φnode(j, grid, face)
@inline φᶠᶠᵃ(i, j, k, grid::OrthogonalSphericalShellGrid) = φnode(i, j, grid, face, face)
@inline φᶠᶠᵃ(i, j, k, grid::ImmersedBoundaryGrid) = φᶠᶠᵃ(i, j, k, grid.underlying_grid)

@inline fᶠᶠᵃ(i, j, k, grid, coriolis::HydrostaticSphericalCoriolis) =
@inline fᶠᶠᵃ(i, j, k, grid, coriolis::SphericalCoriolis) =
2 * coriolis.rotation_rate * hack_sind(φᶠᶠᵃ(i, j, k, grid))
@inline f̃ᶠᶠᵃ(i, j, k, grid, coriolis::SphericalCoriolis) =
2 * coriolis.rotation_rate * hack_cosd(φᶠᶠᵃ(i, j, k, grid))

@inline z_f_cross_U(i, j, k, grid, coriolis::HydrostaticSphericalCoriolis, U) = zero(grid)

#####
##### Active Point Enstrophy-conserving scheme
#####

# It might happen that a cell is active but all the neighbouring staggered nodes are inactive,
# (an example is a 1-cell large channel)
# In that case the Coriolis force is equal to zero

const CoriolisEnstrophyConserving = HydrostaticSphericalCoriolis{<:EnstrophyConserving}

@inline x_f_cross_U(i, j, k, grid, coriolis::CoriolisEnstrophyConserving, U) =
@inbounds - ℑyᵃᶜᵃ(i, j, k, grid, fᶠᶠᵃ, coriolis) *
active_weighted_ℑxyᶠᶜᶜ(i, j, k, grid, Δx_qᶜᶠᶜ, U[2]) * Δx⁻¹ᶠᶜᶜ(i, j, k, grid)
@inline f_ℑy_uᶠᶠᵃ(i, j, k, grid, coriolis, u) = fᶠᶠᵃ(i, j, k, grid, coriolis) * ℑyᵃᶠᵃ(i, j, k, grid, Δy_qᶠᶜᶜ, u)
@inline f̃_ℑz_uᶠᶠᵃ(i, j, k, grid, coriolis::NonhydrostaticSphericalCoriolis, u) = f̃ᶠᶠᵃ(i, j, k, grid, coriolis) * ℑzᵃᶠᵃ(i, j, k, grid, Δz_qᶜᶜᶠ, u)
@inline f̃_ℑz_uᶠᶠᵃ(i, j, k, grid, coriolis::HydrostaticSphericalCoriolis, u) = zero(grid)

@inline y_f_cross_U(i, j, k, grid, coriolis::CoriolisEnstrophyConserving, U) =
@inbounds + ℑxᶜᵃᵃ(i, j, k, grid, fᶠᶠᵃ, coriolis) *
active_weighted_ℑxyᶜᶠᶜ(i, j, k, grid, Δy_qᶠᶜᶜ, U[1]) * Δy⁻¹ᶜᶠᶜ(i, j, k, grid)
@inline f_ℑx_vᶠᶠᵃ(i, j, k, grid, coriolis, v) = fᶠᶠᵃ(i, j, k, grid, coriolis) * ℑxᶠᵃᵃ(i, j, k, grid, Δx_qᶜᶠᶜ, v)

#####
##### Energy-conserving scheme
#####
@inline f̃_ℑx_wᶠᶠᵃ(i, j, k, grid, coriolis::NonhydrostaticSphericalCoriolis, w) = f̃ᶠᶠᵃ(i, j, k, grid, coriolis) * ℑxᶠᵃᵃ(i, j, k, grid, Δx_qᶜᶠᶜ, w)
@inline f̃_ℑx_wᶠᶠᵃ(i, j, k, grid, coriolis::HydrostaticSphericalCoriolis, w) = zero(grid)

const CoriolisEnergyConserving = HydrostaticSphericalCoriolis{<:EnergyConserving}
@inline x_f_cross_U(i, j, k, grid, U) =
@inbounds - ℑyᵃᶜᵃ(i, j, k, grid, f_ℑx_vᶠᶠᵃ, U[2]) * Δx⁻¹ᶠᶜᶜ(i, j, k, grid)
+ ℑzᵃᶜᵃ(i, j, k, grid, f̃_ℑx_wᶠᶠᵃ, U[3]) * Δx⁻¹ᶠᶜᶜ(i, j, k, grid)

@inline f_ℑx_vᶠᶠᵃ(i, j, k, grid, coriolis, v) = fᶠᶠᵃ(i, j, k, grid, coriolis) * ℑxᶠᵃᵃ(i, j, k, grid, Δx_qᶜᶠᶜ, v)
@inline f_ℑy_uᶠᶠᵃ(i, j, k, grid, coriolis, u) = fᶠᶠᵃ(i, j, k, grid, coriolis) * ℑyᵃᶠᵃ(i, j, k, grid, Δy_qᶠᶜᶜ, u)
@inline y_f_cross_U(i, j, k, grid, U) =
@inbounds + ℑxᶜᵃᵃ(i, j, k, grid, f_ℑy_uᶠᶠᵃ, U[1]) * Δy⁻¹ᶜᶠᶜ(i, j, k, grid)

@inline x_f_cross_U(i, j, k, grid, coriolis::CoriolisEnergyConserving, U) =
@inbounds - ℑyᵃᶜᵃ(i, j, k, grid, f_ℑx_vᶠᶠᵃ, coriolis, U[2]) * Δx⁻¹ᶠᶜᶜ(i, j, k, grid)

@inline y_f_cross_U(i, j, k, grid, coriolis::CoriolisEnergyConserving, U) =
@inbounds + ℑxᶜᵃᵃ(i, j, k, grid, f_ℑy_uᶠᶠᵃ, coriolis, U[1]) * Δy⁻¹ᶜᶠᶜ(i, j, k, grid)
@inline z_f_cross_U(i, j, k, grid, U) =
@inbounds - ℑxᶜᵃᵃ(i, j, k, grid, f̃_ℑz_uᶠᶠᵃ, U[1]) * Δz⁻¹ᶜᶜᶠ(i, j, k, grid)

#####
##### Show
#####

function Base.show(io::IO, hydrostatic_spherical_coriolis::HydrostaticSphericalCoriolis)
coriolis_scheme = hydrostatic_spherical_coriolis.scheme
rotation_rate = hydrostatic_spherical_coriolis.rotation_rate
function Base.show(io::IO, spherical_coriolis::SphericalCoriolis)
coriolis_scheme = spherical_coriolis.scheme
rotation_rate = spherical_coriolis.rotation_rate
rotation_rate_Earth = rotation_rate / Ω_Earth
rotation_rate_str = @sprintf("%.2e s⁻¹ = %.2e Ω_Earth", rotation_rate, rotation_rate_Earth)

return print(io, "HydrostaticSphericalCoriolis", '\n',
return print(io, "SphericalCoriolis", '\n',
"├─ rotation rate: ", rotation_rate_str, '\n',
"└─ scheme: ", summary(coriolis_scheme))
end
Loading