1515#include " ../struct/particle.h"
1616#include < vector>
1717
18-
19- // =========================
20- // Octree definition
21- // =========================
2218struct Octree {
19+ // monopole
2320 real cx, cy, cz; // center of mass
2421 real m; // total mass
22+
23+ // node geometry
2524 real x, y, z; // center of node
2625 real size; // half-width
2726 bool leaf = true ;
2827 Particle* body = nullptr ;
2928 Octree* child[8 ] = {nullptr };
3029
30+ // symmetric quadrupole tensor (6 independent components)
31+ real Qxx = 0 , Qyy = 0 , Qzz = 0 ;
32+ real Qxy = 0 , Qxz = 0 , Qyz = 0 ;
33+
3134 Octree (real X, real Y, real Z, real S)
3235 : cx(0 ), cy(0 ), cz(0 ), m(0 ),
3336 x (X), y(Y), z(Z), size(S) {}
@@ -75,21 +78,31 @@ struct Octree {
7578 void computeMass () {
7679 if (leaf) {
7780 if (body) {
78- m = body->m ;
81+ m = body->m ;
7982 cx = body->x ;
8083 cy = body->y ;
8184 cz = body->z ;
85+ } else {
86+ m = 0 ;
87+ cx = cy = cz = 0 ;
8288 }
89+
90+ // single particle → no internal quadrupole
91+ Qxx = Qyy = Qzz = 0 ;
92+ Qxy = Qxz = Qyz = 0 ;
8393 return ;
8494 }
8595
8696 m = 0 ;
8797 cx = cy = cz = 0 ;
8898
99+ // first: recurse and accumulate mass + COM
89100 for (auto c : child) {
90101 if (!c) continue ;
91102 c->computeMass ();
92- m += c->m ;
103+ if (c->m == 0 ) continue ;
104+
105+ m += c->m ;
93106 cx += c->cx * c->m ;
94107 cy += c->cy * c->m ;
95108 cz += c->cz * c->m ;
@@ -99,6 +112,30 @@ struct Octree {
99112 cx /= m;
100113 cy /= m;
101114 cz /= m;
115+ } else {
116+ cx = cy = cz = 0 ;
117+ }
118+
119+ // second: build quadrupole from children treated as point masses
120+ Qxx = Qyy = Qzz = 0 ;
121+ Qxy = Qxz = Qyz = 0 ;
122+
123+ for (auto c : child) {
124+ if (!c || c->m == 0 ) continue ;
125+
126+ real rx = c->cx - cx;
127+ real ry = c->cy - cy;
128+ real rz = c->cz - cz;
129+ real r2 = rx*rx + ry*ry + rz*rz;
130+ real mchild = c->m ;
131+
132+ Qxx += mchild * (3 *rx*rx - r2);
133+ Qyy += mchild * (3 *ry*ry - r2);
134+ Qzz += mchild * (3 *rz*rz - r2);
135+
136+ Qxy += mchild * (3 *rx*ry);
137+ Qxz += mchild * (3 *rx*rz);
138+ Qyz += mchild * (3 *ry*rz);
102139 }
103140 }
104141};
@@ -113,34 +150,76 @@ inline void bhAccel(Octree* node, const Particle& p, real theta,
113150 if (node->leaf && node->body == &p)
114151 return ;
115152
116- constexpr real G = real (1.0 ); // temporarily 1 instead of meter units for galaxies to run efficiently
153+ constexpr real G = real (1.0 );
117154
118155 // Adaptive softening
119- real eps = node->size * real (0.01 );
156+ real eps = node->size * real (0.01 );
120157
121158 real dx = node->cx - p.x ;
122159 real dy = node->cy - p.y ;
123160 real dz = node->cz - p.z ;
124161
125- real distSq = dx*dx + dy*dy + dz*dz + eps*eps;
126- real dist = std::sqrt (distSq );
162+ real r2_soft = dx*dx + dy*dy + dz*dz + eps*eps;
163+ real dist = std::sqrt (r2_soft );
127164
128- // Correct Barnes–Hut acceptance criterion
165+ // BH acceptance criterion
129166 if (node->leaf || (node->size / dist) < theta)
130167 {
131- real invDist = std::sqrt (real (1.0 ) / distSq);
132- real invDist3 = invDist * invDist * invDist;
168+ // Monopole
169+ real invDist = real (1.0 ) / dist;
170+ real invDist2 = invDist * invDist;
171+ real invDist3 = invDist * invDist2;
133172
134173 real fac = G * node->m * invDist3;
135174
136- ax += dx * fac;
137- ay += dy * fac;
138- az += dz * fac;
175+ real ax_m = dx * fac;
176+ real ay_m = dy * fac;
177+ real az_m = dz * fac;
178+
179+ // Quadrupole (use non-softened r^2 for shape; still approximate)
180+ real rx = dx;
181+ real ry = dy;
182+ real rz = dz;
183+ real r2 = rx*rx + ry*ry + rz*rz + real (1e-12 ); // avoid zero
184+ real r = std::sqrt (r2);
185+ real invr = real (1.0 ) / r;
186+ real invr2 = invr * invr;
187+ real invr3 = invr * invr2;
188+ real invr5 = invr3 * invr2;
189+ real invr7 = invr5 * invr2;
190+
191+ // q = r_i Q_ij r_j
192+ real q =
193+ node->Qxx * rx * rx +
194+ node->Qyy * ry * ry +
195+ node->Qzz * rz * rz +
196+ 2 * (node->Qxy * rx * ry +
197+ node->Qxz * rx * rz +
198+ node->Qyz * ry * rz);
199+
200+ // ∇q = 2 Q r
201+ real Qrx = 2 * (node->Qxx * rx + node->Qxy * ry + node->Qxz * rz);
202+ real Qry = 2 * (node->Qxy * rx + node->Qyy * ry + node->Qyz * rz);
203+ real Qrz = 2 * (node->Qxz * rx + node->Qyz * ry + node->Qzz * rz);
204+
205+ // ∇(r^-5) = -5 r^-7 r
206+ real grad_r5_x = -5 * invr7 * rx;
207+ real grad_r5_y = -5 * invr7 * ry;
208+ real grad_r5_z = -5 * invr7 * rz;
209+
210+ // a_Q = (G/2) [ (∇q) r^-5 + q ∇(r^-5) ]
211+ real ax_q = (G * real (0.5 )) * (Qrx * invr5 + q * grad_r5_x);
212+ real ay_q = (G * real (0.5 )) * (Qry * invr5 + q * grad_r5_y);
213+ real az_q = (G * real (0.5 )) * (Qrz * invr5 + q * grad_r5_z);
214+
215+ ax += ax_m + ax_q;
216+ ay += ay_m + ay_q;
217+ az += az_m + az_q;
139218 return ;
140219 }
141220
142221 // Recurse
143222 for (int i = 0 ; i < 8 ; i++)
144223 if (node->child [i])
145224 bhAccel (node->child [i], p, theta, ax, ay, az);
146- }
225+ }
0 commit comments