numerics
Loading...
Searching...
No Matches
cell_list_3d.hpp
Go to the documentation of this file.
1/// @file cell_list_3d.hpp
2/// @brief Cache-coherent 3D cell list for O(1) amortized neighbour queries
3///
4/// Direct extension of CellList2D to three dimensions.
5///
6/// @par Algorithm
7///
8/// build() -- O(n + C) counting sort over C = nx*ny*nz cells
9/// query() -- O(k) sequential reads over 3x3x3 = 27 cells
10///
11/// iterate_pairs() -- forward half-shell with 13 offsets
12/// In 3D there are 26 neighbour directions (3^3-1). The 13 "forward"
13/// offsets are chosen so that (A,B) is visited exactly once:
14///
15/// forward = (dz > 0)
16/// OR (dz == 0 AND dy > 0)
17/// OR (dz == 0 AND dy == 0 AND dx > 0)
18///
19/// This partitions all 26 directed edges into 13 unique unordered pairs.
20/// The 13 offsets:
21/// dz=1 layer (all 9): (-1,-1,1)(0,-1,1)(1,-1,1)
22/// (-1, 0,1)(0, 0,1)(1, 0,1)
23/// (-1, 1,1)(0, 1,1)(1, 1,1)
24/// dz=0, dy=1 (3): (-1,1,0) (0,1,0) (1,1,0)
25/// dz=0, dy=0, dx=1 (1): (1,0,0)
26///
27/// @tparam Scalar float or double
28#pragma once
29
30#include <vector>
31#include <cmath>
32#include <algorithm>
33#include <cassert>
34#include <tuple>
35
36namespace num {
37
38template<typename Scalar>
40public:
41 /// @param cell_size Width of one cell (use 2h -- kernel support radius).
43 Scalar xmin, Scalar xmax,
44 Scalar ymin, Scalar ymax,
45 Scalar zmin, Scalar zmax)
46 : cs_(cell_size)
47 , xmin_(xmin), ymin_(ymin), zmin_(zmin)
48 {
49 nx_ = static_cast<int>(std::ceil((xmax - xmin) / cs_)) + 2;
50 ny_ = static_cast<int>(std::ceil((ymax - ymin) / cs_)) + 2;
51 nz_ = static_cast<int>(std::ceil((zmax - zmin) / cs_)) + 2;
52 const int total = nx_ * ny_ * nz_;
53 start_.assign(total + 1, 0);
54 count_.assign(total, 0);
55 }
56
57 /// Build in O(n + C).
58 /// PosAccessor: callable int -> std::tuple<Scalar,Scalar,Scalar> (x,y,z)
59 template<typename PosAccessor>
60 void build(PosAccessor&& get_pos, int n) {
61 sorted_.resize(n);
62 const int total = nx_ * ny_ * nz_;
63
64 std::fill(count_.begin(), count_.end(), 0);
65 for (int i = 0; i < n; ++i)
66 ++count_[cell_id_of(get_pos(i))];
67
68 start_[0] = 0;
69 for (int c = 0; c < total; ++c)
70 start_[c + 1] = start_[c] + count_[c];
71
72 std::fill(count_.begin(), count_.end(), 0);
73 for (int i = 0; i < n; ++i) {
74 const int cid = cell_id_of(get_pos(i));
75 sorted_[start_[cid] + count_[cid]] = i;
76 ++count_[cid];
77 }
78 }
79
80 /// 3x3x3 neighbourhood query around (px, py, pz).
81 template<typename F>
82 void query(Scalar px, Scalar py, Scalar pz, F&& f) const {
83 const int cx = cell_x(px);
84 const int cy = cell_y(py);
85 const int cz = cell_z(pz);
86 for (int dz = -1; dz <= 1; ++dz) {
87 const int qz = cz + dz;
88 if (qz < 0 || qz >= nz_) continue;
89 for (int dy = -1; dy <= 1; ++dy) {
90 const int qy = cy + dy;
91 if (qy < 0 || qy >= ny_) continue;
92 for (int dx = -1; dx <= 1; ++dx) {
93 const int qx = cx + dx;
94 if (qx < 0 || qx >= nx_) continue;
95 const int cid = (qz * ny_ + qy) * nx_ + qx;
96 for (int k = start_[cid]; k < start_[cid + 1]; ++k)
97 f(sorted_[k]);
98 }
99 }
100 }
101 }
102
103 /// Newton's 3rd law pair traversal -- 13 forward offsets.
104 /// Calls f(int i, int j) for each unique unordered pair exactly once.
105 template<typename F>
106 void iterate_pairs(F&& f) const {
107 // 13 forward offsets: (dz>0) | (dz==0,dy>0) | (dz==0,dy==0,dx>0)
108 static constexpr int FDX[13] = {-1, 0, 1, -1, 0, 1, -1, 0, 1, -1, 0, 1, 1};
109 static constexpr int FDY[13] = {-1,-1,-1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0};
110 static constexpr int FDZ[13] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0};
111
112 for (int cz = 0; cz < nz_; ++cz) {
113 for (int cy = 0; cy < ny_; ++cy) {
114 for (int cx = 0; cx < nx_; ++cx) {
115 const int cid = (cz * ny_ + cy) * nx_ + cx;
116 const int beg = start_[cid];
117 const int end = start_[cid + 1];
118 if (beg == end) continue;
119
120 // Intra-cell pairs
121 for (int a = beg; a < end; ++a)
122 for (int b = a + 1; b < end; ++b)
123 f(sorted_[a], sorted_[b]);
124
125 // Inter-cell: self x 13 forward neighbours
126 for (int d = 0; d < 13; ++d) {
127 const int ncx = cx + FDX[d];
128 const int ncy = cy + FDY[d];
129 const int ncz = cz + FDZ[d];
130 if (ncx < 0 || ncx >= nx_ ||
131 ncy < 0 || ncy >= ny_ ||
132 ncz < 0 || ncz >= nz_) continue;
133 const int ncid = (ncz * ny_ + ncy) * nx_ + ncx;
134 const int nbeg = start_[ncid];
135 const int nend = start_[ncid + 1];
136 if (nbeg == nend) continue;
137 for (int a = beg; a < end; ++a)
138 for (int b = nbeg; b < nend; ++b)
139 f(sorted_[a], sorted_[b]);
140 }
141 }
142 }
143 }
144 }
145
146 int nx() const noexcept { return nx_; }
147 int ny() const noexcept { return ny_; }
148 int nz() const noexcept { return nz_; }
149 int n_particles() const noexcept { return static_cast<int>(sorted_.size()); }
150
151private:
152 Scalar cs_, xmin_, ymin_, zmin_;
153 int nx_, ny_, nz_;
154
155 std::vector<int> sorted_, start_, count_;
156
157 int cell_x(Scalar x) const noexcept {
158 const int cx = static_cast<int>(std::floor((x - xmin_) / cs_)) + 1;
159 return cx < 0 ? 0 : (cx >= nx_ ? nx_ - 1 : cx);
160 }
161 int cell_y(Scalar y) const noexcept {
162 const int cy = static_cast<int>(std::floor((y - ymin_) / cs_)) + 1;
163 return cy < 0 ? 0 : (cy >= ny_ ? ny_ - 1 : cy);
164 }
165 int cell_z(Scalar z) const noexcept {
166 const int cz = static_cast<int>(std::floor((z - zmin_) / cs_)) + 1;
167 return cz < 0 ? 0 : (cz >= nz_ ? nz_ - 1 : cz);
168 }
169 int cell_id_of(std::tuple<Scalar, Scalar, Scalar> p) const noexcept {
170 const auto [x, y, z] = p;
171 return (cell_z(z) * ny_ + cell_y(y)) * nx_ + cell_x(x);
172 }
173};
174
175} // namespace num
void iterate_pairs(F &&f) const
int n_particles() const noexcept
int nx() const noexcept
void build(PosAccessor &&get_pos, int n)
CellList3D(Scalar cell_size, Scalar xmin, Scalar xmax, Scalar ymin, Scalar ymax, Scalar zmin, Scalar zmax)
int nz() const noexcept
int ny() const noexcept
void query(Scalar px, Scalar py, Scalar pz, F &&f) const
3x3x3 neighbourhood query around (px, py, pz).
constexpr T ipow(T x) noexcept
Compute x^N at compile time via repeated squaring.