7#ifndef DEM_STEP_SOLVE_MPI_HPP
8#define DEM_STEP_SOLVE_MPI_HPP
14#include <Kokkos_Core.hpp>
38 static constexpr bool distributed =
true;
42 int numManifoldsVisible;
43 float allMax(
float v)
const {
50 float allMaxAny(
float v,
bool& any)
const {
51 const float in[2] = {v, any ? 1.0f : 0.0f};
52 float out[2] = {v, in[1]};
57 bool syncPoint(
int it)
const {
return (it + 1) % syncEvery == 0; }
58 int visibleManifolds(
int)
const {
return numManifoldsVisible; }
59 void beginSolve(Particles& P)
const { halo.markVelocityBaseline(P); }
60 void syncVelocities(Particles& P)
const { halo.syncVelocities(P, forwardRotation); }
61 void publishPositions(Particles& P)
const { halo.publishPositions(P, forwardRotation); }
62 void syncPositions(Particles& P)
const { halo.syncPositions(P, forwardRotation); }
63 void syncFrictionCounts(Particles& P)
const { halo.syncFrictionCounts(P); }
64 void syncContactCounts(Particles& P)
const { halo.syncContactCounts(P); }
67 return RankK{halo.exchanges(), halo.rankColor(), halo.numRankColors(), syncEvery};
69 void openVelocityPhase(Particles& P,
bool poisson)
const {
70 halo.openVelocityPhase(P, forwardRotation, poisson);
72 void openPositionCounts(Particles& P,
bool velocityMask)
const {
73 halo.openPositionCounts(P, velocityMask);
75 void restoreOrphanBalance(Particles& P)
const { halo.restoreOrphanBalance(P); }
81inline constexpr float kDriftSlack = 0.25f;
89inline void driftVoteLocalKokkos(
const Particles& P,
const ParticleHalo::BlockBox box,
float dt,
90 float& eMax,
float& dMax,
float* scaleMax =
nullptr) {
91 const F3 g = P.gravity;
92 const auto pos = P.pos;
93 const auto vel = P.vel;
94 const auto ext = P.extForce;
95 const auto invM = P.invMass;
96 const auto sc = P.scale;
97 const float lo0 = box.lo.x, lo1 = box.lo.y, lo2 = box.lo.z, hi0 = box.hi.x, hi1 = box.hi.y,
98 hi2 = box.hi.z, L0 = box.period.x, L1 = box.period.y, L2 = box.period.z;
99 float e = 0.0f, d = 0.0f, smax = 0.0f;
100 Kokkos::parallel_reduce(
101 "peclet::dem::drift_vote", Kokkos::RangePolicy<CpExec>(0, P.numReal),
102 KOKKOS_LAMBDA(
int i,
float& em,
float& dm,
float& sm) {
103 const float im = invM(i);
104 const float ax = g.x + ext(i, 0) * im, ay = g.y + ext(i, 1) * im, az = g.z + ext(i, 2) * im;
105 const float v0 = vel(i, 0), v1 = vel(i, 1), v2 = vel(i, 2);
106 const float di = Kokkos::sqrt(v0 * v0 + v1 * v1 + v2 * v2) * dt +
107 Kokkos::sqrt(ax * ax + ay * ay + az * az) * dt * dt;
109#define PECLET_DEM_DRIFT_AXIS(X, LO, HI, L) \
111 float gk = Kokkos::fmax(0.0f, Kokkos::fmax((LO) - (X), (X) - (HI))); \
113 const float xm = (X) - (L), xp = (X) + (L); \
114 gk = Kokkos::fmin(gk, Kokkos::fmax(0.0f, Kokkos::fmax((LO) - xm, xm - (HI)))); \
115 gk = Kokkos::fmin(gk, Kokkos::fmax(0.0f, Kokkos::fmax((LO) - xp, xp - (HI)))); \
119 PECLET_DEM_DRIFT_AXIS(pos(i, 0), lo0, hi0, L0)
120 PECLET_DEM_DRIFT_AXIS(pos(i, 1), lo1, hi1, L1)
121 PECLET_DEM_DRIFT_AXIS(pos(i, 2), lo2, hi2, L2)
122#undef PECLET_DEM_DRIFT_AXIS
123 const float ei = Kokkos::sqrt(d2) + di;
124 em = ei > em ? ei : em;
125 dm = di > dm ? di : dm;
126 sm = sc(i) > sm ? sc(i) : sm;
128 Kokkos::Max<float>(e), Kokkos::Max<float>(d), Kokkos::Max<float>(smax));
138inline float globalMaxRadius(
const Particles& P, MPI_Comm comm) {
148inline double xpbdContactReach(
float rMax) {
149 return 2.1 *
static_cast<double>(rMax);
155inline void debugCaptureOwnedContacts(Particles& P,
const ParticleHalo& halo,
int ncOwned) {
156 DebugContactCapture& cap = P.debugCaptured;
157 const int no = P.numReal;
158 auto hc = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), P.contacts);
159 auto hg = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), P.gid);
160 auto hp = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), P.posPred);
161 auto hr = Kokkos::create_mirror_view_and_copy(Kokkos::HostSpace(), P.rad);
162 const std::vector<float> shift = halo.debugGhostShiftsBySlot();
163 const double box[3] = {
static_cast<double>(P.domain.max.x) - P.domain.min.x,
164 static_cast<double>(P.domain.max.y) - P.domain.min.y,
165 static_cast<double>(P.domain.max.z) - P.domain.min.z};
166 auto image = [&](
int slot, std::vector<int>& out) {
167 for (
int d = 0; d < 3; ++d) {
168 const float sh = slot < no ? 0.0f : shift[3 *
static_cast<std::size_t
>(slot - no) + d];
169 out.push_back(sh == 0.0f ? 0 : static_cast<int>(std::lround(sh / box[d])));
172 cap = DebugContactCapture{};
173 for (
int i = 0; i < ncOwned; ++i) {
174 const ContactC& c = hc(i);
175 cap.gidA.push_back(hg(c.bodyA));
176 cap.gidB.push_back(c.bodyB >= 0 ? hg(c.bodyB) : -1);
177 image(c.bodyA, cap.imageA);
179 image(c.bodyB, cap.imageB);
181 cap.imageB.insert(cap.imageB.end(), {0, 0, 0});
182 cap.dist.push_back(c.dist);
184 for (
int i = 0; i < no; ++i) {
185 cap.gid.push_back(hg(i));
186 for (
int d = 0; d < 3; ++d)
187 cap.posPred.push_back(hp(i, d));
188 cap.rad.push_back(hr(i));
203inline MpiDriftVote mpiDriftVote(Particles& P, ParticleHalo& halo) {
204 float vote[3] = {0.0f, 0.0f, 0.0f}, smax = 0.0f;
205 driftVoteLocalKokkos(P, halo.blockBox(), P.dt, vote[1], vote[2], &smax);
207 vote[0] = P.numReal > 0 ? smax * P.globalScale * P.baseRadius : 0.0f;
210 const MpiDriftVote v{g[0], 0.1f * g[0], kDriftSlack * g[0], g[2]};
212 MPI_Comm_size(halo.comm(), &commSize);
215#if defined(PECLET_DEM_TEST_MUTANT) && PECLET_DEM_TEST_MUTANT == 5
218 if (commSize > 1 && g[1] >= v.slack) {
220 halo.migrateToBlocks(P);
221 ++P.splitStats.driftMigrations;
228inline double mpiXpbdBand(
const Particles& P, ParticleHalo& halo,
double rcut,
229 const MpiDriftVote& v) {
230 return std::max(rcut, xpbdContactReach(globalMaxRadius(P, halo.comm())) +
231 static_cast<double>(v.slack) +
232 (halo.verletSkin() > 0.0f ? 0.0 : static_cast<double>(v.dMax)));
261inline void demStepMpi(Particles& P, ParticleHalo& halo,
double rcut,
int syncEvery,
262 bool forwardRotation) {
264 Kokkos::Profiling::pushRegion(
"dem::vote");
265 const MpiDriftVote vote = mpiDriftVote(P, halo);
266 Kokkos::Profiling::popRegion();
270 const float margin = vote.margin;
272 if (P.growthFactor != -1.0f && P.growthRate != 0.0f) {
273 P.growthFactor *= std::exp(P.growthRate * P.dt);
274 if (P.growthFactor > 1.0f)
275 P.growthFactor = 1.0f;
277 if (P.growthFactor > 0.0f)
279 const double band = mpiXpbdBand(P, halo, rcut, vote);
282 P.numParticles = P.numReal;
284 P.posPred, P.quatPred, P.velPred, P.angVelPred, P.deltaPos, P.deltaQuat,
285 P.deltaVel, P.deltaAngVel, P.constraintCounts, P.gravity, P.dt, P.extForce,
291 Kokkos::Profiling::pushRegion(
"dem::gather");
292 halo.gather(P, band);
293 Kokkos::Profiling::popRegion();
298 Kokkos::Profiling::pushRegion(
"dem::mapSlots");
299 halo.mapVelocitySlots(P);
300 Kokkos::Profiling::popRegion();
308 Kokkos::Profiling::pushRegion(
"dem::broad");
310 Kokkos::Profiling::popRegion();
312 Kokkos::deep_copy(space, P.contactCount, 0);
313 Kokkos::deep_copy(space, P.maxOverlap, 0.0f);
314 Kokkos::Profiling::pushRegion(
"dem::narrow");
316 Kokkos::Profiling::popRegion();
321 Kokkos::Profiling::pushRegion(
"dem::ownership");
323 Kokkos::Profiling::popRegion();
325 debugCaptureOwnedContacts(P, halo, ncOwned);
329 const int nmVisible =
readInt(P.manifoldCount);
334 Kokkos::Profiling::pushRegion(
"dem::solve");
336 MpiSolveHooks{halo, syncEvery < 1 ? 1 : syncEvery, forwardRotation, nmVisible});
337 Kokkos::Profiling::popRegion();
341 finalCommitKokkos(P.numReal, P.pos, P.invMass, P.posPred, P.quat, P.quatPred, P.domain);
343 if (P.thermostatTau > 0.0f && P.dt > 0.0f)
345 P.thermostatKB, P.thermostatTau, P.thermostatTemp, P.dt);
347 P.numParticles = P.numReal;
356struct MpiForceHooks {
357 static constexpr bool distributed =
true;
361 float allMax(
float v)
const {
366 float allMin(
float v)
const {
371 void gatherGhosts(Particles& P)
const {
376 float e = 0.0f, d = 0.0f;
377 driftVoteLocalKokkos(P, halo.blockBox(), 0.0f, e, d);
379 MPI_Comm_size(halo.comm(), &commSize);
380 if (allMax(e) >= slack && commSize > 1) {
381 halo.migrateToBlocks(P);
382 ++P.splitStats.driftMigrations;
389 halo.invalidateTopology();
390 halo.gather(P, band);
393 void refreshGhostState(Particles& P,
bool needQuat)
const {
394 halo.forwardPositions(P.pos);
396 halo.forward(P.angVel);
398 halo.forward4(P.quat);
400 void clearGhostScratch(Particles& P)
const {
408inline void demStepHertzMpi(Particles& P, ParticleHalo& halo,
float dt,
int nsteps,
413 float maxR = P.numReal > 0 ?
maxOwnedRadius(P) : 0.0f, maxRg = maxR;
414 MPI_Allreduce(&maxR, &maxRg, 1, MPI_FLOAT, MPI_MAX, halo.comm());
415 const float slack = kDriftSlack * maxRg;
416 const double band = (2.0 + skinFrac) *
static_cast<double>(maxRg) +
static_cast<double>(slack);
417 demStepForce(P, dt, nsteps, skinFrac, HertzMindlinLaw{}, MpiForceHooks{halo, band, slack});
418 P.numParticles = P.numReal;
dem — portable (Kokkos) owner<->ghost particle halo for the distributed XPBD step.
int narrowPhaseGrow(Particles &P, int np, float margin)
Narrow phase with an automatically-grown contact buffer.
int partitionContactsKokkos(Kokkos::View< ContactC *, CpMem > contacts, int n, const Owns &owns)
Stable partition of contacts [0, n) by owns(contact): the owned ones first, [0, ncOwned),...
float maxOwnedRadius(const Particles &P)
Largest effective particle radius over the owned set (= max scale × globalScale, growth included).
void zeroForceScratchKokkos(V3 dv, V3 dw, int lo, int hi)
Zero the force/torque accumulator rows [lo, hi) — under MPI the pair kernels atomically accumulate on...
void demSolveContacts(Particles &P, int nc, int nm, int nBodies, Kokkos::View< const int *, CpMem > keyIdx, const Hooks &hooks)
One full velocity + position contact solve over the already-built contacts/manifolds (see file commen...
int readInt(Kokkos::View< int, CpMem > v)
void demStepForce(Particles &P, float dt, int nsteps, float skinFrac, const Law &law, const Hooks &hooks)
nsteps explicit force-based DEM steps of size dt with force law Law (see file comment).
int reduceContactsToManifoldsKokkos(Kokkos::View< const ContactC *, CpMem > contacts, int n, Kokkos::View< ManifoldC *, CpMem > outManifolds, Kokkos::View< int, CpMem > outCount, Kokkos::View< int *, CpMem > contactSlot={}, int numOwnedContacts=-1, int *numOwnedManifolds=nullptr)
Reduce n contacts to manifolds (one per unique canonical pair).
int findCollisionsGrow(Particles &P, float margin)
Broad phase with an automatically-grown pair buffer.
void finalCommitKokkos(int n, V3 pos, Vf invMass, V3 posPred, V4 quat, V4 quatPred, Domain dom)
Final commit: periodic wrap of the predicted position into the domain, commit position + quat.
void fillWorldRadiiKokkos(Vf scale, Vf rad, float gs, float bR, int n)
World radii rad(i) = scale(i) * globalScale * baseRadius over [0, n): the owned span before a step,...
void updateGrowthScalesKokkos(int n, Vf scale, Vf targetScale, float factor)
Growth mode: scale = target * factor (when active).
void predictVelocityKokkos(int n, V3 pos, Vf invMass, V3 vel, V4 quat, V3 angVel, V3 invInertia, V3 posPred, V4 quatPred, V3 velPred, V3 angVelPred, V3 deltaPos, V4 deltaQuat, V3 deltaVel, V3 deltaAngVel, Vi constraintCounts, F3 gravity, float dt, V3 extForce, V3 extTorque)
Predict velocity (gravity + gyroscopic precession), speculative position, and clear all deltas.
void applyThermostatKokkos(int numReal, V3 vel, Vf invMass, V3 angVel, V3 invInertia, V4 quat, double kB, double tau, double Ttarget, float dt)
Kokkos::DefaultExecutionSpace CpExec
dem — portable (Kokkos) particle SoA container: the storage the dem flip pivots on.
int MPI_Allreduce(const void *a, void *b, int c, MPI_Datatype d, MPI_Op e, MPI_Comm f)
dem — the shared contact-solve driver: the full modern velocity + position solve sequence (warm-start...
dem — the force-based DEM step driver: explicit soft-contact time stepping (the engine family that co...
dem — the single-rank step drivers over the particle SoA: the XPBD substep (demStep),...