numerics 0.1.0
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>
40 public:
41 /// @param cell_size Width of one cell (use 2h -- kernel support radius).
42 CellList3D(Scalar cell_size,
43 Scalar xmin,
44 Scalar xmax,
45 Scalar ymin,
46 Scalar ymax,
47 Scalar zmin,
48 Scalar zmax)
49 : cs_(cell_size)
50 , xmin_(xmin)
51 , ymin_(ymin)
52 , zmin_(zmin) {
53 nx_ = static_cast<int>(std::ceil((xmax - xmin) / cs_)) + 2;
54 ny_ = static_cast<int>(std::ceil((ymax - ymin) / cs_)) + 2;
55 nz_ = static_cast<int>(std::ceil((zmax - zmin) / cs_)) + 2;
56 const int total = nx_ * ny_ * nz_;
57 start_.assign(total + 1, 0);
58 count_.assign(total, 0);
59 }
60
61 /// Build in O(n + C).
62 /// PosAccessor: callable int -> std::tuple<Scalar,Scalar,Scalar> (x,y,z)
63 template<typename PosAccessor>
64 void build(PosAccessor&& get_pos, int n) {
65 sorted_.resize(n);
66 const int total = nx_ * ny_ * nz_;
67
68 std::fill(count_.begin(), count_.end(), 0);
69 for (int i = 0; i < n; ++i)
70 ++count_[cell_id_of(get_pos(i))];
71
72 start_[0] = 0;
73 for (int c = 0; c < total; ++c)
74 start_[c + 1] = start_[c] + count_[c];
75
76 std::fill(count_.begin(), count_.end(), 0);
77 for (int i = 0; i < n; ++i) {
78 const int cid = cell_id_of(get_pos(i));
79 sorted_[start_[cid] + count_[cid]] = i;
80 ++count_[cid];
81 }
82 }
83
84 /// 3x3x3 neighbourhood query around (px, py, pz).
85 template<typename F>
86 void query(Scalar px, Scalar py, Scalar pz, F&& f) const {
87 const int cx = cell_x(px);
88 const int cy = cell_y(py);
89 const int cz = cell_z(pz);
90 for (int dz = -1; dz <= 1; ++dz) {
91 const int qz = cz + dz;
92 if (qz < 0 || qz >= nz_)
93 continue;
94 for (int dy = -1; dy <= 1; ++dy) {
95 const int qy = cy + dy;
96 if (qy < 0 || qy >= ny_)
97 continue;
98 for (int dx = -1; dx <= 1; ++dx) {
99 const int qx = cx + dx;
100 if (qx < 0 || qx >= nx_)
101 continue;
102 const int cid = (qz * ny_ + qy) * nx_ + qx;
103 for (int k = start_[cid]; k < start_[cid + 1]; ++k)
104 f(sorted_[k]);
105 }
106 }
107 }
108 }
109
110 /// Newton's 3rd law pair traversal -- 13 forward offsets.
111 /// Calls f(int i, int j) for each unique unordered pair exactly once.
112 template<typename F>
113 void iterate_pairs(F&& f) const {
114 // 13 forward offsets: (dz>0) | (dz==0,dy>0) | (dz==0,dy==0,dx>0)
115 static constexpr int FDX[13] =
116 {-1, 0, 1, -1, 0, 1, -1, 0, 1, -1, 0, 1, 1};
117 static constexpr int FDY[13] =
118 {-1, -1, -1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0};
119 static constexpr int FDZ[13] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0};
120
121 for (int cz = 0; cz < nz_; ++cz) {
122 for (int cy = 0; cy < ny_; ++cy) {
123 for (int cx = 0; cx < nx_; ++cx) {
124 const int cid = (cz * ny_ + cy) * nx_ + cx;
125 const int beg = start_[cid];
126 const int end = start_[cid + 1];
127 if (beg == end)
128 continue;
129
130 // Intra-cell pairs
131 for (int a = beg; a < end; ++a)
132 for (int b = a + 1; b < end; ++b)
133 f(sorted_[a], sorted_[b]);
134
135 // Inter-cell: self x 13 forward neighbours
136 for (int d = 0; d < 13; ++d) {
137 const int ncx = cx + FDX[d];
138 const int ncy = cy + FDY[d];
139 const int ncz = cz + FDZ[d];
140 if (ncx < 0 || ncx >= nx_ || ncy < 0 || ncy >= ny_
141 || ncz < 0 || ncz >= nz_)
142 continue;
143 const int ncid = (ncz * ny_ + ncy) * nx_ + ncx;
144 const int nbeg = start_[ncid];
145 const int nend = start_[ncid + 1];
146 if (nbeg == nend)
147 continue;
148 for (int a = beg; a < end; ++a)
149 for (int b = nbeg; b < nend; ++b)
150 f(sorted_[a], sorted_[b]);
151 }
152 }
153 }
154 }
155 }
156
157 int nx() const noexcept {
158 return nx_;
159 }
160 int ny() const noexcept {
161 return ny_;
162 }
163 int nz() const noexcept {
164 return nz_;
165 }
166 int n_particles() const noexcept {
167 return static_cast<int>(sorted_.size());
168 }
169
170 private:
171 Scalar cs_ = 0, xmin_ = 0, ymin_ = 0, zmin_ = 0;
172 int nx_ = 0, ny_ = 0, nz_ = 0;
173
174 std::vector<int> sorted_, start_, count_;
175
176 int cell_x(Scalar x) const noexcept {
177 const int cx = static_cast<int>(std::floor((x - xmin_) / cs_)) + 1;
178 return cx < 0 ? 0 : (cx >= nx_ ? nx_ - 1 : cx);
179 }
180 int cell_y(Scalar y) const noexcept {
181 const int cy = static_cast<int>(std::floor((y - ymin_) / cs_)) + 1;
182 return cy < 0 ? 0 : (cy >= ny_ ? ny_ - 1 : cy);
183 }
184 int cell_z(Scalar z) const noexcept {
185 const int cz = static_cast<int>(std::floor((z - zmin_) / cs_)) + 1;
186 return cz < 0 ? 0 : (cz >= nz_ ? nz_ - 1 : cz);
187 }
188 int cell_id_of(std::tuple<Scalar, Scalar, Scalar> p) const noexcept {
189 const auto [x, y, z] = p;
190 return (cell_z(z) * ny_ + cell_y(y)) * nx_ + cell_x(x);
191 }
192};
193
194} // 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).