# Same algorithm as KernelAbstractions kernels. The cell list is integer-valued (zero
# derivative), so it is built outside the differentiated function with CUDA.jl's sort.
# The gather and the neighbour loops are differentiated.
# Box size, smoothing length etc. are not differentiated; declaring that lets Params be
# passed to GPU kernels, which reject active (differentiable) scalar arguments.
Enzyme.EnzymeRules.inactive_type(::Type{<:Params}) = true
@kernel function cell_ids!(cell, counts, @Const(x), s, nc)
i = @index(Global)
@inbounds begin
cx = mod(unsafe_trunc(Int32, floor(x[1, i] / s)), Int32(nc))
cy = mod(unsafe_trunc(Int32, floor(x[2, i] / s)), Int32(nc))
cell[i] = Int32(1) + cx + Int32(nc) * cy
KernelAbstractions.@atomic counts[cell[i] + 1] += Int32(1)
end
end
function build_cells(x::CuMatrix{T}, p::Params{T}) where {T}
N, nc = size(x, 2), p.nc
cell = CuVector{Int32}(undef, N)
counts = CUDA.zeros(Int32, nc * nc + 1)
cell_ids!(get_backend(x))(cell, counts, x, p.L / nc, nc; ndrange=N)
counts[1:1] .= 1
perm = sortperm(cell)
return (; perm=Int32.(perm), cell=cell[perm], start=cumsum(counts))
end
@kernel function gather!(xs, @Const(x), @Const(perm))
k = @index(Global)
@inbounds xs[1, k] = x[1, perm[k]]
@inbounds xs[2, k] = x[2, perm[k]]
end
@kernel function density_loss!(e, @Const(xs), @Const(cellk), @Const(start), p)
k = @index(Global)
T, nc = eltype(xs), Int32(p.nc)
@inbounds begin
c = cellk[k] - Int32(1)
cx, cy = c % nc, c ÷ nc
ρ = zero(T)
for dy in Int32(-1):Int32(1), dx in Int32(-1):Int32(1)
cn = Int32(1) + mod(cx + dx, nc) + nc * mod(cy + dy, nc)
for l in start[cn]:start[cn + 1] - Int32(1) # data-dependent range
rx = minimg(xs[1, k] - xs[1, l], p.L)
ry = minimg(xs[2, k] - xs[2, l], p.L)
r2 = rx * rx + ry * ry
if r2 < 4 * p.h * p.h
ρ += p.m * W(sqrt(r2), p.h)
end
end
end
e[k] = (ρ - p.ρ0)^2
end
end
"Per-particle loss terms e (in sorted order); the loss is sum(e)."
function sph_terms!(e, xs, x, cells, p)
backend, N = get_backend(x), size(x, 2)
gather!(backend)(xs, x, cells.perm; ndrange=N)
density_loss!(backend)(e, xs, cells.cell, cells.start, p; ndrange=N)
return nothing
end
function sph_loss(x::CuMatrix{T}, p::Params{T}) where {T}
e, xs = similar(x, size(x, 2)), similar(x)
sph_terms!(e, xs, x, build_cells(x, p), p)
return sum(e)
end