|
12 | 12 | #pragma once |
13 | 13 | #include "floatdef.h" |
14 | 14 | #include "dt/softening.h" |
| 15 | +#include <vector> |
15 | 16 | #include <cmath> |
| 17 | +#include <algorithm> |
16 | 18 |
|
17 | | -struct alignas(32) Particle { |
18 | | - real x, y, z; |
19 | | - real vx, vy, vz; |
20 | | - real m; |
21 | | - int type; |
| 19 | +/** |
| 20 | + * @brief Structure of Arrays (SoA) container for the particle data. |
| 21 | + * This layout is significantly faster for SIMD vectorization and cache locality. |
| 22 | + */ |
| 23 | +struct ParticleSystem { |
| 24 | + // Positions |
| 25 | + std::vector<real> x, y, z; |
| 26 | + // Velocities |
| 27 | + std::vector<real> vx, vy, vz; |
| 28 | + // Accelerations (used for force summation) |
| 29 | + std::vector<real> ax, ay, az; |
| 30 | + // Masses and Types (0 = Star, 1 = Dark Matter) |
| 31 | + std::vector<real> m; |
| 32 | + std::vector<int> type; |
| 33 | + |
| 34 | + /** |
| 35 | + * @brief Resizes all buffers and ensures accelerations are zeroed. |
| 36 | + */ |
| 37 | + void resize(size_t n) { |
| 38 | + x.resize(n, 0); y.resize(n, 0); z.resize(n, 0); |
| 39 | + vx.resize(n, 0); vy.resize(n, 0); vz.resize(n, 0); |
| 40 | + ax.assign(n, 0); ay.assign(n, 0); az.assign(n, 0); |
| 41 | + m.resize(n, 0); type.resize(n, 0); |
| 42 | + } |
| 43 | + |
| 44 | + /** |
| 45 | + * @brief Adds a single particle to the system. |
| 46 | + */ |
| 47 | + void addParticle(real px, real py, real pz, real pvx, real pvy, real pvz, real pm, int ptype) { |
| 48 | + x.push_back(px); y.push_back(py); z.push_back(pz); |
| 49 | + vx.push_back(pvx); vy.push_back(pvy); vz.push_back(pvz); |
| 50 | + ax.push_back(0); ay.push_back(0); az.push_back(0); |
| 51 | + m.push_back(pm); |
| 52 | + type.push_back(ptype); |
| 53 | + } |
| 54 | + |
| 55 | + size_t size() const { return x.size(); } |
| 56 | + |
| 57 | + void clear() { |
| 58 | + x.clear(); y.clear(); z.clear(); |
| 59 | + vx.clear(); vy.clear(); vz.clear(); |
| 60 | + ax.clear(); ay.clear(); az.clear(); |
| 61 | + m.clear(); type.clear(); |
| 62 | + } |
22 | 63 | }; |
23 | 64 |
|
24 | | -inline void Gravity(Particle &a, Particle &b, real dt) { |
25 | | - constexpr real G = real(1.0); |
| 65 | +/** |
| 66 | + * @brief Calculates gravity between two indices in the SoA system. |
| 67 | + * This follows Newton's 3rd law to update both particles simultaneously. |
| 68 | + */ |
| 69 | +inline void GravitySoA(ParticleSystem &ps, size_t i, size_t j, real dt) { |
| 70 | + constexpr real G = real(1.0); |
| 71 | + |
| 72 | + // Get separation vector |
| 73 | + real dx = ps.x[j] - ps.x[i]; |
| 74 | + real dy = ps.y[j] - ps.y[i]; |
| 75 | + real dz = ps.z[j] - ps.z[i]; |
26 | 76 |
|
27 | | - real eps = pairSoftening(a.m, b.m); |
| 77 | + // Adaptive Softening (DM vs Stars) |
| 78 | + // Note: eps is based on your pairSoftening logic |
| 79 | + real eps = pairSoftening(ps.m[i], ps.m[j]); |
28 | 80 |
|
29 | | - real dx = b.x - a.x; |
30 | | - real dy = b.y - a.y; |
31 | | - real dz = b.z - a.z; |
| 81 | + // r^2 + epsilon^2 |
| 82 | + real r2 = dx * dx + dy * dy + dz * dz + eps * eps; |
32 | 83 |
|
33 | | - real r2 = dx * dx + dy * dy + dz * dz + eps * eps; |
| 84 | + // Standard Gravity: F = G * m1 * m2 / r^2 |
| 85 | + real invR2 = real(1.0) / r2; |
| 86 | + real invR = std::sqrt(invR2); |
| 87 | + real invR3 = invR * invR2; |
34 | 88 |
|
35 | | - real invR2 = real(1.0) / r2; |
36 | | - real invR = std::sqrt(invR2); |
37 | | - real invR3 = invR * invR2; |
| 89 | + // Common force factor |
| 90 | + real f_ij = G * invR3; |
38 | 91 |
|
39 | | - real ax = G * b.m * dx * invR3; |
40 | | - real ay = G * b.m * dy * invR3; |
41 | | - real az = G * b.m * dz * invR3; |
| 92 | + // Acceleration on i due to j |
| 93 | + real aix = f_ij * ps.m[j] * dx; |
| 94 | + real aiy = f_ij * ps.m[j] * dy; |
| 95 | + real aiz = f_ij * ps.m[j] * dz; |
42 | 96 |
|
43 | | - real bx = -G * a.m * dx * invR3; |
44 | | - real by = -G * a.m * dy * invR3; |
45 | | - real bz = -G * a.m * dz * invR3; |
| 97 | + // Apply Velocity Kicks (dt) |
| 98 | + ps.vx[i] += aix * dt; |
| 99 | + ps.vy[i] += aiy * dt; |
| 100 | + ps.vz[i] += aiz * dt; |
46 | 101 |
|
47 | | - a.vx += ax * dt; |
48 | | - a.vy += ay * dt; |
49 | | - a.vz += az * dt; |
| 102 | + // Apply Velocity Kicks to j (Newton's 3rd Law: a_j = -a_i * (m_i / m_j)) |
| 103 | + // We multiply by m_i here because f_ij only contained G and r^-3 |
| 104 | + ps.vx[j] -= (f_ij * ps.m[i] * dx) * dt; |
| 105 | + ps.vy[j] -= (f_ij * ps.m[i] * dy) * dt; |
| 106 | + ps.vz[j] -= (f_ij * ps.m[i] * dz) * dt; |
| 107 | +} |
50 | 108 |
|
51 | | - b.vx += bx * dt; |
52 | | - b.vy += by * dt; |
53 | | - b.vz += bz * dt; |
| 109 | +/** |
| 110 | + * @brief Contiguous Drift step (Update Positions). |
| 111 | + * High locality ensures the compiler can vectorize this with SIMD. |
| 112 | + */ |
| 113 | +inline void DriftSoA(ParticleSystem &ps, real dt) { |
| 114 | + const size_t n = ps.size(); |
| 115 | + #pragma omp parallel for schedule(static) |
| 116 | + for (size_t i = 0; i < n; ++i) { |
| 117 | + ps.x[i] += ps.vx[i] * dt; |
| 118 | + ps.y[i] += ps.vy[i] * dt; |
| 119 | + ps.z[i] += ps.vz[i] * dt; |
| 120 | + } |
54 | 121 | } |
0 commit comments