-
Notifications
You must be signed in to change notification settings - Fork 200
/
lagrangian_particle_advection.jl
196 lines (152 loc) · 7.3 KB
/
lagrangian_particle_advection.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
using Oceananigans.Utils: instantiate
using Oceananigans.Models: total_velocities
#####
##### Boundary conditions for Lagrangian particles
#####
# Functions for bouncing particles off walls to the right and left
@inline bounce_left(x, xᴿ, Cʳ) = xᴿ - Cʳ * (x - xᴿ)
@inline bounce_right(x, xᴸ, Cʳ) = xᴸ + Cʳ * (xᴸ - x)
"""
enforce_boundary_conditions(::Bounded, x, xᴸ, xᴿ, Cʳ)
Return a new particle position if the particle position `x`
is outside the Bounded interval `(xᴸ, xᴿ)` by bouncing the particle off
the interval edge with coefficient of restitution `Cʳ).
"""
@inline enforce_boundary_conditions(::Bounded, x, xᴸ, xᴿ, Cʳ) = ifelse(x > xᴿ, bounce_left(x, xᴿ, Cʳ),
ifelse(x < xᴸ, bounce_right(x, xᴸ, Cʳ), x))
"""
enforce_boundary_conditions(::Periodic, x, xᴸ, xᴿ, Cʳ)
Return a new particle position if the particle position `x`
is outside the Periodic interval `(xᴸ, xᴿ)`.
"""
@inline enforce_boundary_conditions(::Periodic, x, xᴸ, xᴿ, Cʳ) = ifelse(x > xᴿ, xᴸ + (x - xᴿ),
ifelse(x < xᴸ, xᴿ - (xᴸ - x), x))
"""
enforce_boundary_conditions(::Flat, x, xᴸ, xᴿ, Cʳ)
Do nothing on Flat dimensions.
"""
@inline enforce_boundary_conditions(::Flat, x, xᴸ, xᴿ, Cʳ) = x
const f = Face()
const c = Center()
"""
immersed_boundary_topology(grid_topology)
Unless `Flat`, immersed boundaries are treated as `Bounded` regardless of underlying grid topology.
"""
immersed_boundary_topology(grid_topology) = ifelse(grid_topology == Flat, Flat(), Bounded())
"""
bounce_immersed_particle((x, y, z), grid, restitution, previous_particle_indices)
Return a new particle position if the position `(x, y, z)` lies in an immersed cell by
bouncing the particle off the immersed boundary with a coefficient or `restitution`.
"""
@inline function bounce_immersed_particle((x, y, z), ibg, restitution, previous_particle_indices)
X = flattened_node((x, y, z), ibg)
# Determine current particle cell
fi, fj, fk = fractional_indices(X, ibg.underlying_grid, c, c, c)
i, j, k = truncate_fractional_indices(fi, fj, fk)
# Determine whether particle was _previously_ in a non-immersed cell
i⁻, j⁻, k⁻ = previous_particle_indices
# Left bounds of the previous cell
xᴿ = ξnode(i⁻ + 1, j⁻ + 1, k⁻ + 1, ibg, f, f, f)
yᴿ = ηnode(i⁻ + 1, j⁻ + 1, k⁻ + 1, ibg, f, f, f)
zᴿ = rnode(i⁻ + 1, j⁻ + 1, k⁻ + 1, ibg, f, f, f)
# Right bounds of the previous cell
xᴸ = ξnode(i⁻, j⁻, k⁻, ibg, f, f, f)
yᴸ = ηnode(i⁻, j⁻, k⁻, ibg, f, f, f)
zᴸ = rnode(i⁻, j⁻, k⁻, ibg, f, f, f)
Cʳ = restitution
tx, ty, tz = map(immersed_boundary_topology, topology(ibg))
xb⁺ = enforce_boundary_conditions(tx, x, xᴸ, xᴿ, Cʳ)
yb⁺ = enforce_boundary_conditions(ty, y, yᴸ, yᴿ, Cʳ)
zb⁺ = enforce_boundary_conditions(tz, z, zᴸ, zᴿ, Cʳ)
immersed = immersed_cell(i, j, k, ibg)
x⁺ = ifelse(immersed, xb⁺, x)
y⁺ = ifelse(immersed, yb⁺, y)
z⁺ = ifelse(immersed, zb⁺, z)
return (x⁺, y⁺, z⁺)
end
"""
rightmost_interface_index(topology, N)
Return the index of the rightmost cell interface for a grid with `topology` and `N` cells.
"""
rightmost_interface_index(::Bounded, N) = N + 1
rightmost_interface_index(::Periodic, N) = N + 1
rightmost_interface_index(::Flat, N) = N
"""
advect_particle((x, y, z), p, restitution, grid, Δt, velocities)
Return new position `(x⁺, y⁺, z⁺)` for a particle at current position (x, y, z),
given `velocities`, time-step `Δt, and coefficient of `restitution`.
"""
@inline function advect_particle((x, y, z), p, restitution, grid, Δt, velocities)
X = flattened_node((x, y, z), grid)
# Obtain current particle indices
fi, fj, fk = fractional_indices(X, grid, c, c, c)
i, j, k = truncate_fractional_indices(fi, fj, fk)
current_particle_indices = (i, j, k)
# Interpolate velocity to particle position
u = interpolate(X, velocities.u, (f, c, c), grid)
v = interpolate(X, velocities.v, (c, f, c), grid)
w = interpolate(X, velocities.w, (c, c, f), grid)
# Advect particles, calculating the advection metric for a curvilinear grid.
# Note that all supported grids use length coordinates in the vertical, so we do not
# transform the vertical velocity nor invoke the k-index.
ξ = x_metric(i, j, grid)
η = y_metric(i, j, grid)
x⁺ = x + ξ * u * Δt
y⁺ = y + η * v * Δt
z⁺ = z + w * Δt
# Satisfy boundary conditions for particles: bounce off walls, travel over periodic boundaries.
tx, ty, tz = map(instantiate, topology(grid))
Nx, Ny, Nz = size(grid)
# Find index of the "rightmost" cell interface
iᴿ = rightmost_interface_index(tx, Nx)
jᴿ = rightmost_interface_index(ty, Ny)
kᴿ = rightmost_interface_index(tz, Nz)
xᴸ = ξnode(1, j, k, grid, f, f, f)
yᴸ = ηnode(i, 1, k, grid, f, f, f)
zᴸ = rnode(i, j, 1, grid, f, f, f)
xᴿ = ξnode(iᴿ, j, k, grid, f, f, f)
yᴿ = ηnode(i, jᴿ, k, grid, f, f, f)
zᴿ = rnode(i, j, kᴿ, grid, f, f, f)
# Enforce boundary conditions for particles.
Cʳ = restitution
x⁺ = enforce_boundary_conditions(tx, x⁺, xᴸ, xᴿ, Cʳ)
y⁺ = enforce_boundary_conditions(ty, y⁺, yᴸ, yᴿ, Cʳ)
z⁺ = enforce_boundary_conditions(tz, z⁺, zᴸ, zᴿ, Cʳ)
if grid isa ImmersedBoundaryGrid
previous_particle_indices = current_particle_indices # particle has been advected
(x⁺, y⁺, z⁺) = bounce_immersed_particle((x⁺, y⁺, z⁺), grid, Cʳ, previous_particle_indices)
end
return (x⁺, y⁺, z⁺)
end
# Calculate the metric for particle advection according to the coordinate system of the `grid`:
# * Unity metric for `RectilinearGrid` / Cartesian coordinates
# * Sphere metric for `LatitudeLongitudeGrid` and geographic coordinates
@inline x_metric(i, j, grid::RectilinearGrid) = 1
@inline x_metric(i, j, grid::LatitudeLongitudeGrid{FT}) where FT = @inbounds 1 / (grid.radius * hack_cosd(grid.φᵃᶜᵃ[j])) * FT(360 / 2π)
@inline x_metric(i, j, grid::ImmersedBoundaryGrid) = x_metric(i, j, grid.underlying_grid)
@inline y_metric(i, j, grid::RectilinearGrid) = 1
@inline y_metric(i, j, grid::LatitudeLongitudeGrid{FT}) where FT = 1 / grid.radius * FT(360 / 2π)
@inline y_metric(i, j, grid::ImmersedBoundaryGrid) = y_metric(i, j, grid.underlying_grid)
@kernel function _advect_particles!(particles, restitution, grid::AbstractGrid, Δt, velocities)
p = @index(Global)
@inbounds begin
x = particles.x[p]
y = particles.y[p]
z = particles.z[p]
end
x⁺, y⁺, z⁺ = advect_particle((x, y, z), p, restitution, grid, Δt, velocities)
@inbounds begin
particles.x[p] = x⁺
particles.y[p] = y⁺
particles.z[p] = z⁺
end
end
function advect_lagrangian_particles!(particles, model, Δt)
grid = model.grid
arch = architecture(grid)
workgroup = min(length(particles), 256)
worksize = length(particles)
advect_particles_kernel! = _advect_particles!(device(arch), workgroup, worksize)
advect_particles_kernel!(particles.properties, particles.restitution, model.grid, Δt, total_velocities(model))
return nothing
end