peclet-amr 0.2.0
Block-local-Morton AMR octree + collocated cut-cell Navier–Stokes solver (header-only C++20, Kokkos + MPI)
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// follow-up; the aggregates + leaf arrays are already device-friendly).
13#ifndef PECLET_AMR_BARNES_HUT_HPP
14#define PECLET_AMR_BARNES_HUT_HPP
15
16#include <algorithm>
17#include <array>
18#include <cmath>
19#include <map>
20#include <vector>
21
23#include "peclet/amr/common.hpp"
25#include "peclet/core/common/types.hpp"
26
27namespace peclet::amr {
28
29template <int Dim = 3, unsigned Bits = (Dim == 2 ? 32u : (Dim == 3 ? 21u : 16u))>
30class BarnesHut {
31 public:
33 using M = typename Octree::M;
34 using Code = typename Octree::Code;
35 using Coord = typename Octree::Coord;
36
39 void build(const std::vector<Vec<Dim>>& pos, const std::vector<double>& mass,
40 const AmrGeometry<Dim>& geo, unsigned lmax, double theta, double soft = 1e-3) {
41 pos_ = pos;
42 mass_ = mass;
43 geo_ = geo;
44 lmax_ = lmax;
45 theta_ = theta;
46 soft2_ = soft * soft;
47 fineExt_ = Coord(Coord(1) << lmax_);
48 octree_.init(IVec<Dim>{makeBrick()}, lmax_);
49
50 // Top-down: refine any leaf holding more than one particle (until level 0).
51 for (;;) {
52 std::vector<int> cnt(static_cast<std::size_t>(octree_.numLeaves()), 0);
53 for (std::size_t p = 0; p < pos_.size(); ++p)
54 ++cnt[static_cast<std::size_t>(leafOf(p))];
55 std::vector<Code> split;
56 for (Index i = 0; i < octree_.numLeaves(); ++i)
57 if (cnt[static_cast<std::size_t>(i)] > 1 && octree_.level(i) > 0)
58 split.push_back(octree_.code(i));
59 if (split.empty())
60 break;
61 std::sort(split.begin(), split.end());
62 octree_.refineIf(
63 [&](Code c, unsigned) { return std::binary_search(split.begin(), split.end(), c); });
64 }
65
66 // Leaf -> particle CSR.
67 const Index nleaf = octree_.numLeaves();
68 leafStart_.assign(static_cast<std::size_t>(nleaf) + 1, 0);
69 std::vector<Index> ofLeaf(pos_.size());
70 for (std::size_t p = 0; p < pos_.size(); ++p) {
71 Index li = leafOf(p);
72 ofLeaf[p] = li;
73 ++leafStart_[static_cast<std::size_t>(li) + 1];
74 }
75 for (Index i = 0; i < nleaf; ++i)
76 leafStart_[static_cast<std::size_t>(i) + 1] += leafStart_[static_cast<std::size_t>(i)];
77 leafParts_.assign(pos_.size(), 0);
78 std::vector<Index> cur(leafStart_.begin(), leafStart_.end() - 1);
79 for (std::size_t p = 0; p < pos_.size(); ++p) {
80 Index li = ofLeaf[p];
81 leafParts_[static_cast<std::size_t>(cur[static_cast<std::size_t>(li)]++)] =
82 static_cast<Index>(p);
83 }
84
85 // Node aggregates (mass + mass-weighted position) for every ancestor level.
86 agg_.assign(lmax_ + 1, {});
87 for (std::size_t p = 0; p < pos_.size(); ++p) {
88 Index li = ofLeaf[p];
89 Code lo = octree_.code(li);
90 unsigned Ll = octree_.level(li);
91 for (unsigned L = Ll; L <= lmax_; ++L) {
92 Code anc = M::from_code(lo).ancestor(L).code();
93 Agg& a = agg_[L][anc];
94 a.m += mass_[p];
95 for (int d = 0; d < Dim; ++d)
96 a.com[d] += mass_[p] * pos_[p][d];
97 }
98 }
99 }
100
103 Vec<Dim> a{};
104 walk(lmax_, Code(0), pi, a);
105 return a;
106 }
107
108 std::vector<Vec<Dim>> accelerations() const {
109 std::vector<Vec<Dim>> a(pos_.size());
110 for (std::size_t p = 0; p < pos_.size(); ++p)
111 a[p] = acceleration(static_cast<Index>(p));
112 return a;
113 }
114
117 Vec<Dim> a{};
118 for (std::size_t q = 0; q < pos_.size(); ++q)
119 if (static_cast<Index>(q) != pi)
120 addPair(pos_[static_cast<std::size_t>(pi)], pos_[q], mass_[q], a);
121 return a;
122 }
123
124 const Octree& octree() const { return octree_; }
125
126 private:
127 struct Agg {
128 double m = 0.0;
129 std::array<double, Dim> com{}; // stores mass-weighted sum until normalized at use
130 };
131
132 std::array<Index, Dim> makeBrick() const {
133 std::array<Index, Dim> b{};
134 for (int d = 0; d < Dim; ++d)
135 b[d] = 1;
136 return b;
137 }
138
139 std::array<Coord, Dim> fineCoord(std::size_t p) const {
140 std::array<Coord, Dim> c{};
141 for (int d = 0; d < Dim; ++d) {
142 long v = static_cast<long>(std::floor((pos_[p][d] - geo_.origin[d]) / geo_.h0[d]));
143 if (v < 0)
144 v = 0;
145 if (v >= static_cast<long>(fineExt_))
146 v = static_cast<long>(fineExt_) - 1;
147 c[d] = static_cast<Coord>(v);
148 }
149 return c;
150 }
151 Index leafOf(std::size_t p) const { return octree_.find(M::encode(fineCoord(p)).code()); }
152
153 void addPair(const Vec<Dim>& x, const Vec<Dim>& y, double m, Vec<Dim>& a) const {
154 double r2 = soft2_;
155 Vec<Dim> dir{};
156 for (int d = 0; d < Dim; ++d) {
157 dir[d] = y[d] - x[d];
158 r2 += dir[d] * dir[d];
159 }
160 double inv = 1.0 / (r2 * std::sqrt(r2));
161 for (int d = 0; d < Dim; ++d)
162 a[d] += m * dir[d] * inv;
163 }
164
165 bool isLeafNode(unsigned L, Code code, Index& li) const {
166 li = octree_.find(code);
167 return li >= 0 && octree_.code(li) == code && octree_.level(li) == L;
168 }
169
170 void walk(unsigned L, Code code, Index pi, Vec<Dim>& a) const {
171 auto it = agg_[L].find(code);
172 if (it == agg_[L].end())
173 return;
174 const Agg& g = it->second;
175
176 Index li = -1;
177 if (isLeafNode(L, code, li)) {
178 for (Index k = leafStart_[static_cast<std::size_t>(li)];
180 Index q = leafParts_[static_cast<std::size_t>(k)];
181 if (q != pi)
182 addPair(pos_[static_cast<std::size_t>(pi)], pos_[static_cast<std::size_t>(q)],
183 mass_[static_cast<std::size_t>(q)], a);
184 }
185 return;
186 }
187
188 // Internal node: try the multipole acceptance s/d < theta.
189 const Vec<Dim>& x = pos_[static_cast<std::size_t>(pi)];
190 Vec<Dim> com{};
191 double r2 = soft2_;
192 for (int d = 0; d < Dim; ++d) {
193 com[d] = g.com[d] / g.m;
194 double dd = com[d] - x[d];
195 r2 += dd * dd;
196 }
197 // Phase 3: the opening criterion is a single length, so it takes the COARSEST axis — the
198 // conservative reading (a box opens no later than the cube inscribing it).
199 const double width = geo_.hMax() * 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::amr
222
223#endif // PECLET_AMR_BARNES_HUT_HPP
Vec< Dim > accelerationDirect(Index pi) const
Direct O(N^2) reference acceleration on particle pi (for validation).
BlockOctree< Dim, Bits > Octree
const Octree & octree() const
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;...
typename Octree::Coord Coord
std::vector< Vec< Dim > > accelerations() const
Vec< Dim > acceleration(Index pi) const
Approximate acceleration on particle pi.
typename Octree::M M
typename Octree::Code Code
Index refineIf(Pred &&pred)
Split every leaf for which pred(code, level) is true (and level > 0) into its 2^Dim children one leve...
Code code(Index i) const
morton::Morton< Dim, Bits > M
unsigned level(Index i) const
Index find(Code p) const
Leaf containing Morton code p, or -1. Host wrapper over amrLocate.
void init(IVec< Dim > brick, unsigned lmax, IVec< Dim > globalOrigin=IVec< Dim >{})
static constexpr unsigned octants
std::vector< std::array< double, Dim > > transferFieldGradients(const BlockOctree< Dim, Bits > &oldT, const std::vector< double > &oldF)
Per-old-leaf minmod prolongation gradients (per fine-coordinate unit) — transferField's stencil,...
Definition adapt.hpp:51
World-space placement of a block-local octree: fine coordinate (0,..,0) sits at origin,...