core
Shared MPI block decomposition + asynchronous ghost-layer exchange (header-only C++20)
Loading...
Searching...
No Matches
barnes_hut.hpp
Go to the documentation of this file.
1// core — Barnes–Hut tree over a BlockOctree (particle-tree mode).
2//
3// The same per-block octree that carries an AMR grid doubles as a general
4// adaptive spatial tree. Here it is built top-down by particle insertion (refine
5// any leaf holding more than one particle) over a single root cube (root brick
6// 1x1x1), with per-node centre-of-mass / total-mass aggregates accumulated up the
7// Morton hierarchy. A standard theta-criterion traversal then evaluates an
8// approximate 1/r^2 (softened) interaction in O(N log N). This is the primitive a
9// PBD / N-body consumer (e.g. dem) would use; theta = 0 recurses fully and
10// reproduces the direct O(N^2) sum exactly.
11//
12// Header-only, guarded by PECLET_CORE_HAVE_MORTON. Serial/host (the device traversal is a
13// follow-up; the aggregates + leaf arrays are already device-friendly).
14#ifndef PECLET_CORE_AMR_BARNES_HUT_HPP
15#define PECLET_CORE_AMR_BARNES_HUT_HPP
16
17#ifdef PECLET_CORE_HAVE_MORTON
18
19#include <algorithm>
20#include <array>
21#include <cmath>
22#include <map>
23#include <vector>
24
28
29namespace peclet::core::amr {
30
31template <int Dim = 3, unsigned Bits = (Dim == 2 ? 32u : (Dim == 3 ? 21u : 16u))>
32class BarnesHut {
33 public:
35 using M = typename Octree::M;
36 using Code = typename Octree::Code;
37 using Coord = typename Octree::Coord;
38
41 void build(const std::vector<Vec<Dim>>& pos, const std::vector<double>& mass,
42 const AmrGeometry<Dim>& geo, unsigned lmax, double theta, double soft = 1e-3) {
43 pos_ = pos;
44 mass_ = mass;
45 geo_ = geo;
46 lmax_ = lmax;
47 theta_ = theta;
48 soft2_ = soft * soft;
49 fineExt_ = Coord(Coord(1) << lmax_);
50 octree_.init(IVec<Dim>{makeBrick()}, lmax_);
51
52 // Top-down: refine any leaf holding more than one particle (until level 0).
53 for (;;) {
54 std::vector<int> cnt(static_cast<std::size_t>(octree_.numLeaves()), 0);
55 for (std::size_t p = 0; p < pos_.size(); ++p)
56 ++cnt[static_cast<std::size_t>(leafOf(p))];
57 std::vector<Code> split;
58 for (Index i = 0; i < octree_.numLeaves(); ++i)
59 if (cnt[static_cast<std::size_t>(i)] > 1 && octree_.level(i) > 0)
60 split.push_back(octree_.code(i));
61 if (split.empty())
62 break;
63 std::sort(split.begin(), split.end());
64 octree_.refineIf(
65 [&](Code c, unsigned) { return std::binary_search(split.begin(), split.end(), c); });
66 }
67
68 // Leaf -> particle CSR.
69 const Index nleaf = octree_.numLeaves();
70 leafStart_.assign(static_cast<std::size_t>(nleaf) + 1, 0);
71 std::vector<Index> ofLeaf(pos_.size());
72 for (std::size_t p = 0; p < pos_.size(); ++p) {
73 Index li = leafOf(p);
74 ofLeaf[p] = li;
75 ++leafStart_[static_cast<std::size_t>(li) + 1];
76 }
77 for (Index i = 0; i < nleaf; ++i)
78 leafStart_[static_cast<std::size_t>(i) + 1] += leafStart_[static_cast<std::size_t>(i)];
79 leafParts_.assign(pos_.size(), 0);
80 std::vector<Index> cur(leafStart_.begin(), leafStart_.end() - 1);
81 for (std::size_t p = 0; p < pos_.size(); ++p) {
82 Index li = ofLeaf[p];
83 leafParts_[static_cast<std::size_t>(cur[static_cast<std::size_t>(li)]++)] =
84 static_cast<Index>(p);
85 }
86
87 // Node aggregates (mass + mass-weighted position) for every ancestor level.
88 agg_.assign(lmax_ + 1, {});
89 for (std::size_t p = 0; p < pos_.size(); ++p) {
90 Index li = ofLeaf[p];
91 Code lo = octree_.code(li);
92 unsigned Ll = octree_.level(li);
93 for (unsigned L = Ll; L <= lmax_; ++L) {
94 Code anc = M::from_code(lo).ancestor(L).code();
95 Agg& a = agg_[L][anc];
96 a.m += mass_[p];
97 for (int d = 0; d < Dim; ++d)
98 a.com[d] += mass_[p] * pos_[p][d];
99 }
100 }
101 }
102
105 Vec<Dim> a{};
106 walk(lmax_, Code(0), pi, a);
107 return a;
108 }
109
110 std::vector<Vec<Dim>> accelerations() const {
111 std::vector<Vec<Dim>> a(pos_.size());
112 for (std::size_t p = 0; p < pos_.size(); ++p)
113 a[p] = acceleration(static_cast<Index>(p));
114 return a;
115 }
116
119 Vec<Dim> a{};
120 for (std::size_t q = 0; q < pos_.size(); ++q)
121 if (static_cast<Index>(q) != pi)
122 addPair(pos_[static_cast<std::size_t>(pi)], pos_[q], mass_[q], a);
123 return a;
124 }
125
126 const Octree& octree() const { return octree_; }
127
128 private:
129 struct Agg {
130 double m = 0.0;
131 std::array<double, Dim> com{}; // stores mass-weighted sum until normalized at use
132 };
133
134 std::array<Index, Dim> makeBrick() const {
135 std::array<Index, Dim> b{};
136 for (int d = 0; d < Dim; ++d)
137 b[d] = 1;
138 return b;
139 }
140
141 std::array<Coord, Dim> fineCoord(std::size_t p) const {
142 std::array<Coord, Dim> c{};
143 for (int d = 0; d < Dim; ++d) {
144 long v = static_cast<long>(std::floor((pos_[p][d] - geo_.origin[d]) / geo_.h0));
145 if (v < 0)
146 v = 0;
147 if (v >= static_cast<long>(fineExt_))
148 v = static_cast<long>(fineExt_) - 1;
149 c[d] = static_cast<Coord>(v);
150 }
151 return c;
152 }
153 Index leafOf(std::size_t p) const { return octree_.find(M::encode(fineCoord(p)).code()); }
154
155 void addPair(const Vec<Dim>& x, const Vec<Dim>& y, double m, Vec<Dim>& a) const {
156 double r2 = soft2_;
157 Vec<Dim> dir{};
158 for (int d = 0; d < Dim; ++d) {
159 dir[d] = y[d] - x[d];
160 r2 += dir[d] * dir[d];
161 }
162 double inv = 1.0 / (r2 * std::sqrt(r2));
163 for (int d = 0; d < Dim; ++d)
164 a[d] += m * dir[d] * inv;
165 }
166
167 bool isLeafNode(unsigned L, Code code, Index& li) const {
168 li = octree_.find(code);
169 return li >= 0 && octree_.code(li) == code && octree_.level(li) == L;
170 }
171
172 void walk(unsigned L, Code code, Index pi, Vec<Dim>& a) const {
173 auto it = agg_[L].find(code);
174 if (it == agg_[L].end())
175 return;
176 const Agg& g = it->second;
177
178 Index li = -1;
179 if (isLeafNode(L, code, li)) {
180 for (Index k = leafStart_[static_cast<std::size_t>(li)];
182 Index q = leafParts_[static_cast<std::size_t>(k)];
183 if (q != pi)
184 addPair(pos_[static_cast<std::size_t>(pi)], pos_[static_cast<std::size_t>(q)],
185 mass_[static_cast<std::size_t>(q)], a);
186 }
187 return;
188 }
189
190 // Internal node: try the multipole acceptance s/d < theta.
191 const Vec<Dim>& x = pos_[static_cast<std::size_t>(pi)];
192 Vec<Dim> com{};
193 double r2 = soft2_;
194 for (int d = 0; d < Dim; ++d) {
195 com[d] = g.com[d] / g.m;
196 double dd = com[d] - x[d];
197 r2 += dd * dd;
198 }
199 const double width = geo_.h0 * static_cast<double>(Index(1) << L);
200 if (width * width < theta_ * theta_ * r2) {
201 addPair(x, com, g.m, a);
202 return;
203 }
204 for (unsigned oct = 0; oct < Octree::octants; ++oct)
205 walk(L - 1, M::from_code(code).child(L, oct).code(), pi, a);
206 }
207
208 Octree octree_;
209 std::vector<Vec<Dim>> pos_;
210 std::vector<double> mass_;
211 AmrGeometry<Dim> geo_{};
212 unsigned lmax_ = 0;
213 Coord fineExt_ = 1;
214 double theta_ = 0.5;
215 double soft2_ = 1e-6;
216 std::vector<Index> leafStart_; // CSR offsets, length nleaf+1
217 std::vector<Index> leafParts_; // particle indices grouped by leaf
218 std::vector<std::map<Code, Agg>> agg_; // agg_[level][nodeOriginCode]
219};
220
221} // namespace peclet::core::amr
222
223#endif // PECLET_CORE_HAVE_MORTON
224#endif // PECLET_CORE_AMR_BARNES_HUT_HPP
const Octree & octree() const
Vec< Dim > accelerationDirect(Index pi) const
Direct O(N^2) reference acceleration on particle pi (for validation).
void build(const std::vector< Vec< Dim > > &pos, const std::vector< double > &mass, const AmrGeometry< Dim > &geo, unsigned lmax, double theta, double soft=1e-3)
Build the tree from particle positions (+ masses) inside the world box described by geo (origin + h0;...
BlockOctree< Dim, Bits > Octree
Vec< Dim > acceleration(Index pi) const
Approximate acceleration on particle pi.
std::vector< Vec< Dim > > accelerations() const
typename Octree::Coord Coord
typename Octree::Code Code
void init(IVec< Dim > brick, unsigned lmax, IVec< Dim > globalOrigin=IVec< Dim >{})
Index refineIf(Pred &&pred)
Split every leaf for which pred(code, level) is true (and level > 0) into its 2^Dim children one leve...
unsigned level(Index i) const
Index find(Code p) const
Leaf containing Morton code p, or -1. Host wrapper over amrLocate.
Kokkos::View< T *, MemSpace > View
1D device array.
Definition view.hpp:26
std::int64_t Index
Signed index type for grids and particles (supersedes block_decomposer's long int IndxT).
Definition types.hpp:15