Skip to content

Commit

Permalink
Merge 2b48385 into a65ffcd
Browse files Browse the repository at this point in the history
  • Loading branch information
simone-silvestri authored Oct 10, 2024
2 parents a65ffcd + 2b48385 commit 156b79f
Show file tree
Hide file tree
Showing 11 changed files with 188 additions and 43 deletions.
1 change: 1 addition & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ KernelAbstractions = "63c18a36-062a-441e-b654-da1e3ab1ce7c"
MPI = "da04e1cc-30fd-572f-bb4f-1f8673147195"
Oceananigans = "9e8cae18-63c1-5223-a75c-80ca9d6e9a09"
OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"

[compat]
Adapt = "4"
Expand Down
4 changes: 2 additions & 2 deletions src/OrthogonalSphericalShellGrids.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ using OffsetArrays

@inline convert_to_0_360(x) = ((x % 360) + 360) % 360

include("grid_utils.jl")
include("tripolar_grid_utils.jl")
include("zipper_boundary_condition.jl")
include("generate_tripolar_coordinates.jl")
include("tripolar_grid.jl")
include("grid_extensions.jl")
include("tripolar_grid_extensions.jl")
include("distributed_tripolar_grid.jl")
include("with_halo.jl")
include("split_explicit_free_surface.jl")
Expand Down
4 changes: 2 additions & 2 deletions src/distributed_tripolar_grid.jl
Original file line number Diff line number Diff line change
Expand Up @@ -220,13 +220,13 @@ function reconstruct_global_grid(grid::DistributedTripolarGrid)

north_poles_latitude = grid.conformal_mapping.north_poles_latitude
first_pole_longitude = grid.conformal_mapping.first_pole_longitude
southermost_latitude = grid.conformal_mapping.southermost_latitude
southernmost_latitude = grid.conformal_mapping.southernmost_latitude

return TripolarGrid(child_arch, FT;
halo,
size,
north_poles_latitude,
first_pole_longitude,
southermost_latitude,
southernmost_latitude,
z)
end
18 changes: 9 additions & 9 deletions src/tripolar_grid.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@
struct Tripolar{N, F, S}
north_poles_latitude :: N
first_pole_longitude :: F
southermost_latitude :: S
southernmost_latitude :: S
end

Adapt.adapt_structure(to, t::Tripolar) =
Tripolar(Adapt.adapt(to, t.north_poles_latitude),
Adapt.adapt(to, t.first_pole_longitude),
Adapt.adapt(to, t.southermost_latitude))
Adapt.adapt(to, t.southernmost_latitude))

const TripolarGrid{FT, TX, TY, TZ, A, R, FR, Arch} = OrthogonalSphericalShellGrid{FT, TX, TY, TZ, A, R, FR, <:Tripolar, Arch}

"""
TripolarGrid(arch = CPU(), FT::DataType = Float64;
size,
southermost_latitude = -80,
southernmost_latitude = -80,
halo = (4, 4, 4),
radius = R_Earth,
z = (0, 1),
Expand All @@ -39,7 +39,7 @@ Keyword Arguments
=================
- `size`: The number of cells in the (longitude, latitude, vertical) dimensions.
- `southermost_latitude`: The southernmost `Center` latitude of the grid. Default is -80.
- `southernmost_latitude`: The southernmost `Center` latitude of the grid. Default is -80.
- `halo`: The halo size in the (longitude, latitude, vertical) dimensions. Default is (4, 4, 4).
- `radius`: The radius of the spherical shell. Default is `R_Earth`.
- `z`: The vertical ``z``-coordinate range of the grid. Default is (0, 1).
Expand All @@ -57,7 +57,7 @@ The north singularities are located at
"""
function TripolarGrid(arch = CPU(), FT::DataType = Float64;
size,
southermost_latitude = -80, # The southermost `Center` latitude of the grid
southernmost_latitude = -80, # The southermost `Center` latitude of the grid
halo = (4, 4, 4),
radius = R_Earth,
z = (0, 1),
Expand All @@ -68,7 +68,7 @@ function TripolarGrid(arch = CPU(), FT::DataType = Float64;
# to construct the grid on the GPU. This is not a huge problem as
# grid generation is quite fast, but it might become for sub-kilometer grids

latitude = (southermost_latitude, 90)
latitude = (southernmost_latitude, 90)
longitude = (-180, 180)

focal_distance = tand((90 - north_poles_latitude) / 2)
Expand All @@ -87,8 +87,8 @@ function TripolarGrid(arch = CPU(), FT::DataType = Float64;
Lz, zᵃᵃᶠ, zᵃᵃᶜ, Δzᵃᵃᶠ, Δzᵃᵃᶜ = generate_coordinate(FT, Bounded(), Nz, Hz, z, :z, CPU())

# The φ coordinate is a bit more complicated because the center points start from
# southermost_latitude and end at 90ᵒ N.
φᵃᶜᵃ = collect(range(southermost_latitude, 90, length = Nφ))
# southernmost_latitude and end at 90ᵒ N.
φᵃᶜᵃ = collect(range(southernmost_latitude, 90, length = Nφ))
Δφ = φᵃᶜᵃ[2] - φᵃᶜᵃ[1]
φᵃᶠᵃ = φᵃᶜᵃ .- Δφ / 2

Expand Down Expand Up @@ -322,7 +322,7 @@ function TripolarGrid(arch = CPU(), FT::DataType = Float64;
on_architecture(arch, Azᶜᶠᵃ),
on_architecture(arch, Azᶠᶠᵃ),
radius,
Tripolar(north_poles_latitude, first_pole_longitude, southermost_latitude))
Tripolar(north_poles_latitude, first_pole_longitude, southernmost_latitude))

return grid
end
Expand Down
File renamed without changes.
24 changes: 9 additions & 15 deletions src/grid_utils.jl → src/tripolar_grid_utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -38,21 +38,15 @@ end
d = lat_lon_to_cartesian(φᶠᶠᵃ[ i , j+1], λᶠᶠᵃ[ i , j+1], 1)

Azᶜᶜᵃ[i, j] = spherical_area_quadrilateral(a, b, c, d) * radius^2

a = lat_lon_to_cartesian(φᶜᶠᵃ[i-1, j ], λᶜᶠᵃ[i-1, j ], 1)
b = lat_lon_to_cartesian(φᶜᶠᵃ[ i , j ], λᶜᶠᵃ[ i , j ], 1)
c = lat_lon_to_cartesian(φᶜᶠᵃ[ i , j+1], λᶜᶠᵃ[ i , j+1], 1)
d = lat_lon_to_cartesian(φᶜᶠᵃ[i-1, j+1], λᶜᶠᵃ[i-1, j+1], 1)

Azᶠᶜᵃ[i, j] = spherical_area_quadrilateral(a, b, c, d) * radius^2

a = lat_lon_to_cartesian(φᶠᶜᵃ[ i , j-1], λᶠᶜᵃ[ i , j-1], 1)
b = lat_lon_to_cartesian(φᶠᶜᵃ[i+1, j-1], λᶠᶜᵃ[i+1, j-1], 1)
c = lat_lon_to_cartesian(φᶠᶜᵃ[i+1, j ], λᶠᶜᵃ[i+1, j ], 1)
d = lat_lon_to_cartesian(φᶠᶜᵃ[ i , j ], λᶠᶜᵃ[ i , j ], 1)

Azᶜᶠᵃ[i, j] = spherical_area_quadrilateral(a, b, c, d) * radius^2


# To be able to conserve kinetic energy specifically the momentum equation,
# it is better to define the face areas as products of
# the edge lengths rather than using the spherical area of the face (cit JMC).
# TODO: find a reference to support this statement
Azᶠᶜᵃ[i, j] = Δyᶠᶜᵃ[i, j] * Δxᶠᶜᵃ[i, j]
Azᶜᶠᵃ[i, j] = Δyᶜᶠᵃ[i, j] * Δxᶜᶠᵃ[i, j]

# Face - Face areas are calculated as the Center - Center ones
a = lat_lon_to_cartesian(φᶜᶜᵃ[i-1, j-1], λᶜᶜᵃ[i-1, j-1], 1)
b = lat_lon_to_cartesian(φᶜᶜᵃ[ i , j-1], λᶜᶜᵃ[ i , j-1], 1)
c = lat_lon_to_cartesian(φᶜᶜᵃ[ i , j ], λᶜᶜᵃ[ i , j ], 1)
Expand Down
8 changes: 4 additions & 4 deletions src/with_halo.jl
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ function with_halo(new_halo, old_grid::TripolarGrid)

north_poles_latitude = old_grid.conformal_mapping.north_poles_latitude
first_pole_longitude = old_grid.conformal_mapping.first_pole_longitude
southermost_latitude = old_grid.conformal_mapping.southermost_latitude
southernmost_latitude = old_grid.conformal_mapping.southernmost_latitude

new_grid = TripolarGrid(architecture(old_grid), eltype(old_grid);
size, z, halo = new_halo,
radius = old_grid.radius,
north_poles_latitude,
first_pole_longitude,
southermost_latitude)
southernmost_latitude)

return new_grid
end
Expand All @@ -32,13 +32,13 @@ function with_halo(new_halo, old_grid::DistributedTripolarGrid)

north_poles_latitude = old_grid.conformal_mapping.north_poles_latitude
first_pole_longitude = old_grid.conformal_mapping.first_pole_longitude
southermost_latitude = old_grid.conformal_mapping.southermost_latitude
southernmost_latitude = old_grid.conformal_mapping.southernmost_latitude

return TripolarGrid(arch, eltype(old_grid);
halo = new_halo,
size = N,
north_poles_latitude,
first_pole_longitude,
southermost_latitude,
southernmost_latitude,
z)
end
12 changes: 12 additions & 0 deletions test/dependencies_for_runtests.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using OrthogonalSphericalShellGrids
using Oceananigans
using Oceananigans.Grids: halo_size, φnodes, λnodes
using Oceananigans.Utils
using Oceananigans.BoundaryConditions
using OrthogonalSphericalShellGrids: get_cartesian_nodes_and_vertices
using Oceananigans.CUDA
using Test

using KernelAbstractions: @kernel, @index

arch = CUDA.has_cuda_gpu() ? GPU() : CPU()
42 changes: 31 additions & 11 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,16 +1,36 @@
using OrthogonalSphericalShellGrids
using OrthogonalSphericalShellGrids.Oceananigans
using Oceananigans: GPU, CPU
using Oceananigans.CUDA
using Test
include("dependencies_for_runtests.jl")

arch = CUDA.has_cuda_gpu() ? GPU() : CPU()
@testset "Unit tests..." begin
grid = TripolarGrid(size = (4, 5, 1), z = (0, 1),
first_pole_longitude = 75,
north_poles_latitude = 35,
southernmost_latitude = -80)

@testset "OrthogonalSphericalShellGrids.jl" begin
# We probably do not need any unit tests.
@test grid isa TripolarGrid

# Test the grid?
grid = TripolarGrid(arch; size = (10, 10, 1))
@test grid.Nx == 4
@test grid.Ny == 5
@test grid.Nz == 1

# Test boundary conditions?
@test grid.conformal_mapping.first_pole_longitude == 75
@test grid.conformal_mapping.north_poles_latitude == 35
@test grid.conformal_mapping.southernmost_latitude == -80

λᶜᶜᵃ = λnodes(grid, Center(), Center())
φᶜᶜᵃ = φnodes(grid, Center(), Center())

min_Δφ = minimum(φᶜᶜᵃ[:, 2] .- φᶜᶜᵃ[:, 1])

@test minimum(λᶜᶜᵃ) 0
@test maximum(λᶜᶜᵃ) 360
@test maximum(φᶜᶜᵃ) 90

# The minimum latitude is not exactly the southermost latitude because the grid
# undulates slightly to maintain the same analytical description in the whole sphere
# (i.e. constant latitude lines do not exist anywhere in this grid)
@test minimum(φᶜᶜᶜ .+ min_Δφ / 10) grid.conformal_mapping.southernmost_latitude
end

include("test_tripolar_grid.jl")
include("test_zipper_boundary_conditions.jl")

75 changes: 75 additions & 0 deletions test/test_tripolar_grid.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
include("dependencies_for_runtests.jl")

using Statistics: dot, norm
using Oceananigans.Utils: getregion
using Oceananigans.ImmersedBoundaries: immersed_cell

@kernel function compute_nonorthogonality_angle!(angle, grid, xF, yF, zF)
i, j = @index(Global, NTuple)

@inbounds begin
x⁻ = xF[i, j]
y⁻ = yF[i, j]
z⁻ = zF[i, j]

x⁺¹ = xF[i + 1, j]
y⁺¹ = yF[i + 1, j]
z⁺¹ = zF[i + 1, j]
x⁺² = xF[i, j + 1]
y⁺² = yF[i, j + 1]
z⁺² = zF[i, j + 1]

v1 = (x⁺¹ - x⁻, y⁺¹ - y⁻, z⁺¹ - z⁻)
v2 = (x⁺² - x⁻, y⁺² - y⁻, z⁺² - z⁻)

# Check orthogonality by computing the angle between the vectors
cosθ = dot(v1, v2) / (norm(v1) * norm(v2))
immersed = immersed_cell(i, j, 1, grid)
angle[i, j] = ifelse(immersed, π / 2, acos(cosθ)) - π / 2

# convert to degrees
angle[i, j] = rad2deg(angle[i, j])
end
end

@testset "Orthogonality of family of ellipses and hyperbolae..." begin

# Test the orthogonality of a tripolar grid based on the orthogonality of a
# cubed sphere of the same size (1ᵒ in latitude and longitude)
cubed_sphere_grid = ConformalCubedSphereGrid(panel_size = (90, 90, 1), z = (0, 1))
cubed_sphere_panel = getregion(cubed_sphere_grid, 1)

angle_cubed_sphere = zeros(size(cubed_sphere_panel)...)
cartesian_nodes, _ = get_cartesian_nodes_and_vertices(cubed_sphere_panel, Face(), Face(), Center())
xF, yF, zF = cartesian_nodes
Nx, Ny, _ = size(cubed_sphere_panel)

# Exclude the corners from the computation! (They are definitely not orthogonal)
params = KernelParameters(5:Nx-5, 5:Ny-5)

launch!(CPU(), cubed_sphere_panel, params, compute_nonorthogonality_angle!, angle_cubed_sphere, cubed_sphere_panel, xF, yF, zF)

first_pole_longitude = λ¹ₚ = 75
north_poles_latitude = φₚ = 35

λ²ₚ = λ¹ₚ + 180

# Build a tripolar grid at 1ᵒ
underlying_grid = TripolarGrid(; size = (360, 180, 1), first_pole_longitude, north_poles_latitude)

# We need a bottom height field that ``masks'' the singularities
bottom_height(λ, φ) = ((abs- λ¹ₚ) < 5) & (abs(φₚ - φ) < 5)) |
((abs- λ²ₚ) < 5) & (abs(φₚ - φ) < 5)) |< -78) ? 1 : 0

# Exclude the singularities from the computation! (They are definitely not orthogonal)
tripolar_grid = ImmersedBoundaryGrid(underlying_grid, GridFittedBottom(bottom_height))
angle_tripolar = zeros(size(tripolar_grid)...)
cartesian_nodes, _ = get_cartesian_nodes_and_vertices(tripolar_grid.underlying_grid, Face(), Face(), Center())
xF, yF, zF = cartesian_nodes
Nx, Ny, _ = size(tripolar_grid)

launch!(CPU(), tripolar_grid, (Nx-1, Ny-1), compute_nonorthogonality_angle!, angle_tripolar, tripolar_grid, xF, yF, zF)

@test maximum(angle_tripolar) < maximum(angle_cubed_sphere)
@test minimum(angle_tripolar) > minimum(angle_cubed_sphere)
end
43 changes: 43 additions & 0 deletions test/test_zipper_boundary_conditions.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
include("dependencies_for_runtests.jl")

using OrthogonalSphericalShellGrids: Zipper

@testset "Zipper boundary conditions..." begin
grid = TripolarGrid(size = (10, 10, 1))
Nx, Ny, _ = size(grid)
Hx, Hy, _ = halo_size(grid)

c = CenterField(grid)
u = XFaceField(grid)
v = YFaceField(grid)

@test c.boundary_conditions.north.classification isa Zipper
@test u.boundary_conditions.north.classification isa Zipper
@test v.boundary_conditions.north.classification isa Zipper

@test c.boundary_conditions.north.condition == 1
@test u.boundary_conditions.north.condition == -1
@test v.boundary_conditions.north.condition == -1

set!(c, 1)
set!(u, 1)
set!(v, 1)

fill_halo_regions!(c)
fill_halo_regions!(u)
fill_halo_regions!(v)

north_boundary_c = view(c.data, :, Ny+1:Ny+Hy, 1)
north_boundary_v = view(v.data, :, Ny+1:Ny+Hy, 1)
@test all(north_boundary_c .== 1)
@test all(north_boundary_v .== -1)

# U is special, because periodicity is hardcoded in the x-direction
north_interior_boundary_u = view(u.data, 2:Nx-1, Ny+1:Ny+Hy, 1)
@test all(north_interior_boundary_u .== -1)

north_boundary_u_left = view(u.data, 1, Ny+1:Ny+Hy, 1)
north_boundary_u_right = view(u.data, Nx+1, Ny+1:Ny+Hy, 1)
@test all(north_boundary_u_left .== 1)
@test all(north_boundary_u_right .== 1)
end

0 comments on commit 156b79f

Please sign in to comment.