Skip to content

Commit 9cc7112

Browse files
Fix bug in advect_particle (#3923)
* this should work * remove all the @show * restart tests * will this work? * test more than one position * bugfix * this should work * remove unused truncate * new interpolate * I think this works * try option 2 * no need for flip anymore * fix gpu tests * change comment and restore docstring * fix the interpolate * add (nothing nothing nothing) interpolate * do not reuse names * bugfix * change ᵃ to ᶜ * back to previous code * add the :a in the metrics
1 parent 504ab5d commit 9cc7112

File tree

5 files changed

+95
-56
lines changed

5 files changed

+95
-56
lines changed

src/Fields/interpolate.jl

Lines changed: 21 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Oceananigans.Grids: topology, node, _node,
1+
using Oceananigans.Grids: topology, node, _node, φnode, λnode,
22
xspacings, yspacings, zspacings, λspacings, φspacings,
33
XFlatGrid, YFlatGrid, ZFlatGrid,
44
XYFlatGrid, YZFlatGrid, XZFlatGrid,
@@ -7,6 +7,8 @@ using Oceananigans.Grids: topology, node, _node,
77
ZRegOrthogonalSphericalShellGrid,
88
RectilinearGrid, LatitudeLongitudeGrid
99

10+
using Oceananigans.Operators: Δx, Δy, Δz
11+
1012
using Oceananigans.Architectures: child_architecture
1113

1214
# GPU-compatile middle point calculation
@@ -66,64 +68,64 @@ end
6668

6769
@inline function fractional_x_index(x, locs, grid::XRegularRG)
6870
x₀ = xnode(1, 1, 1, grid, locs...)
69-
Δx = @inbounds first(xspacings(grid, locs...))
71+
dx = Δx(1, 1, 1, grid, locs...)
7072
FT = eltype(grid)
71-
return convert(FT, (x - x₀) / Δx)
73+
return convert(FT, (x - x₀) / dx) + 1 # 1 - based indexing
7274
end
7375

7476
@inline function fractional_x_index(λ, locs, grid::XRegularLLG)
7577
λ₀ = λnode(1, 1, 1, grid, locs...)
76-
Δλ = @inbounds first(λspacings(grid, locs...))
78+
λ₁ = λnode(2, 1, 1, grid, locs...)
7779
FT = eltype(grid)
78-
return convert(FT, (λ - λ₀) / Δλ)
80+
return convert(FT, (λ - λ₀) / (λ₁ - λ₀)) + 1 # 1 - based indexing
7981
end
8082

8183
@inline function fractional_x_index(x, locs, grid::RectilinearGrid)
8284
loc = @inbounds locs[1]
8385
Tx = topology(grid, 1)()
8486
Nx = length(loc, Tx, grid.Nx)
8587
xn = xnodes(grid, locs...)
86-
return fractional_index(x, xn, Nx) - 1
88+
return fractional_index(x, xn, Nx)
8789
end
8890

8991
@inline function fractional_x_index(x, locs, grid::LatitudeLongitudeGrid)
9092
loc = @inbounds locs[1]
9193
Tx = topology(grid, 1)()
9294
Nx = length(loc, Tx, grid.Nx)
9395
xn = λnodes(grid, locs...)
94-
return fractional_index(x, xn, Nx) - 1
96+
return fractional_index(x, xn, Nx)
9597
end
9698

9799
@inline fractional_y_index(y, locs, grid::YFlatGrid) = zero(grid)
98100

99101
@inline function fractional_y_index(y, locs, grid::YRegularRG)
100102
y₀ = ynode(1, 1, 1, grid, locs...)
101-
Δy = @inbounds first(yspacings(grid, locs...))
103+
dy = Δy(1, 1, 1, grid, locs...)
102104
FT = eltype(grid)
103-
return convert(FT, (y - y₀) / Δy)
105+
return convert(FT, (y - y₀) / dy) + 1 # 1 - based indexing
104106
end
105107

106108
@inline function fractional_y_index(φ, locs, grid::YRegularLLG)
107-
φ₀ = φnode(1, 1, 1, grid, locs...)
108-
Δφ = @inbounds first(φspacings(grid, locs...))
109+
φ₀ = φnode(1, 1, 1, grid, locs...)
110+
φ₁ = φnode(1, 2, 1, grid, locs...)
109111
FT = eltype(grid)
110-
return convert(FT, (φ - φ₀) / Δφ)
112+
return convert(FT, (φ - φ₀) / (φ₁ - φ₀)) + 1 # 1 - based indexing
111113
end
112114

113115
@inline function fractional_y_index(y, locs, grid::RectilinearGrid)
114116
loc = @inbounds locs[2]
115117
Ty = topology(grid, 2)()
116118
Ny = length(loc, Ty, grid.Ny)
117119
yn = ynodes(grid, locs...)
118-
return fractional_index(y, yn, Ny) - 1
120+
return fractional_index(y, yn, Ny)
119121
end
120122

121123
@inline function fractional_y_index(y, locs, grid::LatitudeLongitudeGrid)
122124
loc = @inbounds locs[2]
123125
Ty = topology(grid, 2)()
124126
Ny = length(loc, Ty, grid.Ny)
125127
yn = φnodes(grid, locs...)
126-
return fractional_index(y, yn, Ny) - 1
128+
return fractional_index(y, yn, Ny)
127129
end
128130

129131
@inline fractional_z_index(z, locs, grid::ZFlatGrid) = zero(grid)
@@ -132,16 +134,16 @@ ZRegGrid = Union{ZRegularRG, ZRegularLLG, ZRegOrthogonalSphericalShellGrid}
132134

133135
@inline function fractional_z_index(z::FT, locs, grid::ZRegGrid) where FT
134136
z₀ = znode(1, 1, 1, grid, locs...)
135-
Δz = @inbounds first(zspacings(grid, locs...))
136-
return convert(FT, (z - z₀) / Δz)
137+
dz = Δz(1, 1, 1, grid, locs...)
138+
return convert(FT, (z - z₀) / dz) + 1 # 1 - based indexing
137139
end
138140

139141
@inline function fractional_z_index(z, locs, grid)
140142
loc = @inbounds locs[3]
141143
Tz = topology(grid, 3)()
142144
Nz = length(loc, Tz, grid.Nz)
143145
zn = znodes(grid, loc)
144-
return fractional_index(z, zn, Nz) - 1
146+
return fractional_index(z, zn, Nz)
145147
end
146148

147149
"""
@@ -210,22 +212,7 @@ end
210212
return (ii, jj, kk)
211213
end
212214

213-
"""
214-
truncate_fractional_indices(fi, fj, fk)
215-
216-
Truncate _fractional_ indices output from fractional indices `fi, fj, fk` to integer indices, dealing
217-
with `nothing` indices for `Flat` domains.
218-
"""
219-
@inline function truncate_fractional_indices(fi, fj, fk)
220-
i = truncate_fractional_index(fi)
221-
j = truncate_fractional_index(fj)
222-
k = truncate_fractional_index(fk)
223-
return (i, j, k)
224-
end
225-
226-
@inline truncate_fractional_index(::Nothing) = 1
227-
@inline truncate_fractional_index(fi) = Base.unsafe_trunc(Int, fi)
228-
215+
@inline _fractional_indices(at_node, grid, ::Nothing, ::Nothing, ::Nothing) = (nothing, nothing, nothing)
229216

230217
"""
231218
interpolate(at_node, from_field, from_loc, from_grid)
@@ -260,15 +247,12 @@ right of `i`, and `ξ` is the fractional distance between `i` and the
260247
left bound `i⁻`, such that `ξ ∈ [0, 1)`.
261248
"""
262249
@inline function interpolator(fractional_idx)
263-
# We use mod and trunc as CUDA.modf is not defined.
264250
# For why we use Base.unsafe_trunc instead of trunc see:
265251
# https://github.com/CliMA/Oceananigans.jl/issues/828
266252
# https://github.com/CliMA/Oceananigans.jl/pull/997
267253

268254
i⁻ = Base.unsafe_trunc(Int, fractional_idx)
269-
i⁻ = Int(i⁻ + 1) # convert to "proper" integer?
270-
shift = Int(sign(fractional_idx))
271-
i⁺ = i⁻ + shift
255+
i⁺ = i⁻ + 1
272256
ξ = mod(fractional_idx, 1)
273257

274258
return (i⁻, i⁺, ξ)

src/Models/LagrangianParticleTracking/LagrangianParticleTracking.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ using Oceananigans.Grids: XYFlatGrid, YZFlatGrid, XZFlatGrid
1717
using Oceananigans.ImmersedBoundaries: immersed_cell
1818
using Oceananigans.Architectures: device, architecture
1919
using Oceananigans.Fields: interpolate, datatuple, compute!, location
20-
using Oceananigans.Fields: fractional_indices, truncate_fractional_indices
20+
using Oceananigans.Fields: fractional_indices
2121
using Oceananigans.TimeSteppers: AbstractLagrangianParticles
2222
using Oceananigans.Utils: prettysummary, launch!, SumOfArrays
2323

src/Models/LagrangianParticleTracking/lagrangian_particle_advection.jl

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Oceananigans.Utils: instantiate, KernelParameters
22
using Oceananigans.Models: total_velocities
3+
using Oceananigans.Fields: interpolator
34

45
#####
56
##### Boundary conditions for Lagrangian particles
@@ -54,30 +55,35 @@ bouncing the particle off the immersed boundary with a coefficient or `restituti
5455
@inline function bounce_immersed_particle((x, y, z), ibg, restitution, previous_particle_indices)
5556
X = flattened_node((x, y, z), ibg)
5657

57-
# Determine current particle cell
58-
fi, fj, fk = fractional_indices(X, ibg.underlying_grid, c, c, c)
59-
i, j, k = truncate_fractional_indices(fi, fj, fk)
58+
# Determine current particle cell from the interfaces
59+
fi, fj, fk = fractional_indices(X, ibg.underlying_grid, f, f, f)
60+
61+
i, i⁺, _ = interpolator(fi)
62+
j, j⁺, _ = interpolator(fj)
63+
k, k⁺, _ = interpolator(fk)
6064

6165
# Determine whether particle was _previously_ in a non-immersed cell
6266
i⁻, j⁻, k⁻ = previous_particle_indices
6367

64-
# Left bounds of the previous cell
65-
xᴿ = ξnode(i⁻ + 1, j⁻ + 1, k⁻ + 1, ibg, f, f, f)
66-
yᴿ = ηnode(i⁻ + 1, j⁻ + 1, k⁻ + 1, ibg, f, f, f)
67-
zᴿ = rnode(i⁻ + 1, j⁻ + 1, k⁻ + 1, ibg, f, f, f)
68+
tx, ty, tz = map(immersed_boundary_topology, topology(ibg))
6869

6970
# Right bounds of the previous cell
71+
xᴿ = ξnode(i⁺, j, k, ibg, f, f, f)
72+
yᴿ = ηnode(i, j⁺, k, ibg, f, f, f)
73+
zᴿ = rnode(i, j, k⁺, ibg, f, f, f)
74+
75+
# Left bounds of the previous cell
7076
xᴸ = ξnode(i⁻, j⁻, k⁻, ibg, f, f, f)
7177
yᴸ = ηnode(i⁻, j⁻, k⁻, ibg, f, f, f)
7278
zᴸ = rnode(i⁻, j⁻, k⁻, ibg, f, f, f)
7379

7480
= restitution
75-
tx, ty, tz = map(immersed_boundary_topology, topology(ibg))
81+
7682
xb⁺ = enforce_boundary_conditions(tx, x, xᴸ, xᴿ, Cʳ)
7783
yb⁺ = enforce_boundary_conditions(ty, y, yᴸ, yᴿ, Cʳ)
7884
zb⁺ = enforce_boundary_conditions(tz, z, zᴸ, zᴿ, Cʳ)
7985

80-
immersed = immersed_cell(i, j, k, ibg)
86+
immersed = immersed_cell(i, j, k, ibg)
8187
x⁺ = ifelse(immersed, xb⁺, x)
8288
y⁺ = ifelse(immersed, yb⁺, y)
8389
z⁺ = ifelse(immersed, zb⁺, z)
@@ -90,7 +96,7 @@ end
9096
9197
Return the index of the rightmost cell interface for a grid with `topology` and `N` cells.
9298
"""
93-
rightmost_interface_index(::Bounded, N) = N + 1
99+
rightmost_interface_index(::Bounded, N) = N + 1
94100
rightmost_interface_index(::Periodic, N) = N + 1
95101
rightmost_interface_index(::Flat, N) = N
96102

@@ -103,9 +109,12 @@ given `velocities`, time-step `Δt, and coefficient of `restitution`.
103109
@inline function advect_particle((x, y, z), p, restitution, grid, Δt, velocities)
104110
X = flattened_node((x, y, z), grid)
105111

106-
# Obtain current particle indices
107-
fi, fj, fk = fractional_indices(X, grid, c, c, c)
108-
i, j, k = truncate_fractional_indices(fi, fj, fk)
112+
# Obtain current particle indices, looking at the interfaces
113+
fi, fj, fk = fractional_indices(X, grid, f, f, f)
114+
115+
i, i⁺, _ = interpolator(fi)
116+
j, j⁺, _ = interpolator(fj)
117+
k, k⁺, _ = interpolator(fk)
109118

110119
current_particle_indices = (i, j, k)
111120

@@ -137,15 +146,16 @@ given `velocities`, time-step `Δt, and coefficient of `restitution`.
137146
yᴸ = ηnode(i, 1, k, grid, f, f, f)
138147
zᴸ = rnode(i, j, 1, grid, f, f, f)
139148

140-
xᴿ = ξnode(iᴿ, j, k, grid, f, f, f)
141-
yᴿ = ηnode(i, jᴿ, k, grid, f, f, f)
142-
zᴿ = rnode(i, j, kᴿ, grid, f, f, f)
149+
xᴿ = ξnode(iᴿ, j, k, grid, f, f, f)
150+
yᴿ = ηnode(i, jᴿ, k, grid, f, f, f)
151+
zᴿ = rnode(i, j, kᴿ, grid, f, f, f)
143152

144153
# Enforce boundary conditions for particles.
145154
= restitution
146155
x⁺ = enforce_boundary_conditions(tx, x⁺, xᴸ, xᴿ, Cʳ)
147156
y⁺ = enforce_boundary_conditions(ty, y⁺, yᴸ, yᴿ, Cʳ)
148157
z⁺ = enforce_boundary_conditions(tz, z⁺, zᴸ, zᴿ, Cʳ)
158+
149159
if grid isa ImmersedBoundaryGrid
150160
previous_particle_indices = current_particle_indices # particle has been advected
151161
(x⁺, y⁺, z⁺) = bounce_immersed_particle((x⁺, y⁺, z⁺), grid, Cʳ, previous_particle_indices)

src/Operators/spacings_and_areas_and_volumes.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ The operators in this file fall into three categories:
3636
#####
3737

3838
# Convenience Functions for all grids
39-
for LX in (:ᶜ, :ᶠ), LY in (:ᶜ, :ᶠ)
39+
for LX in (:ᶜ, :ᶠ, :ᵃ), LY in (:ᶜ, :ᶠ, :ᵃ)
4040

4141
x_spacing_1D = Symbol(:Δx, LX, :ᵃ, :ᵃ)
4242
x_spacing_2D = Symbol(:Δx, LX, LY, :ᵃ)
@@ -197,7 +197,7 @@ end
197197
#####
198198
#####
199199

200-
for LX in (:ᶜ, :ᶠ), LY in (:ᶜ, :ᶠ)
200+
for LX in (:ᶜ, :ᶠ, :ᵃ), LY in (:ᶜ, :ᶠ, :ᵃ)
201201

202202
x_spacing_2D = Symbol(:Δx, LX, LY, :ᵃ)
203203
y_spacing_2D = Symbol(:Δy, LX, LY, :ᵃ)

test/test_field.jl

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ using Oceananigans.Grids: total_length
66
using Oceananigans.Fields: ReducedField, has_velocities
77
using Oceananigans.Fields: VelocityFields, TracerFields, interpolate, interpolate!
88
using Oceananigans.Fields: reduced_location
9+
using Oceananigans.Fields: fractional_indices, interpolator
10+
using Oceananigans.Grids: ξnode, ηnode, rnode
11+
12+
using Random
13+
using CUDA: @allowscalar
914

1015
"""
1116
correct_field_size(grid, FieldType, Tx, Ty, Tz)
@@ -404,6 +409,46 @@ end
404409
end
405410
end
406411

412+
@testset "Unit interpolation" begin
413+
for arch in archs
414+
hu = (-1, 1)
415+
hs = range(-1, 1, length=21)
416+
zu = (-100, 0)
417+
zs = range(-100, 0, length=33)
418+
419+
for latitude in (hu, hs), longitude in (hu, hs), z in (zu, zs), loc in (Center(), Face())
420+
@info " Testing interpolation for $(latitude) latitude and longitude, $(z) z on $(typeof(loc))s..."
421+
grid = LatitudeLongitudeGrid(arch; size = (20, 20, 32), longitude, latitude, z, halo = (5, 5, 5))
422+
423+
# Test random positions,
424+
# set seed for reproducibility
425+
Random.seed!(1234)
426+
Xs = [(2rand()-1, 2rand()-1, -100rand()) for p in 1:20]
427+
428+
for X in Xs
429+
(x, y, z) = X
430+
fi, fj, fk = @allowscalar fractional_indices(X, grid, loc, loc, loc)
431+
432+
i⁻, i⁺, _ = interpolator(fi)
433+
j⁻, j⁺, _ = interpolator(fj)
434+
k⁻, k⁺, _ = interpolator(fk)
435+
436+
x⁻ = @allowscalar ξnode(i⁻, j⁻, k⁻, grid, loc, loc, loc)
437+
y⁻ = @allowscalar ηnode(i⁻, j⁻, k⁻, grid, loc, loc, loc)
438+
z⁻ = @allowscalar rnode(i⁻, j⁻, k⁻, grid, loc, loc, loc)
439+
440+
x⁺ = @allowscalar ξnode(i⁺, j⁺, k⁺, grid, loc, loc, loc)
441+
y⁺ = @allowscalar ηnode(i⁺, j⁺, k⁺, grid, loc, loc, loc)
442+
z⁺ = @allowscalar rnode(i⁺, j⁺, k⁺, grid, loc, loc, loc)
443+
444+
@test x⁻ x x⁺
445+
@test y⁻ y y⁺
446+
@test z⁻ z z⁺
447+
end
448+
end
449+
end
450+
end
451+
407452
@testset "Field interpolation" begin
408453
@info " Testing field interpolation..."
409454

0 commit comments

Comments
 (0)