Skip to content

Commit 7798178

Browse files
authored
Fix RotatedLatitudeLongitudeGrid and add more tests (#4879)
* Fix RotatedLatitudeLongitudeGrid and add more tests * fix error comment * fix docstring * infer topology as for ordinary lat lon grid
1 parent 030b1b9 commit 7798178

File tree

2 files changed

+83
-42
lines changed

2 files changed

+83
-42
lines changed

src/OrthogonalSphericalShellGrids/rotated_latitude_longitude_grid.jl

Lines changed: 55 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
using CubedSphere.SphericalGeometry
2-
using Oceananigans.Grids: LatitudeLongitudeGrid, Bounded
2+
using Oceananigans.Grids: LatitudeLongitudeGrid, Bounded, validate_lat_lon_grid_args
33
using Oceananigans.Utils: KernelParameters
44
using StaticArrays
55
using LinearAlgebra
@@ -59,24 +59,26 @@ z = (0, 1)
5959
grid = RotatedLatitudeLongitudeGrid(; size, longitude, latitude, z, north_pole=(70, 55))
6060
6161
# output
62-
90×40×1 OrthogonalSphericalShellGrid{Float64, Bounded, Bounded, Bounded} on CPU with 3×3×3 halo and with precomputed metrics
63-
├── centered at (λ, φ) = (146.656, 11.3134)
64-
├── longitude: Bounded extent 360.0 degrees variably spaced with min(Δλ)=0.694593, max(Δλ)=4.0
65-
├── latitude: Bounded extent 160.0 degrees variably spaced with min(Δφ)=4.0, max(Δφ)=4.0
66-
└── z: Bounded z ∈ [0.0, 1.0] regularly spaced with Δz=1.0
62+
90×40×1 OrthogonalSphericalShellGrid{Float64, Periodic, Bounded, Bounded} on CPU with 3×3×3 halo and with precomputed metrics
63+
├── centered at (λ, φ) = (-110.0, 35.0)
64+
├── longitude: Periodic extent 360.0 degrees variably spaced with min(Δλ)=0.694593, max(Δλ)=4.0
65+
├── latitude: Bounded extent 160.0 degrees variably spaced with min(Δφ)=4.0, max(Δφ)=4.0
66+
└── z: Bounded z ∈ [0.0, 1.0] regularly spaced with Δz=1.0
6767
```
6868
69-
We can also make an ordinary LatitudeLongitudeGrid using `north_polar = (0, 90)`:
69+
Note that the center latitude ``λ = -110`` follows from ``180 + 70 - 360 = -110``:
70+
a clockwise rotation of 70 degrees modulo 360 degrees.
71+
We can also make an ordinary LatitudeLongitudeGrid using `north_pole = (0, 90)`:
7072
7173
```jldoctest rllg
7274
grid = RotatedLatitudeLongitudeGrid(; size, longitude, latitude, z, north_pole=(0, 90))
7375
7476
# output
75-
90×40×1 OrthogonalSphericalShellGrid{Float64, Bounded, Bounded, Bounded} on CPU with 3×3×3 halo and with precomputed metrics
77+
90×40×1 OrthogonalSphericalShellGrid{Float64, Periodic, Bounded, Bounded} on CPU with 3×3×3 halo and with precomputed metrics
7678
├── centered at (λ, φ) = (180.0, 0.0)
77-
├── longitude: Bounded extent 360.0 degrees variably spaced with min(Δλ)=0.694593, max(Δλ)=4.0
78-
├── latitude: Bounded extent 160.0 degrees variably spaced with min(Δφ)=4.0, max(Δφ)=4.0
79-
└── z: Bounded z ∈ [0.0, 1.0] regularly spaced with Δz=1.0
79+
├── longitude: Periodic extent 360.0 degrees variably spaced with min(Δλ)=0.694593, max(Δλ)=4.0
80+
├── latitude: Bounded extent 160.0 degrees variably spaced with min(Δφ)=4.0, max(Δφ)=4.0
81+
└── z: Bounded z ∈ [0.0, 1.0] regularly spaced with Δz=1.0
8082
```
8183
"""
8284
function RotatedLatitudeLongitudeGrid(arch::AbstractArchitecture = CPU(),
@@ -88,15 +90,23 @@ function RotatedLatitudeLongitudeGrid(arch::AbstractArchitecture = CPU(),
8890
z,
8991
halo = (3, 3, 3),
9092
radius = Oceananigans.defaults.planet_radius,
91-
topology = (Bounded, Bounded, Bounded))
93+
topology = nothing)
9294

93-
shifted_halo = halo .+ 1
94-
source_grid = LatitudeLongitudeGrid(arch, FT; size, z, topology, radius,
95-
latitude, longitude, halo = shifted_halo)
95+
precompute_metrics = true
96+
topology, size, halo, latitude, longitude, z, precompute_metrics =
97+
validate_lat_lon_grid_args(topology, size, halo, FT, latitude, longitude, z, precompute_metrics)
98+
99+
_, φ₀ = north_pole
100+
101+
if φ₀ < 0
102+
throw(ArgumentError("North pole latitude must be >= 0."))
103+
elseif φ₀ > 90
104+
throw(ArgumentError("North pole latitude must be <= 90 degrees."))
105+
end
96106

97-
Nx, Ny, Nz = size
98-
Hx, Hy, Hz = halo_size(source_grid)
99-
Lz = source_grid.Lz
107+
shifted_halo = halo .+ 1
108+
source_grid = LatitudeLongitudeGrid(arch, FT; halo=shifted_halo, precompute_metrics,
109+
size, z, topology, radius, latitude, longitude)
100110

101111
conformal_mapping = LatitudeLongitudeRotation(north_pole)
102112
grid = OrthogonalSphericalShellGrid(arch, FT; size, z, radius, halo, topology, conformal_mapping)
@@ -136,44 +146,50 @@ function cartesian_to_spherical(X)
136146
end
137147

138148
# Rotation about x-axis by dλ (Change in Longitude)
139-
x_rotation(dλ) = @SMatrix [1 0 0
140-
0 cos(dλ) -sin(dλ)
141-
0 sin(dλ) cos(dλ)]
149+
x_rotation_matrix(dλ) = @SMatrix [1 0 0
150+
0 cos(dλ) -sin(dλ)
151+
0 sin(dλ) cos(dλ)]
142152

143153
# Rotation about y-axis by dφ (Change in Latitude)
144-
y_rotation(dφ) = @SMatrix [ cos(dφ) 0 sin(dφ)
145-
0 1 0
146-
-sin(dφ) 0 cos(dφ)]
154+
y_rotation_matrix(dφ) = @SMatrix [ cos(dφ) 0 sin(dφ)
155+
0 1 0
156+
-sin(dφ) 0 cos(dφ)]
147157

148158
# Rotation about z-axis by dλ (Change in Longitude)
149-
z_rotation(dλ) = @SMatrix [cos(dλ) -sin(dλ) 0
150-
sin(dλ) cos(dλ) 0
151-
0 0 1]
159+
z_rotation_matrix(dλ) = @SMatrix [cos(dλ) -sin(dλ) 0
160+
sin(dλ) cos(dλ) 0
161+
0 0 1]
162+
163+
"""
164+
rotate_coordinates(λ′, φ′, λ₀, φ₀)
152165
153-
# Perform the rotation
166+
Return the geographic longitude and latitude `(λ, φ)` corresponding to the rotated
167+
coordinates `(λ′, φ′)` on a grid whose north pole is located at `(λ₀, φ₀)`. All
168+
arguments are interpreted in degrees. The rotation aligns the grid pole with the
169+
geographic pole, then maps the rotated point back to geographic coordinates.
170+
"""
154171
function rotate_coordinates(λ′, φ′, λ₀, φ₀)
155-
λ′ *= π/180
156-
φ′ *= π/180
157-
λ₀ *= π/180
158-
φ₀ *= π/180
172+
λ′ *= π / 180
173+
φ′ *= π / 180
174+
λ₀ *= π / 180
175+
φ₀ *= π / 180
159176

160-
= - λ₀
177+
= λ₀
161178
= π/2 - φ₀
162179

163180
# Convert to Cartesian
164181
X′ = SVector(spherical_to_cartesian(φ′, λ′; check_latitude_bounds = false)...)
165182

166183
# Rotate Cartesian coordinates
167-
Rx = x_rotation(dλ)
168-
Ry = y_rotation(dφ)
169-
Rz = z_rotation(dλ)
170-
X = Rx * Ry * X′
184+
Ry = y_rotation_matrix(dφ)
185+
Rz = z_rotation_matrix(dλ)
186+
X = Rz * Ry * X′
171187

172188
# Convert back to Spherical
173189
φ, λ = cartesian_to_spherical(X)
174190

175-
λ *= 180/π
176-
φ *= 180/π
191+
λ *= 180 / π
192+
φ *= 180 / π
177193

178194
return λ, φ
179195
end

test/test_grids.jl

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ using Oceananigans.Grids: total_extent, ColumnEnsembleSize,
66
xnode, ynode, znode, λnode, φnode,
77
λspacings, φspacings
88

9-
using Oceananigans.OrthogonalSphericalShellGrids: RotatedLatitudeLongitudeGrid, ConformalCubedSpherePanelGrid
9+
using Oceananigans.OrthogonalSphericalShellGrids: RotatedLatitudeLongitudeGrid, ConformalCubedSpherePanelGrid, rotate_coordinates
1010

1111
using Oceananigans.Operators: Δx, Δy, Δz, Δλ, Δφ, Ax, Ay, Az, volume
1212
using Oceananigans.Operators: Δxᶠᶜᵃ, Δxᶜᶠᵃ, Δxᶠᶠᵃ, Δxᶜᶜᵃ, Δyᶠᶜᵃ, Δyᶜᶠᵃ, Azᶠᶜᵃ, Azᶜᶠᵃ, Azᶠᶠᵃ, Azᶜᶜᵃ
@@ -1243,13 +1243,38 @@ end
12431243
north_pole = (0, 0),
12441244
topology = (Bounded, Bounded, Bounded))
12451245

1246-
@show grid.conformal_mapping
1247-
12481246
@test grid isa OrthogonalSphericalShellGrid
12491247
@test grid isa RotatedLatitudeLongitudeGrid
12501248
@test grid.Lz == 1000
12511249
@test size(grid) == (10, 10, 1)
12521250

1251+
@testset "RotatedLatitudeLongitudeGrid: rotate_coordinates utility" begin
1252+
for φ₀ = (90, 30, 10, 0)
1253+
λ, φ = rotate_coordinates(0, 0, 0, φ₀)
1254+
@test λ 0
1255+
@test φ φ₀ - 90
1256+
end
1257+
1258+
λᴺ, φᴺ = rotate_coordinates(0, 90, 70, 55)
1259+
@test λᴺ 70
1260+
@test φᴺ 55
1261+
end
1262+
1263+
@testset "RotatedLatitudeLongitudeGrid respects north_pole argument" begin
1264+
test_kw = (size = (6, 6, 1),
1265+
latitude = (-80, 80),
1266+
longitude = (-120, 120),
1267+
z = (-100, 0),
1268+
topology = (Bounded, Bounded, Bounded))
1269+
1270+
tilted_pole = (70, 55)
1271+
tilted_grid = RotatedLatitudeLongitudeGrid(north_pole = tilted_pole; test_kw...)
1272+
@test tilted_grid.conformal_mapping.north_pole == tilted_pole
1273+
1274+
@test_throws ArgumentError RotatedLatitudeLongitudeGrid(north_pole=(0, 91); test_kw...)
1275+
@test_throws ArgumentError RotatedLatitudeLongitudeGrid(north_pole=(0, -1); test_kw...)
1276+
end
1277+
12531278
for arch in archs
12541279
for FT in float_types
12551280
z = (0, 1)

0 commit comments

Comments
 (0)