Algorithm
August 17, 2026 ยท View on GitHub
Linked-cell k-nearest search in src/knearest.rs. Allen and
Tildesley linked cells, a k-heap per source, Chebyshev shells until
the k-th neighbour cannot lie outside the visited cube. The search
takes no cutoff.
Inputs
xyz: Cartesian points.simbox: periodic parallelepiped (Cell/lc_cell).k: neighbours per source. Must be at least 1.mask: optional.false/ zero drops a point as source and as candidate.cell_hint: target bin edge.Noneor<= 0uses 3.0, then the value is clamped to the smallest perpendicular box width.
Each source keeps min(k, n_active - 1) neighbours. Packed outputs
fill unused slots with -1.
Fold and bin
- Take fractional coordinates in
[0, 1)(Cell::fractional). - Store the Cartesian image in the primary cell (
Cell::cartesian). - Bin in fractional space:
floor(s * n*), clamped to[0, n* - 1]. - Chain each active point onto a linked list per bin.
nx, ny, nz are floor(width / edge), at least 1. cell_min is the
smallest of the three actual cell edges (perpendicular width over
count). Orthorhombic boxes wrap each lattice direction independently;
a sheared box uses Hinv only for this fold.
Shells
For each source, walk integer cell offsets (dx, dy, dz) in
Chebyshev shells. Shell reach is the surface
max(|dx|, |dy|, |dz|) == reach (reach == 1 also visits the home
cell). max_reach is max(nx, ny, nz) / 2 + 1.
Each offset maps to:
- a primary bin,
rem_euclidon the cell indices - a lattice translation,
div_euclidcounts throughCell::lattice_shift
The pair distance is Cell::dist2_shifted: Cartesian subtract of the
folded points plus that shift. The inner loop does not wrap with
Hinv.
The same primary bin can appear under more than one wrap. Each wrap is a separate visit. Skipping those repeats (one shift per unique bin) misses images. That construction, and the ortho cheap path, is in MIC and cells.
Heap and stop
A max-heap of size k stores (dist2, index). For k <= 16 it
lives on the stack. After each shell, if the heap is full and the
worst dist2 is at most (reach * cell_min)^2, no unvisited point
can beat the k-th neighbour, and the walk stops.
knearest returns Neighbors rows (indices, dist2), nearest
first. knearest_into / lc_knearest write packed indices.
knearest_into_d2 / lc_knearest_d2 also write squared distances.
knearest_into_many / lc_knearest_many cover a frame-major batch.
knearest_brute is the all-pairs check used by tests and small
systems. It calls Cell::dist2 per pair (ortho: three wraps;
general: fractional wrap). It is not the production walk.
Parallel
The parallel Cargo feature (on by default) maps sources with
rayon. Each source owns its heap.
Device
linkcell::gpu::Workspace is the same walk on a CUDA device. Fold,
bin, then a tiled Hillis-Steele exclusive scan (CUB DeviceScan /
HOOMD cell offsets). Occupants are stored in cell-major order with
an O(1) home slot. The stencil is a precomputed Chebyshev shell
table (LAMMPS NStencil, HOOMD d_cell_adj), not a nested 3-D
loop. Eight threads share each source and stride occupants; after
each shell they merge heaps and apply the host stop. Output is
Cabana's 2-D packed n * k list. knearest_into_many covers every
frame that shares a cell. Fold uses Hinv; the pair shift is
na a + nb b + nc c. k <= 16.