peclet-dem
Performance-portable XPBD Discrete Element Method (Kokkos + ArborX)
Loading...
Searching...
No Matches
test_dem_scheme_c.cpp
Go to the documentation of this file.
1// Scheme C (the user's): compute each pair once, reverse-accumulate forces to owners, then FORWARD
2// the total force to ghosts and integrate ghosts LOCALLY — so ghost state is re-derived by each
3// rank rather than imported. Validated with transport-core's ParticleHaloTopology (reverse +
4// forward).
5//
6// Two checks, np=1,2,4:
7// (1) owned trajectories match a serial all-pairs reference (forces are correct), and
8// (2) the locally-integrated ghost copies stay consistent with their owners
9// (forwardPositions(current owned) == the locally-integrated ghost positions).
10// To isolate the local-integration property the halo is built ONCE with a skin and positions are
11// not wrapped (periodicity is handled by the min-image force); particles drift << skin over the
12// run. Scheme A is in test_dem_step.cpp, scheme B in test_dem_scheme_b.cpp.
13#include <mpi.h>
14
15#include <cmath>
16#include <cstdint>
17#include <cstdio>
18#include <cstring>
19#include <vector>
20
21#include "peclet/core/common/types.hpp"
22#include "peclet/core/decomp/block_decomposer.hpp"
23#include "peclet/core/halo/particle_halo_topology.hpp"
24#include "peclet/core/halo/particle_migrator.hpp"
25
26using namespace tpx;
27using peclet::core::decomp::BlockDecomposer;
28using peclet::core::halo::DomainMap;
29using peclet::core::halo::ParticleHaloTopology;
30using peclet::core::halo::ParticleMigrator;
31
32static constexpr int kSteps = 15;
33static const double Ld[3] = {10.0, 8.0, 6.0};
34static const double dmin[3] = {0, 0, 0};
35static constexpr double kR = 0.4, kRcut = 2.0 * kR, kSkin = 0.5 * kRcut;
36static constexpr double kStiff = 0.2, kDt = 0.02, kDamp = 0.99;
37
38struct F3 {
39 double v[3] = {0, 0, 0};
40 F3& operator+=(const F3& o) {
41 v[0] += o.v[0];
42 v[1] += o.v[1];
43 v[2] += o.v[2];
44 return *this;
45 }
46};
47struct Pay {
48 double vel[3];
49 std::int64_t id;
50};
51static double frac(std::uint64_t x, int s) {
52 x ^= (std::uint64_t)s * 2654435761u;
53 x ^= x >> 33;
54 x *= 0xff51afd7ed558ccdULL;
55 x ^= x >> 33;
56 return (double)(x & 0xFFFFFF) / (double)0x1000000;
57}
58static double mi(double d, double L) {
59 return d - L * std::round(d / L);
60}
61static F3 pf(const Vec<3>& a, const Vec<3>& b) {
62 double s[3] = {mi(a[0] - b[0], Ld[0]), mi(a[1] - b[1], Ld[1]), mi(a[2] - b[2], Ld[2])};
63 double d = std::sqrt(s[0] * s[0] + s[1] * s[1] + s[2] * s[2]);
64 F3 f;
65 if (d < kRcut && d > 1e-12) {
66 double m = kStiff * (kRcut - d) / d;
67 f.v[0] = m * s[0];
68 f.v[1] = m * s[1];
69 f.v[2] = m * s[2];
70 }
71 return f;
72}
73
74int main(int argc, char** argv) {
75 MPI_Init(&argc, &argv);
76 int rank = 0, size = 1;
77 MPI_Comm_rank(MPI_COMM_WORLD, &rank);
78 MPI_Comm_size(MPI_COMM_WORLD, &size);
79 const std::int64_t N = 400;
80
81 std::vector<Vec<3>> p0(N), v0(N);
82 for (std::int64_t i = 0; i < N; ++i)
83 for (int a = 0; a < 3; ++a) {
84 p0[i][a] = dmin[a] + frac(i, a) * Ld[a];
85 v0[i][a] = (frac(i, 10 + a) - 0.5) * 0.2;
86 }
87
88 // serial reference: all-pairs, NO position wrap (min-image force).
89 std::vector<Vec<3>> sp = p0, sv = v0;
90 for (int step = 0; step < kSteps; ++step) {
91 std::vector<F3> F(N);
92 for (std::int64_t i = 0; i < N; ++i)
93 for (std::int64_t j = 0; j < N; ++j)
94 if (j != i)
95 F[i] += pf(sp[i], sp[j]);
96 for (std::int64_t i = 0; i < N; ++i)
97 for (int a = 0; a < 3; ++a) {
98 sv[i][a] = (sv[i][a] + F[i].v[a] * kDt) * kDamp;
99 sp[i][a] += sv[i][a] * kDt;
100 }
101 }
102
103 // distributed scheme C
104 IVec<3> gsize{40, 32, 24};
105 BlockDecomposer<3> dec(static_cast<std::size_t>(size), gsize);
106 DomainMap<3> map;
107 for (int a = 0; a < 3; ++a) {
108 map.origin[a] = dmin[a];
109 map.cellSize[a] = Ld[a] / gsize[a];
110 map.periodic[a] = true;
111 }
112 ParticleMigrator<3> mig;
113 mig.init(dec, rank, map, MPI_COMM_WORLD);
114 ParticleHaloTopology<3> halo;
115 halo.init(mig);
116
117 const std::size_t stride = sizeof(Pay);
118 std::vector<Vec<3>> pos;
119 std::vector<char> payload;
120 for (std::int64_t id = rank; id < N; id += size) {
121 pos.push_back(p0[id]);
122 Pay p{};
123 for (int a = 0; a < 3; ++a)
124 p.vel[a] = v0[id][a];
125 p.id = id;
126 std::size_t off = payload.size();
127 payload.resize(off + stride);
128 std::memcpy(&payload[off], &p, stride);
129 }
130 mig.migrate(pos, payload, stride); // establish ownership once
131 std::size_t n = pos.size();
132 std::vector<std::int64_t> id(n);
133 std::vector<Vec<3>> vel(n);
134 for (std::size_t i = 0; i < n; ++i) {
135 Pay p;
136 std::memcpy(&p, &payload[i * stride], stride);
137 id[i] = p.id;
138 vel[i] = {p.vel[0], p.vel[1], p.vel[2]};
139 }
140
141 // build ONCE with a skin; maintain ghost state locally thereafter
142 halo.build(pos, kRcut + kSkin);
143 std::size_t G = halo.numGhost();
144 std::vector<Vec<3>> gpos = halo.ghostPositions();
145 std::vector<Vec<3>> gvel(G);
146 std::vector<double> ownIdD(n), ghIdD(G);
147 for (std::size_t i = 0; i < n; ++i)
148 ownIdD[i] = (double)id[i];
149 halo.forward(ownIdD.data(), ghIdD.data());
150 halo.forward(vel.data(), gvel.data()); // initial ghost velocities
151
152 for (int step = 0; step < kSteps; ++step) {
153 std::vector<F3> Fown(n), Fgh(G);
154 for (std::size_t i = 0; i < n; ++i)
155 for (std::size_t j = i + 1; j < n; ++j) {
156 F3 f = pf(pos[i], pos[j]);
157 Fown[i] += f;
158 Fown[j] += F3{{-f.v[0], -f.v[1], -f.v[2]}};
159 }
160 for (std::size_t i = 0; i < n; ++i)
161 for (std::size_t g = 0; g < G; ++g) {
162 if (id[i] >= (std::int64_t)std::llround(ghIdD[g]))
163 continue;
164 F3 f = pf(pos[i], gpos[g]);
165 Fown[i] += f;
166 Fgh[g] += F3{{-f.v[0], -f.v[1], -f.v[2]}};
167 }
168 halo.reverse(Fgh.data(), Fown.data()); // owners get total force
169 std::vector<F3> FghTot(G);
170 halo.forward(Fown.data(), FghTot.data()); // ghosts get THEIR total force
171
172 for (std::size_t i = 0; i < n; ++i)
173 for (int a = 0; a < 3; ++a) {
174 vel[i][a] = (vel[i][a] + Fown[i].v[a] * kDt) * kDamp;
175 pos[i][a] += vel[i][a] * kDt;
176 }
177 for (std::size_t g = 0; g < G; ++g)
178 for (int a = 0; a < 3; ++a) { // integrate ghosts LOCALLY (no state import)
179 gvel[g][a] = (gvel[g][a] + FghTot[g].v[a] * kDt) * kDamp;
180 gpos[g][a] += gvel[g][a] * kDt;
181 }
182 }
183
184 int fail = 0;
185 for (std::size_t i = 0; i < n; ++i)
186 for (int a = 0; a < 3; ++a)
187 if (std::fabs(pos[i][a] - sp[id[i]][a]) > 1e-9 || std::fabs(vel[i][a] - sv[id[i]][a]) > 1e-9)
188 ++fail;
189 // ghost consistency: forwarding current owned positions reproduces the locally-integrated ghosts
190 std::vector<Vec<3>> fresh(G);
191 halo.forwardPositions(pos.data(), fresh.data());
192 for (std::size_t g = 0; g < G; ++g)
193 for (int a = 0; a < 3; ++a)
194 if (std::fabs(fresh[g][a] - gpos[g][a]) > 1e-9)
195 ++fail;
196
197 int total = 0;
198 MPI_Allreduce(&fail, &total, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD);
199 if (rank == 0) {
200 if (total == 0)
201 std::printf(
202 "OK (np=%d): scheme C (local ghost integration via forwarded forces) matches serial"
203 " AND ghosts stay consistent\n",
204 size);
205 else
206 std::fprintf(stderr, "FAILED (np=%d): %d\n", size, total);
207 }
208 MPI_Finalize();
209 return total == 0 ? 0 : 1;
210}
F3 & operator+=(const F3 &o)
double v[3]
std::int64_t id
double vel[3]
static constexpr double kSkin
static F3 pf(const Vec< 3 > &a, const Vec< 3 > &b)
static constexpr double kRcut
int main(int argc, char **argv)
static constexpr double kDamp
static const double Ld[3]
static double frac(std::uint64_t x, int s)
static constexpr int kSteps
static double mi(double d, double L)
static constexpr double kR
static constexpr double kStiff
static constexpr double kDt
static const double dmin[3]
static const double L[3]