peclet-dem 0.4.0
Performance-portable XPBD Discrete Element Method (Kokkos + ArborX)
Loading...
Searching...
No Matches
solve_driver.hpp
Go to the documentation of this file.
1
23#ifndef DEM_SOLVE_DRIVER_HPP
24#define DEM_SOLVE_DRIVER_HPP
25
26#include <algorithm>
27#include <cstdlib>
28#include <Kokkos_Core.hpp>
29#include <utility>
30#include <vector>
31
32#include "broadphase_arborx.hpp"
34#include "particles.hpp"
35#include "solver_friction.hpp"
36#include "solver_multilevel.hpp"
37#include "solver_position.hpp"
38#include "solver_velocity.hpp"
39
40namespace peclet::dem {
41
42inline int readInt(Kokkos::View<int, CpMem> v) {
43 int h;
44 Kokkos::deep_copy(h, v);
45 return h;
46}
47inline float readFloat(Kokkos::View<float, CpMem> v) {
48 float h;
49 Kokkos::deep_copy(h, v);
50 return h;
51}
52
55inline void fillGidBaseKokkos(Vi gid, int n, int base) {
56 Kokkos::parallel_for(
57 "peclet::dem::gid_base", Kokkos::RangePolicy<CpExec>(0, n),
58 KOKKOS_LAMBDA(int i) { gid(i) = base + i; });
59 Kokkos::fence();
60}
61
67inline float maxOwnedRadius(const Particles& P) {
68 if (P.numReal <= 0)
69 return P.globalScale * P.baseRadius;
70 float mx = 0.0f;
71 auto sc = P.scale;
72 Kokkos::parallel_reduce(
73 "peclet::dem::max_scale", Kokkos::RangePolicy<CpExec>(0, P.numReal),
74 KOKKOS_LAMBDA(int i, float& m) { m = sc(i) > m ? sc(i) : m; }, Kokkos::Max<float>(mx));
75 return mx * P.globalScale * P.baseRadius;
76}
77
88inline int findCollisionsGrow(Particles& P, float margin) {
89 const float boxCap = std::max(std::max(P.domain.size.x, P.domain.size.y), P.domain.size.z);
90 int np = findCollisionsArborX(P.posPred, P.crad(), P.numParticles, P.numReal, margin, P.pairs,
91 P.pairCount, boxCap);
92 if (np > static_cast<int>(P.pairs.extent(0))) {
93 const int grown = np + np / 2 + 64; // 1.5× + slack
94 Kokkos::realloc(Kokkos::WithoutInitializing, P.pairs, grown);
95 P.maxPairs = grown;
96 np = findCollisionsArborX(P.posPred, P.crad(), P.numParticles, P.numReal, margin, P.pairs,
97 P.pairCount, boxCap);
98 }
99 return std::min(np, static_cast<int>(P.pairs.extent(0)));
100}
101
110inline int findCollisionsVerlet(Particles& P, float margin, float maxRad) {
111 const float skin = P.verletSkinFrac * maxRad;
112 bool rebuild = (P.impNumPairs < 0);
113 if (!rebuild) {
114 float md2 = 0.0f;
115 auto pp = P.posPred;
116 auto rp = P.impRefPos;
117 Kokkos::parallel_reduce(
118 "peclet::dem::verlet_disp", Kokkos::RangePolicy<CpExec>(0, P.numReal),
119 KOKKOS_LAMBDA(int i, float& acc) {
120 const float dx = pp(i, 0) - rp(i, 0), dy = pp(i, 1) - rp(i, 1), dz = pp(i, 2) - rp(i, 2);
121 const float d = dx * dx + dy * dy + dz * dz;
122 if (d > acc)
123 acc = d;
124 },
125 Kokkos::Max<float>(md2));
126 Kokkos::fence();
127 // Growth grows radii between rebuilds; a pair can close by ~2x the radius growth with no CoM
128 // motion, so fold it into the displacement budget.
129 const float grow = std::max(0.0f, maxRad - P.impRefMaxRad);
130 if (std::sqrt(md2) + 2.0f * grow > 0.5f * skin)
131 rebuild = true;
132 }
133 if (rebuild) {
134 const int np = findCollisionsGrow(P, margin + skin); // candidate list at margin + skin
135 const auto rng = Kokkos::pair<int, int>(0, P.numReal);
136 Kokkos::deep_copy(Kokkos::subview(P.impRefPos, rng, Kokkos::ALL),
137 Kokkos::subview(P.posPred, rng, Kokkos::ALL));
138 P.impNumPairs = np;
139 P.impRefMaxRad = maxRad;
140 return np;
141 }
142 return P.impNumPairs; // reuse the cached candidate list (P.pairs is unchanged)
143}
144
145#ifdef KOKKOS_ENABLE_CUDA
153struct CudaIterGraph {
154 cudaGraphExec_t exec = nullptr;
155 template <class F>
156 bool capture(CpExec& space, F&& emit, void*& cacheSlot) {
157 cudaStream_t str = space.cuda_stream();
158 if (cudaStreamBeginCapture(str, cudaStreamCaptureModeThreadLocal) != cudaSuccess) {
159 (void)cudaGetLastError();
160 return false;
161 }
162 emit();
163 cudaGraph_t g = nullptr;
164 if (cudaStreamEndCapture(str, &g) != cudaSuccess || g == nullptr) {
165 (void)cudaGetLastError();
166 return false;
167 }
168 // Instantiation dominates the per-step graph cost (~10 us/node); the topology is stable
169 // step-to-step, so refresh last step's executable in place and only re-instantiate when the
170 // structure genuinely changed (bucket emptiness pattern / colour count shifts).
171 if (cacheSlot != nullptr) {
172 exec = static_cast<cudaGraphExec_t>(cacheSlot);
173 cudaGraphExecUpdateResultInfo ri;
174 if (cudaGraphExecUpdate(exec, g, &ri) != cudaSuccess) {
175 (void)cudaGetLastError();
176 cudaGraphExecDestroy(exec);
177 exec = nullptr;
178 cacheSlot = nullptr;
179 }
180 }
181 if (exec == nullptr) {
182 if (cudaGraphInstantiate(&exec, g, nullptr, nullptr, 0) != cudaSuccess) {
183 (void)cudaGetLastError();
184 exec = nullptr;
185 }
186 cacheSlot = exec;
187 }
188 cudaGraphDestroy(g);
189 return exec != nullptr;
190 }
191 void launch(CpExec& space) { cudaGraphLaunch(exec, space.cuda_stream()); }
192 // the executable is owned by the cross-step cache slot, not this per-step handle
193};
194#define PECLET_DEM_GRAPH_LOOP(useVar, graphVar, emitVar, slotVar) \
195 if constexpr (!Hooks::distributed) { \
196 static const bool gOff = std::getenv("PECLET_DEM_NO_GRAPH") != nullptr; \
197 if (!gOff) \
198 useVar = graphVar.capture(space, emitVar, slotVar); \
199 }
200#else
202 template <class F>
203 bool capture(CpExec&, F&&, void*&) {
204 return false;
205 }
206 void launch(CpExec&) {}
207};
208#define PECLET_DEM_GRAPH_LOOP(useVar, graphVar, emitVar, slotVar) (void)graphVar;
209#endif
210
213 static constexpr bool distributed = false;
214 float allMax(float v) const { return v; }
215 bool syncPoint(int) const { return false; }
216 void syncVelocities(Particles&) const {}
217 void syncPositions(Particles&) const {}
218};
219
223template <class Hooks>
224inline void demSolveContacts(Particles& P, int nc, int nm, int nBodies,
225 Kokkos::View<const int*, CpMem> keyIdx, const Hooks& hooks) {
226 CpExec space;
227
228 // A frictional wall drives friction even when the body-body material is frictionless.
229 const bool friction = (P.frictionDynamic > 0.0f || P.wallFrictionMax > 0.0f);
230 const bool usePersistPre = (P.gravity.x != 0.0f || P.gravity.y != 0.0f || P.gravity.z != 0.0f);
231 // Incremental (warm-started) colouring A/B gate: PECLET_DEM_NO_INCR_COLOR=1 forces the full
232 // per-substep recolour (the pre-incremental behaviour) for validation; default on (single-GPU
233 // PGS path). Read once — the flag never changes within a run.
234 static const bool incrColorOff = [] {
235 const char* e = std::getenv("PECLET_DEM_NO_INCR_COLOR");
236 return e && std::atoi(e) != 0;
237 }();
238 // Island sleeping (single-GPU statics, gravity on, no external drag): both-asleep manifolds /
239 // contacts are excluded from the colouring / sweeps / multilevel hierarchy (their masks were
240 // filled by the caller). Empty views leave every colouring bit-identical to the sleeping-off
241 // path. The caller has already swapped P.invMass to the effective (sleeper -> 0) inverse mass.
242 const bool sleepOn =
243 P.sleepingEnabled && !Hooks::distributed && usePersistPre && !P.extForceActive;
244 const Kokkos::View<const unsigned char*, CpMem> mSleep =
245 sleepOn ? Kokkos::View<const unsigned char*, CpMem>(P.manifoldSleep)
246 : Kokkos::View<const unsigned char*, CpMem>();
247 const Kokkos::View<const unsigned char*, CpMem> cSleep =
248 sleepOn ? Kokkos::View<const unsigned char*, CpMem>(P.contactSleep)
249 : Kokkos::View<const unsigned char*, CpMem>();
250 const bool legacyFriction = friction && !(usePersistPre && P.velocityUseGS);
251 if (legacyFriction)
253 P.planeFriction);
254
255 // Colour the manifold graph ONCE (topology-only; reused across the sweeps), then normal
256 // restitution as colored Gauss–Seidel: correct multi-contact dissipation with no count-averaging
257 // (see solver_velocity.hpp). count==1 binary collisions are identical to the old Jacobi path.
258 int velLeftover = 0;
259 int numColors = 0;
260 if (P.velocityUseGS) {
261 // Incremental colouring (single-GPU PGS path only): carry surviving pairs' colours across
262 // substeps, re-arbitrating only the new manifolds. Bit-identical colouring FORM to the full
263 // recolour; forced full on the fresh substep and when the colour count creeps > 1.3x the last
264 // full recolour (compaction keeps the serialized-phase count bounded). Under MPI or g == 0 the
265 // full recolour stays (a carried colour can cross a rank boundary it never arbitrated against).
266 const bool incrColor = usePersistPre && P.velocityUseGS && !Hooks::distributed && !incrColorOff;
267 if (incrColor) {
268 bool didFull = (P.prevPairCount <= 0);
270 P.manifolds, nm, P.realIndices, nBodies,
271 Kokkos::View<const unsigned long long*, CpMem>(P.prevPairKeys),
272 Kokkos::View<const int*, CpMem>(P.prevManifoldColor), P.prevPairCount, P.manifoldColor,
273 P.bodyWinner, P.bodyColorMask, velLeftover, /*forceFull*/ didFull, mSleep);
274 if (!didFull && P.velLastFullColors > 0 && numColors > (P.velLastFullColors * 13) / 10) {
276 P.manifolds, nm, P.realIndices, nBodies,
277 Kokkos::View<const unsigned long long*, CpMem>(P.prevPairKeys),
278 Kokkos::View<const int*, CpMem>(P.prevManifoldColor), P.prevPairCount, P.manifoldColor,
279 P.bodyWinner, P.bodyColorMask, velLeftover, /*forceFull*/ true, mSleep);
280 didFull = true;
281 }
282 if (didFull)
283 P.velLastFullColors = numColors;
284 } else {
285 numColors = colorManifoldsKokkos(P.manifolds, nm, P.realIndices, nBodies, P.manifoldColor,
286 P.bodyWinner, P.bodyColorMask, velLeftover, mSleep);
287 }
288 }
289 // Dense colour buckets for the PGS sweeps (bit-identical: colour classes are body-disjoint).
290 std::vector<int> velOffs;
291 const bool velBuckets = P.velocityUseGS && nm > 0 && numColors > 0;
292 if (velBuckets)
293 buildColorBucketsKokkos(Kokkos::View<const int*, CpMem>(P.manifoldColor), nm, numColors,
294 P.velPerm, P.bucketCursor, velOffs);
295 const Kokkos::View<const int*, CpMem> velPermC(P.velPerm);
296 const std::vector<int>* velOffsP = velBuckets ? &velOffs : nullptr;
297 // Fused colour sweeps (CUDA): the whole PGS sweep — and where eligible the whole adaptive
298 // iteration loop — as ONE kernel iterating device-side (see solver_fused.hpp); same colour
299 // ordering, bit-identical. Default policy: on exactly where CUDA-graph replay is unavailable
300 // (the distributed step, PECLET_DEM_NO_GRAPH); PECLET_DEM_FUSED / PECLET_DEM_NO_FUSED force.
301 static const bool graphEnvOff = std::getenv("PECLET_DEM_NO_GRAPH") != nullptr;
302 const bool wantFused = demFusedWanted(!Hooks::distributed && !graphEnvOff);
303 const FusedSweepCtx velFused = (velBuckets && wantFused)
304 ? demMakeFusedCtx(space, velOffs, P.velOffsDev, P.fusedBar)
305 : FusedSweepCtx{};
306 const FusedSweepCtx* velFusedP = velFused.maxBucket > 0 ? &velFused : nullptr;
307 // Persistent-contact restitution, gravity-gated (|g| = 0 leaves behaviour untouched: growth
308 // packing / HCS bit-identical). A pair already in contact LAST substep is loaded, not impacting:
309 // it gets e = 0 (the impulse still cancels the approach — pure inelastic support), so the
310 // velocity solve carries a pile's static weight through impulse chains and a settling column
311 // actually cools; material/wall restitution stays reserved for newly formed contacts.
312 const bool usePersist = (P.gravity.x != 0.0f || P.gravity.y != 0.0f || P.gravity.z != 0.0f);
313 // Warm-started PGS velocity solve (|g| > 0): gather each manifold's previous-substep converged
314 // push impulse by pair key, record the pre-solve approach (restitution bias), and apply the warm
315 // impulses up front -- a static pile's force network is re-established in ~one sweep. g = 0
316 // keeps the original one-shot colored-GS path bit-identical (HCS, growth packing).
317 const bool usePGS = usePersist && P.velocityUseGS;
318 const float gMagP = Kokkos::sqrt(P.gravity.x * P.gravity.x + P.gravity.y * P.gravity.y +
319 P.gravity.z * P.gravity.z);
320 const F3 gHat =
321 usePersist ? F3{P.gravity.x / gMagP, P.gravity.y / gMagP, P.gravity.z / gMagP} : F3{0, 0, 0};
322 // Event-level (Poisson) restitution: per-pair banked compression budget, released as a
323 // budget-capped separation-velocity target during unloading (see updateRestitutionBankKokkos /
324 // PGSManifoldSweep). Off (empty views) the sweeps run the per-substep Newton path verbatim.
325 const bool poisson = usePGS && P.restitutionModel == 1;
326 const Kokkos::View<float*, CpMem> bankV = poisson ? P.restBank : Kokkos::View<float*, CpMem>();
327 const Kokkos::View<float*, CpMem> relV = poisson ? P.restRel : Kokkos::View<float*, CpMem>();
328 const Kokkos::View<const unsigned char*, CpMem> persC =
329 poisson ? Kokkos::View<const unsigned char*, CpMem>(P.manifoldPersistent)
330 : Kokkos::View<const unsigned char*, CpMem>();
331 const Kokkos::View<const float*, CpMem> vpkC =
332 poisson ? Kokkos::View<const float*, CpMem>(P.restVPeak)
333 : Kokkos::View<const float*, CpMem>();
334 const Kokkos::View<const unsigned char*, CpMem> grdC =
335 poisson ? Kokkos::View<const unsigned char*, CpMem>(P.groundedLevel)
336 : Kokkos::View<const unsigned char*, CpMem>();
337 const Kokkos::View<float*, CpMem> orphV = poisson ? P.bodyOrphan : Kokkos::View<float*, CpMem>();
338 const Kokkos::View<const float*, CpMem> orphPk =
339 poisson ? Kokkos::View<const float*, CpMem>(P.bodyOrphanVPeak)
340 : Kokkos::View<const float*, CpMem>();
341 // A/B measurement toggles for the Poisson channel (default: Newton alive + symmetric release —
342 // the measured-best config on the 25k Dosta impact).
343 static const bool restNewtonOff = [] {
344 const char* e2 = std::getenv("PECLET_DEM_REST_NEWTON_OFF");
345 return e2 && std::atoi(e2) != 0;
346 }();
347 static const bool restOneSided = [] {
348 const char* e2 = std::getenv("PECLET_DEM_REST_ONESIDED");
349 return e2 && std::atoi(e2) != 0;
350 }();
351 if (usePGS) {
352 if (poisson && P.prevPairCount > 0) { // reset the prev-ledger survival flags for the gather
353 auto mt = Kokkos::subview(P.prevMatched, Kokkos::pair<int, int>(0, P.prevPairCount));
354 Kokkos::deep_copy(mt, static_cast<unsigned char>(0));
355 }
359 P.restBank, P.restVPeak,
360 poisson ? P.prevMatched : Kokkos::View<unsigned char*, CpMem>());
361 if (poisson) {
362 { // per-substep release accumulator starts from zero every substep
363 auto rr = Kokkos::subview(P.restRel, Kokkos::pair<int, int>(0, nm));
364 Kokkos::deep_copy(rr, 0.0f);
365 }
366 // Orphan transfer: age the body accounts (owned range; MPI ghosts are mirrored), then
367 // settle dead pairs' remaining budgets onto their endpoint bodies. Under MPI the pair-key
368 // identities are gids, so the scatter resolves them through a sorted gid -> slot map built
369 // over owned + ghost slots (a ghost-side credit is overwritten by the next owner mirror —
370 // the owner's redundant ledger copy applies the same credit authoritatively).
371 decayBodyOrphanKokkos(P.bodyOrphan, P.bodyOrphanVPeak, P.numReal, 2.0f * P.dt * gMagP);
372 if (P.prevPairCount > 0) {
373 Kokkos::View<const int*, CpMem> gidSorted, slotSorted;
374 if constexpr (Hooks::distributed) {
375 Kokkos::View<int*, CpMem> gs(
376 Kokkos::view_alloc(space, "peclet::dem::orphan_gids", Kokkos::WithoutInitializing),
377 nBodies);
378 Kokkos::View<int*, CpMem> ss(
379 Kokkos::view_alloc(space, "peclet::dem::orphan_slots", Kokkos::WithoutInitializing),
380 nBodies);
381 auto gid = P.gid;
382 Kokkos::parallel_for(
383 "peclet::dem::orphan_gid_map", Kokkos::RangePolicy<CpExec>(space, 0, nBodies),
384 KOKKOS_LAMBDA(int i) {
385 gs(i) = gid(i);
386 ss(i) = i;
387 });
388 Kokkos::Experimental::sort_by_key(space, gs, ss);
389 gidSorted = gs;
390 slotSorted = ss;
391 }
392 scatterOrphanBanksKokkos(Kokkos::View<const unsigned long long*, CpMem>(P.prevPairKeys),
393 Kokkos::View<const float*, CpMem>(P.prevRestBank),
394 Kokkos::View<const float*, CpMem>(P.prevRestVPeak),
395 Kokkos::View<const unsigned char*, CpMem>(P.prevMatched),
396 P.prevPairCount, Kokkos::View<const float*, CpMem>(P.invMass),
397 P.bodyOrphan, P.bodyOrphanVPeak, gidSorted, slotSorted);
398 }
399 }
403 nBodies, /*sweeps*/ 8, /*decay*/ 8);
404 // STAGED SOLVE (Guendelman): the main sweeps are fully momentum-conserving (side flags all
405 // zero) -- ballistic impact, discharge and shear see correct physics. One-sided grounding is
406 // reserved for the STABILIZATION pass below, which runs only if the main sweeps leave an
407 // unconverged residual (a deep column mid-collapse that symmetric GS cannot arrest within the
408 // iteration budget).
409 {
410 auto flags = Kokkos::subview(P.sideFlags, Kokkos::pair<int, int>(0, nm));
411 Kokkos::deep_copy(flags, static_cast<unsigned char>(0));
412 }
414 P.vt0);
417 // The warm impulses just moved every owned AND ghost body: re-publish the owners' velocities
418 // so the first sweep reads a consistent ghost state.
419 if constexpr (Hooks::distributed)
420 hooks.syncVelocities(P);
421 }
422 // Restitution threshold ~ the speed one substep of free fall gains: below it a contact is
423 // RESTING and bounces with e=0 (see solveVelocityKokkos — dense-pile energy-bomb guard).
424 const float vRest = 2.0f * P.dt * gMagP;
425 // One PGS velocity iteration (async residual zero + full colour sweep). Captured as a CUDA
426 // graph and replayed per iteration on the single-GPU path: the step is host-submission-bound
427 // (measured 3,300 launches / ~11 ms per step at 25k), and replay collapses each iteration's
428 // launch storm into one. The residual readback between replays is unchanged, so the adaptive
429 // stop — and the physics — are bit-identical to the submission path.
430 auto emitVelIter = [&] {
431 Kokkos::deep_copy(space, P.maxApproach, 0.0f);
433 P.manifolds, nm, P.manifoldColor, numColors, P.invMass, P.invInertia, P.quat, P.velPred,
435 P.lambdaAcc, P.vn0, Kokkos::View<const unsigned char*, CpMem>(P.sideFlags), P.lambdaT,
437 Kokkos::View<const float*, CpMem>(P.posImpulse), {}, bankV, relV, persC, vpkC, gHat, grdC,
438 restNewtonOff, restOneSided, orphV, orphPk, velPermC, velOffsP, velFusedP);
439 };
440 // Device-side iteration loop (CUDA, single-rank): the whole adaptive velocity loop as ONE
441 // kernel — same sweeps, same residual, same stop; the per-iteration readback and the graph
442 // capture disappear. The final residual stays in P.maxApproach for the stabilization trigger.
443 bool velLoopDone = false;
444 if constexpr (!Hooks::distributed) {
445 if (usePGS && P.velocityUseGS && velLeftover == 0 && velFusedP) {
446 const FusedLoopSpec spec{P.velocityIterations, 0.02f * vRest, false};
447 velLoopDone = solveVelocityPGSKokkos(
448 P.manifolds, nm, P.manifoldColor, numColors, P.invMass, P.invInertia, P.quat, P.velPred,
450 P.lambdaAcc, P.vn0, Kokkos::View<const unsigned char*, CpMem>(P.sideFlags), P.lambdaT,
452 Kokkos::View<const float*, CpMem>(P.posImpulse), {}, bankV, relV, persC, vpkC, gHat, grdC,
453 restNewtonOff, restOneSided, orphV, orphPk, velPermC, velOffsP, velFusedP, &spec);
454 }
455 }
456 bool graphVel = false;
457 CudaIterGraph gVel;
458 if (!velLoopDone && usePGS && P.velocityUseGS && velLeftover == 0) {
459 PECLET_DEM_GRAPH_LOOP(graphVel, gVel, emitVelIter, P.graphCache[0])
460 }
461 for (int it = 0; !velLoopDone && it < P.velocityIterations; ++it) {
462 if (legacyFriction)
465 if (P.velocityUseGS) {
466 if (usePGS) {
467 if (graphVel)
468 gVel.launch(space);
469 else
470 emitVelIter();
471 } else {
472 Kokkos::deep_copy(space, P.maxApproach, 0.0f);
476 }
477 // Colour-mask saturation fallback (interpenetration degree > 62): the manifolds the colouring
478 // could not place are applied with the count-averaged Jacobi pass — stable, and only active
479 // in pathologically crushed regions; without it those manifolds were silently skipped and
480 // deep overlap could never resolve.
481 if (velLeftover > 0) {
485 Kokkos::View<const int*, CpMem>(P.manifoldColor), -1);
488 }
489 // Adaptive stop. One-shot GS: end once no pair approaches above the resting threshold. PGS:
490 // maxApproach records the largest APPLIED correction, and meaningful increments are ~g dt
491 // (they propagate a chain one link per sweep), so the tolerance must sit well below vRest or
492 // the stop starves deep-chain convergence permanently (measured: a 113-layer pile plateaued
493 // at vz ~ -5 with the vRest stop). Once the warm-started network is converged the first
494 // sweep's correction is ~0 and the loop still exits immediately. Distributed: the residual
495 // is Allreduce-MAXed so every rank takes the same break (collective-refresh consistency).
496 if (hooks.allMax(readFloat(P.maxApproach)) <= (usePGS ? 0.02f * vRest : vRest))
497 break;
498 } else {
504 }
505 if constexpr (Hooks::distributed) {
506 if (hooks.syncPoint(it))
507 hooks.syncVelocities(P);
508 }
509 }
510 if constexpr (Hooks::distributed)
511 hooks.syncVelocities(P); // final owner->ghost refresh of the main velocity phase
512 // STABILIZATION PASS: if the symmetric sweeps could not drain the residual (a collapsing
513 // column needs ~one sweep per layer to carry its weight to the floor -- unaffordable), arrest
514 // the remaining quasi-static approach with grounded one-sided sweeps. In dynamic scenes the
515 // residual is below the threshold and this pass never runs, so impact/discharge/shear keep
516 // pure momentum-conserving physics. (PECLET_DEM_SYMMETRIC_PGS=1 disables the pass -- sandbox
517 // A/B toggle.)
518 if (usePGS) {
519 const float vRestS = 2.0f * P.dt * gMagP;
520 const int smode = P.stabilizationMode;
521 if (smode != 0 && hooks.allMax(readFloat(P.maxApproach)) > vRestS) {
522 if (smode == 1) { // ONE-SIDED grounded pass (default): held-lower-side impulses
524 Kokkos::View<const unsigned char*, CpMem>(P.manifoldPersistent),
525 Kokkos::View<const unsigned char*, CpMem>(P.groundedLevel),
526 P.posPred, P.velPred, gHat, 8.0f * P.dt * gMagP, P.sideFlags, P.vn0,
527 8.0f * P.dt * gMagP);
528 // Arrest budget: 2x the main budget -- the pass must out-pace a violent collapse, and it
529 // only ever runs when the residual says one is happening (adaptive stop ends it early).
530 auto emitOsIter = [&] {
531 Kokkos::deep_copy(space, P.maxApproach, 0.0f);
533 P.manifolds, nm, P.manifoldColor, numColors, P.invMass, P.invInertia, P.quat,
535 P.maxApproach, P.lambdaAcc, P.vn0,
536 Kokkos::View<const unsigned char*, CpMem>(P.sideFlags), P.lambdaT, P.frictionDynamic,
537 P.vt0, P.restitutionTangent, Kokkos::View<const float*, CpMem>(P.posImpulse), {},
538 bankV, relV, persC, vpkC, gHat, grdC, restNewtonOff, restOneSided, orphV, orphPk,
539 velPermC, velOffsP, velFusedP);
540 };
541 bool osLoopDone = false;
542 if constexpr (!Hooks::distributed) {
543 if (velLeftover == 0 && velFusedP) {
544 const FusedLoopSpec spec{2 * P.velocityIterations, vRestS, false};
545 osLoopDone = solveVelocityPGSKokkos(
546 P.manifolds, nm, P.manifoldColor, numColors, P.invMass, P.invInertia, P.quat,
548 P.maxApproach, P.lambdaAcc, P.vn0,
549 Kokkos::View<const unsigned char*, CpMem>(P.sideFlags), P.lambdaT,
551 Kokkos::View<const float*, CpMem>(P.posImpulse), {}, bankV, relV, persC, vpkC, gHat,
552 grdC, restNewtonOff, restOneSided, orphV, orphPk, velPermC, velOffsP, velFusedP,
553 &spec);
554 }
555 }
556 bool graphOs = false;
557 CudaIterGraph gOs;
558 if (!osLoopDone && velLeftover == 0) {
559 PECLET_DEM_GRAPH_LOOP(graphOs, gOs, emitOsIter, P.graphCache[1])
560 }
561 for (int it = 0; !osLoopDone && it < 2 * P.velocityIterations; ++it) {
562 if (graphOs)
563 gOs.launch(space);
564 else
565 emitOsIter();
566 if (hooks.allMax(readFloat(P.maxApproach)) <= vRestS)
567 break;
568 if constexpr (Hooks::distributed) {
569 if (hooks.syncPoint(it))
570 hooks.syncVelocities(P);
571 }
572 }
573 } else if (smode == 2) {
574 // MULTILEVEL (GraphMG) pass: never deletes momentum, only accelerates its transport.
575 // Greedy pairwise aggregation over the quasi-static contact graph builds super-bodies
576 // (summed mass, momentum-weighted velocity); the fine manifolds crossing aggregate
577 // boundaries are re-solved with the AGGREGATE masses -- the supported chain's genuinely
578 // huge inertia plays the role the held lower side faked, so a wall contact drains a
579 // whole column's momentum in one coarse impulse while every impulse stays symmetric.
580 // Ballistic pairs (|vn0| > qsThr) never aggregate: an impactor keeps its fine-level,
581 // momentum-conserving physics and its rebound. Coarse lambda shares the fine
582 // accumulator, so the force-network ledger stays consistent for next substep's warm
583 // start and the friction cone's Coulomb bound. See solver_multilevel.hpp. Distributed:
584 // the hierarchy is built rank-locally over owned + ghost bodies (aggregates never cross
585 // a rank boundary beyond the ghost band; the syncEvery refresh reconciles).
586 const float qsThr = 8.0f * P.dt * gMagP;
587 // Eligibility gates (mldetail::kGate*): slip is the production default -- it keeps the
588 // pass off sustained shear (silo bulk) without starving a crushing bed's aggregation.
589 // PECLET_DEM_ML_GATES overrides the mask for A/B measurement.
590 static const int mlGates = [] {
591 const char* e = std::getenv("PECLET_DEM_ML_GATES");
592 return e ? std::atoi(e) : mldetail::kGateSlip;
593 }();
595 P.mlVelG0, P.mlMassG, P.mlGrp, P.mlMate};
597 P.manifolds, nm, P.realIndices, Kokkos::View<const int*, CpMem>(P.manifoldColor),
598 Kokkos::View<const float*, CpMem>(P.vn0), Kokkos::View<const float* [3], CpMem>(P.vt0),
599 Kokkos::View<const unsigned char*, CpMem>(P.manifoldPersistent), P.posPred, gHat,
600 Kokkos::View<const float*, CpMem>(P.invMass), qsThr, mlGates, nBodies, S, P.bodyWinner,
601 P.bodyColorMask, /*excludeImmovable*/ sleepOn,
602 sleepOn ? Kokkos::View<const unsigned char*, CpMem>(P.asleep)
603 : Kokkos::View<const unsigned char*, CpMem>());
604 // Dense per-(level, colour) buckets, built once per hierarchy (see solver_multilevel.hpp).
605 std::vector<std::vector<int>> mlOffs;
606 if (H.numLevels > 0) {
607 if (static_cast<int>(P.mlBucketPerm.extent(0)) < H.numLevels * nm)
608 Kokkos::realloc(Kokkos::WithoutInitializing, P.mlBucketPerm, H.numLevels * nm);
610 }
611 // Fused coarse cycle (CUDA): the whole per-iteration coarse leg as ONE kernel.
612 const MlFusedCtx mlFused =
613 (H.numLevels > 0 && wantFused)
614 ? demMakeMlFusedCtx(space, H, mlOffs, nm, nBodies, /*coarseSweeps*/ 2, P.mlOffsDev,
615 P.fusedBar)
616 : MlFusedCtx{};
617 const MlFusedCtx* mlFusedP = mlFused.maxWork > 0 ? &mlFused : nullptr;
618 // The loop's stop criterion is the QUASI-STATIC residual (fine corrections on contacts
619 // with |vn0| <= 4 vRest, plus every coarse correction): the fine sweep's full residual
620 // is dominated by ballistic contacts in flowing scenes (a discharging silo never gets
621 // below vRest there), and gating on it burns the full budget of extra fine sweeps every
622 // substep -- an over-convergence brake on discharge (the escalate effect, measured -7%).
623 // The pass exists to converge the quasi-static network; once that is done, it is done.
624 // One stabilization iteration (async QS-residual zero + fine sweep + coarse cycle),
625 // graph-captured on the single-GPU path — this loop is THE launch storm (up to 16
626 // iterations x [colour sweeps + per-(level, colour) coarse kernels] per substep).
627 auto emitMlIter = [&] {
628 Kokkos::deep_copy(space, P.maxApproachQS, 0.0f);
630 P.manifolds, nm, P.manifoldColor, numColors, P.invMass, P.invInertia, P.quat,
632 P.maxApproach, P.lambdaAcc, P.vn0,
633 Kokkos::View<const unsigned char*, CpMem>(P.sideFlags), P.lambdaT, P.frictionDynamic,
634 P.vt0, P.restitutionTangent, Kokkos::View<const float*, CpMem>(P.posImpulse),
635 P.maxApproachQS, bankV, relV, persC, vpkC, gHat, grdC, restNewtonOff, restOneSided,
636 orphV, orphPk, velPermC, velOffsP, velFusedP);
637 if (H.numLevels > 0)
639 P.manifolds, nm, P.realIndices, Kokkos::View<const float*, CpMem>(P.invMass),
640 P.velPred, P.lambdaAcc, P.maxApproachQS, nBodies, H, S,
641 /*coarseSweeps*/ 2, Kokkos::View<const float*, CpMem>(relV), &mlOffs,
642 Kokkos::View<const int*, CpMem>(P.mlBucketPerm), mlFusedP);
643 };
644 // Device-side stabilization loop (CUDA, single-rank): fine sweep + coarse cycle +
645 // adaptive stop, all iterations in ONE kernel (see demFusedMlLoopK).
646 bool mlLoopDone = false;
647#ifdef KOKKOS_ENABLE_CUDA
648 if constexpr (!Hooks::distributed) {
649 if (velLeftover == 0 && velFusedP) {
650 if (H.numLevels > 0 && mlFusedP) {
655 Kokkos::View<const unsigned char*, CpMem>(P.sideFlags), P.lambdaT,
657 Kokkos::View<const float*, CpMem>(P.posImpulse), bankV, relV, persC, vpkC, gHat,
658 grdC, restNewtonOff, restOneSided, orphV, orphPk);
659 mlLoopDone = demLaunchFusedMlLoop(
660 space, fStab, velPermC, *velFusedP, numColors, P.manifolds, P.realIndices,
661 Kokkos::View<const float*, CpMem>(P.invMass), P.velPred, P.lambdaAcc,
662 P.maxApproachQS, Kokkos::View<const float*, CpMem>(relV), S,
663 Kokkos::View<const int*, CpMem>(P.mlBucketPerm), *mlFusedP,
664 2 * P.velocityIterations, vRestS);
665 } else if (H.numLevels == 0) {
666 // aggregation found nothing: the loop is plain fine sweeps on the QS residual
667 const FusedLoopSpec spec{2 * P.velocityIterations, vRestS, false};
668 mlLoopDone = solveVelocityPGSKokkos(
669 P.manifolds, nm, P.manifoldColor, numColors, P.invMass, P.invInertia, P.quat,
671 P.maxApproach, P.lambdaAcc, P.vn0,
672 Kokkos::View<const unsigned char*, CpMem>(P.sideFlags), P.lambdaT,
674 Kokkos::View<const float*, CpMem>(P.posImpulse), P.maxApproachQS, bankV, relV,
675 persC, vpkC, gHat, grdC, restNewtonOff, restOneSided, orphV, orphPk, velPermC,
676 velOffsP, velFusedP, &spec);
677 }
678 }
679 }
680#endif
681 bool graphMl = false;
682 CudaIterGraph gMl;
683 if (!mlLoopDone && velLeftover == 0) {
684 PECLET_DEM_GRAPH_LOOP(graphMl, gMl, emitMlIter, P.graphCache[2])
685 }
686 for (int it = 0; !mlLoopDone && it < 2 * P.velocityIterations; ++it) {
687 if (graphMl)
688 gMl.launch(space);
689 else
690 emitMlIter();
691 if (hooks.allMax(readFloat(P.maxApproachQS)) <= vRestS)
692 break;
693 if constexpr (Hooks::distributed) {
694 if (hooks.syncPoint(it))
695 hooks.syncVelocities(P);
696 }
697 }
698 } else if (smode == 4) {
699 // ORDERED (level-ordered symmetric sweeps; measurement mode): fresh height-from-floor
700 // BFS levels order the manifolds bottom-up + top-down. Fully symmetric, but a pairwise
701 // inelastic impulse only EQUALIZES velocities, so a deep column still cools one halving
702 // per cycle -- measured insufficient on the statics battery (kept for A/B comparison
703 // against the multilevel pass).
705 nBodies);
706 std::vector<std::pair<int, int>> buckets;
708 P.manifolds, nm, P.realIndices, Kokkos::View<const int*, CpMem>(P.manifoldColor),
709 Kokkos::View<const int*, CpMem>(P.heightLevel), P.levelKey, P.levelPerm, buckets);
710 const PGSManifoldSweep sweep{P.manifolds,
711 P.invMass,
712 P.invInertia,
713 P.quat,
714 P.velPred,
715 P.angVelPred,
716 P.realIndices,
717 P.growthRate,
719 vRestS,
720 P.maxApproach,
721 P.maxApproach,
722 P.lambdaAcc,
723 P.vn0,
724 Kokkos::View<const unsigned char*, CpMem>(P.sideFlags),
725 P.lambdaT,
727 P.vt0,
729 Kokkos::View<const float*, CpMem>(P.posImpulse),
730 bankV,
731 relV,
732 persC,
733 vpkC,
734 gHat,
735 grdC,
736 restNewtonOff,
737 restOneSided,
738 orphV,
739 orphPk};
740 for (int it = 0; it < 2 * P.velocityIterations; ++it) {
741 Kokkos::deep_copy(P.maxApproach, 0.0f);
742 solveVelocityPGSBucketsKokkos(sweep, Kokkos::View<const int*, CpMem>(P.levelPerm),
743 buckets, /*topDown*/ false);
744 solveVelocityPGSBucketsKokkos(sweep, Kokkos::View<const int*, CpMem>(P.levelPerm),
745 buckets, /*topDown*/ true);
746 if (hooks.allMax(readFloat(P.maxApproach)) <= vRestS)
747 break;
748 if constexpr (Hooks::distributed) {
749 if (hooks.syncPoint(it))
750 hooks.syncVelocities(P);
751 }
752 }
753 } else if (smode == 3) {
754 // ESCALATION (diagnostic/fallback): keep running plain symmetric colored sweeps until
755 // the residual drains or the 256-sweep cap -- provably correct physics, and the sweep
756 // count it needs bounds what the ordered pass must deliver.
757 for (int it = 0; it < 256; ++it) {
758 Kokkos::deep_copy(P.maxApproach, 0.0f);
760 P.manifolds, nm, P.manifoldColor, numColors, P.invMass, P.invInertia, P.quat,
762 P.maxApproach, P.lambdaAcc, P.vn0,
763 Kokkos::View<const unsigned char*, CpMem>(P.sideFlags), P.lambdaT, P.frictionDynamic,
764 P.vt0, P.restitutionTangent, Kokkos::View<const float*, CpMem>(P.posImpulse), {},
765 bankV, relV, persC, vpkC, gHat, grdC, restNewtonOff, restOneSided, orphV, orphPk,
766 velPermC, velOffsP, velFusedP);
767 if (hooks.allMax(readFloat(P.maxApproach)) <= vRestS)
768 break;
769 if constexpr (Hooks::distributed) {
770 if (hooks.syncPoint(it))
771 hooks.syncVelocities(P);
772 }
773 }
774 }
775 if constexpr (Hooks::distributed)
776 hooks.syncVelocities(P); // final refresh of the stabilization phase
777 }
778 }
779 // Poisson bookkeeping runs once per substep on the FINAL velocity state (after every phase and
780 // ghost refresh): bank this substep's kinetic compression, deduct what was returned/released.
781 if (poisson)
784 P.growthRate, P.restitutionNormal, 2.0f * P.dt * gMagP, P.vn0, P.lambdaAcc,
785 Kokkos::View<const float*, CpMem>(P.restRel), P.restBank, P.restVPeak);
786 if (usePGS) { // save the converged force network for next substep's warm start
788 P.pairKeys, P.lambdaAcc, P.lambdaT, Kokkos::View<const float*, CpMem>(P.restBank),
789 Kokkos::View<const float*, CpMem>(P.restVPeak), P.prevPairKeys, P.prevLambda, P.prevLambdaT,
791 // Carry the per-manifold colour by pair key (single-GPU incremental
792 // colouring warm start; empty on the distributed path).
793 Hooks::distributed ? Kokkos::View<const int*, CpMem>()
794 : Kokkos::View<const int*, CpMem>(P.manifoldColor),
795 Hooks::distributed ? Kokkos::View<int*, CpMem>() : P.prevManifoldColor);
796 P.prevPairCount = nm;
797 }
798 if (legacyFriction) {
802 P.deltaAngVel);
804 if constexpr (Hooks::distributed)
805 hooks.syncVelocities(P); // publish the friction velocity update to the ghosts
806 }
807
809 P.angVelPred, P.posPred, P.quatPred, P.angVel, P.dt);
810 if constexpr (Hooks::distributed)
811 hooks.syncPositions(P);
812
813 // Colour the contact graph ONCE (topology-only; reused across the position sweeps), then remove
814 // overlap with colored Gauss–Seidel (true sequential projection, no count-averaging softening).
815 int posLeftover = 0;
816 int numPosColors = 0;
817 // Incremental position colouring, same single-GPU PGS gate + creep policy as the velocity path.
818 const bool incrPosColor =
819 usePersistPre && P.velocityUseGS && !Hooks::distributed && !incrColorOff;
820 bool posDidFull = false;
821 if (P.velocityUseGS) {
822 if (incrPosColor) {
823 posDidFull = (P.posPrevContactCount <= 0);
824 numPosColors = colorContactsIncrementalKokkos(
825 P.contacts, nc, P.numParticles,
826 Kokkos::View<const unsigned long long*, CpMem>(P.prevContactKeys),
827 Kokkos::View<const int*, CpMem>(P.prevContactColor), P.posPrevContactCount,
828 P.contactColor, P.contactKeys, P.bodyWinner, P.bodyColorMask, posLeftover,
829 /*forceFull*/ posDidFull, cSleep);
830 if (!posDidFull && P.posLastFullColors > 0 &&
831 numPosColors > (P.posLastFullColors * 13) / 10) {
832 numPosColors = colorContactsIncrementalKokkos(
833 P.contacts, nc, P.numParticles,
834 Kokkos::View<const unsigned long long*, CpMem>(P.prevContactKeys),
835 Kokkos::View<const int*, CpMem>(P.prevContactColor), P.posPrevContactCount,
836 P.contactColor, P.contactKeys, P.bodyWinner, P.bodyColorMask, posLeftover,
837 /*forceFull*/ true, cSleep);
838 posDidFull = true;
839 }
840 if (posDidFull)
841 P.posLastFullColors = numPosColors;
842 // Commit this substep's (contact key, colour) for next substep's warm gather.
843 commitContactColorKokkos(Kokkos::View<const unsigned long long*, CpMem>(P.contactKeys),
844 Kokkos::View<const int*, CpMem>(P.contactColor), P.prevContactKeys,
846 P.posPrevContactCount = nc;
847 } else {
848 numPosColors = colorContactsKokkos(P.contacts, nc, P.numParticles, P.contactColor,
849 P.bodyWinner, P.bodyColorMask, posLeftover, cSleep);
850 }
851 }
852 // Overlap resolved once the deepest penetration falls below ~0.01% of a particle radius.
853 const float posTol = 1e-4f * P.baseRadius * P.globalScale;
854 {
855 auto pc = Kokkos::subview(P.posLambdaContact, Kokkos::pair<int, int>(0, nc));
856 Kokkos::deep_copy(pc, 0.0f);
857 }
858 // Dense colour buckets + fused sweep for the position projection (same precedent as the
859 // velocity sweeps: colour classes are body-disjoint => bit-identical; uncoloured leftovers
860 // keep the Jacobi fallback below).
861 std::vector<int> posOffs;
862 const bool posBuckets = P.velocityUseGS && nc > 0 && numPosColors > 0;
863 if (posBuckets)
864 buildColorBucketsKokkos(Kokkos::View<const int*, CpMem>(P.contactColor), nc, numPosColors,
865 P.posPerm, P.bucketCursor, posOffs);
866 const Kokkos::View<const int*, CpMem> posPermC(P.posPerm);
867 const std::vector<int>* posOffsP = posBuckets ? &posOffs : nullptr;
868 const FusedSweepCtx posFused = (posBuckets && wantFused)
869 ? demMakeFusedCtx(space, posOffs, P.posOffsDev, P.fusedBar)
870 : FusedSweepCtx{};
871 const FusedSweepCtx* posFusedP = posFused.maxBucket > 0 ? &posFused : nullptr;
872 // One position iteration (async residual zero + colored overlap sweep), graph-captured on
873 // the single-GPU path like the velocity loops.
874 auto emitPosIter = [&] {
875 Kokkos::deep_copy(space, P.maxOverlap, 0.0f);
878 posPermC, posOffsP, posFusedP);
879 };
880 // Device-side position loop (CUDA, single-rank): all overlap-projection iterations + the
881 // adaptive stop in ONE kernel. Leftover contacts (colour-mask saturation) need the host
882 // loop's per-iteration Jacobi fallback, so they keep the launch path.
883 bool posLoopDone = false;
884 if constexpr (!Hooks::distributed) {
885 if (P.velocityUseGS && posLeftover == 0 && posFusedP) {
886 const FusedLoopSpec spec{P.positionIterations, posTol, true};
887 posLoopDone = solvePositionColoredGSKokkos(
888 P.contacts, nc, P.contactColor, numPosColors, P.invMass, P.posPred, P.quatPred, P.quat,
889 P.invInertia, P.maxOverlap, P.posLambdaContact, posPermC, posOffsP, posFusedP, &spec);
890 }
891 }
892 bool graphPos = false;
893 CudaIterGraph gPos;
894 if (!posLoopDone && P.velocityUseGS && posLeftover == 0) {
895 PECLET_DEM_GRAPH_LOOP(graphPos, gPos, emitPosIter, P.graphCache[3])
896 }
897 for (int it = 0; !posLoopDone && it < P.positionIterations; ++it) {
898 if (P.velocityUseGS) {
899 if (graphPos)
900 gPos.launch(space);
901 else
902 emitPosIter();
903 // Colour-mask saturation fallback: contacts the colouring could not place (degree > 62 in
904 // crushed regions) get the count-averaged Jacobi projection so deep overlap still resolves.
905 if (posLeftover > 0) {
908 Kokkos::View<const int*, CpMem>(P.contactColor), -1);
911 }
912 // Adaptive stop: end once no contact overlaps by more than posTol. Fixed positionIterations
913 // is the cap. Distributed: Allreduce-MAXed so all ranks break together.
914 if (hooks.allMax(readFloat(P.maxOverlap)) < posTol)
915 break;
916 } else {
921 }
922 if constexpr (Hooks::distributed) {
923 if (hooks.syncPoint(it))
924 hooks.syncPositions(P);
925 }
926 }
927 if constexpr (Hooks::distributed)
928 hooks.syncPositions(P); // final owner->ghost refresh of the position phase
929 // Position-channel Coulomb-bound carry (PGS path): next substep's friction cone sees
930 // mu * (velocity-impulse channel + this position-channel load). Without it a jostled bed's
931 // bound under-counts the true normal force and stick leaks (measured: 99% sliding wall
932 // contacts in the benchmark drum while the Hertz reference sticks).
933 if (usePGS && nm > 0)
936 (void)space;
937}
938
939} // namespace peclet::dem
940
941#endif // DEM_SOLVE_DRIVER_HPP
dem — portable (ArborX) broad-phase, the Kokkos-native replacement for the CUDA-only cuBQL broad-phas...
dem — portable (Kokkos) contact->manifold reduction, replacing the thrust-based reduce_contacts_to_ma...
void commitPosImpulseKokkos(Kokkos::View< const float *, CpMem > posLambdaContact, int numContacts, Kokkos::View< const int *, CpMem > contactSlot, Kokkos::View< const ManifoldC *, CpMem > manifolds, int numManifolds, Kokkos::View< const unsigned long long *, CpMem > keys, Kokkos::View< const unsigned long long *, CpMem > prevKeysSorted, int prevCount, float dt, Kokkos::View< float *, CpMem > scratchManifold, Kokkos::View< float *, CpMem > prevPosImpulse)
After the position solve: convert the per-contact positional lambdas into an impulse- equivalent per ...
void multilevelCoarseCycleKokkos(Kokkos::View< const ManifoldC *, CpMem > manifolds, int numManifolds, Kokkos::View< const int *, CpMem > realIdx, Kokkos::View< const float *, CpMem > invMass, Kokkos::View< float *[3], CpMem > velPred, Kokkos::View< float *, CpMem > lambdaAcc, Kokkos::View< float, CpMem > maxApproach, int numReal, const ContactHierarchy &H, MlScratch &S, int coarseSweeps, Kokkos::View< const float *, CpMem > restRel={}, const std::vector< std::vector< int > > *bkOffs=nullptr, Kokkos::View< const int *, CpMem > bkPerm={}, const MlFusedCtx *fused=nullptr)
void warmStartApplyKokkos(Kokkos::View< const ManifoldC *, CpMem > manifolds, int numManifolds, Kokkos::View< const float *, CpMem > invMass, Kokkos::View< const float *[3], CpMem > invInertia, Kokkos::View< const float *[4], CpMem > quat, Kokkos::View< float *[3], CpMem > velPred, Kokkos::View< float *[3], CpMem > angVelPred, Kokkos::View< const int *, CpMem > realIdx, Kokkos::View< const float *, CpMem > warmP, Kokkos::View< float *[3], CpMem > warmT)
Apply the warm-start impulses up front (order-independent: fixed impulses, atomic adds).
void commitContactColorKokkos(Kokkos::View< const unsigned long long *, CpMem > keys, Kokkos::View< const int *, CpMem > color, Kokkos::View< unsigned long long *, CpMem > prevKeys, Kokkos::View< int *, CpMem > prevColor, Kokkos::View< int *, CpMem > perm, int numContacts)
Commit this substep's per-contact (key, colour) sorted by key, for next substep's warm gather.
void updateRestitutionBankKokkos(Kokkos::View< const ManifoldC *, CpMem > manifolds, int numManifolds, Kokkos::View< const float *, CpMem > invMass, Kokkos::View< const float *[3], CpMem > invInertia, Kokkos::View< const float *[4], CpMem > quat, Kokkos::View< const float *[3], CpMem > velPred, Kokkos::View< const float *[3], CpMem > angVelPred, Kokkos::View< const int *, CpMem > realIdx, float growthRate, float restitutionNormal, float restVelThreshold, Kokkos::View< const float *, CpMem > vn0, Kokkos::View< const float *, CpMem > lambdaAcc, Kokkos::View< const float *, CpMem > restRel, Kokkos::View< float *, CpMem > restBank, Kokkos::View< float *, CpMem > restVPeak)
Event-level (Poisson) restitution bookkeeping, once per substep AFTER all velocity phases (restitutio...
void computeHeightLevelsKokkos(Kokkos::View< const ManifoldC *, CpMem > manifolds, int numManifolds, Kokkos::View< const int *, CpMem > realIdx, Kokkos::View< const float *[3], CpMem > posPred, F3 gHat, Kokkos::View< int *, CpMem > heights, int numReal)
void applyVelocityDeltasAveragedKokkos(int n, V3 velPred, V3 angVelPred, V3 deltaVel, V3 deltaAngVel, Vi velCounts)
Apply the accumulated velocity deltas AVERAGED by the per-body manifold count — the velocity- solve t...
void computePlaneLoadKokkos(Kokkos::View< ContactC *, CpMem > contacts, int numContacts, Kokkos::View< const float *, CpMem > invMass, Kokkos::View< const float *[3], CpMem > invInertia, Kokkos::View< const float *[3], CpMem > velPred, Kokkos::View< const float *[3], CpMem > angVelPred, FrManifoldCounts planeFriction)
Plane (idB<0) one-shot loads.
int colorManifoldsIncrementalKokkos(Kokkos::View< const ManifoldC *, CpMem > manifolds, int numManifolds, Kokkos::View< const int *, CpMem > realIdx, int numReal, Kokkos::View< const unsigned long long *, CpMem > prevKeys, Kokkos::View< const int *, CpMem > prevColor, int prevCount, Kokkos::View< int *, CpMem > mColor, Kokkos::View< long long *, CpMem > bodyWinner, Kokkos::View< std::uint64_t *, CpMem > bodyMask, int &leftover, bool forceFull, Kokkos::View< const unsigned char *, CpMem > sleepMask={})
Incremental (warm-started) manifold colouring for the single-GPU PGS path.
void fillGidBaseKokkos(Vi gid, int n, int base)
gid(i) = base + i over [0, n) — the per-rank global-id re-base of the distributed step (namespace sco...
float maxOwnedRadius(const Particles &P)
Largest effective particle radius over the owned set (= max scale × globalScale, growth included).
void accumulateNormalImpulseKokkos(Kokkos::View< ContactC *, CpMem > contacts, int numContacts, Kokkos::View< const float *, CpMem > invMass, Kokkos::View< const float *[3], CpMem > invInertia, Kokkos::View< const float *[3], CpMem > velPred, Kokkos::View< const float *[3], CpMem > angVelPred, Kokkos::View< const int *, CpMem > realIdx, float growthRate)
Force-chain normal load, accumulated over the velocity iterations: contacts(idx).friction_lambda_n +=...
bool solvePositionColoredGSKokkos(Kokkos::View< const ContactC *, CpMem > contacts, int numContacts, Kokkos::View< const int *, CpMem > cColor, int numColors, Kokkos::View< const float *, CpMem > invMass, Kokkos::View< float *[3], CpMem > posPred, Kokkos::View< const float *[4], CpMem > quatPred, Kokkos::View< const float *[4], CpMem > quatStatic, Kokkos::View< const float *[3], CpMem > invInertia, Kokkos::View< float, CpMem > maxOverlap, Kokkos::View< float *, CpMem > posLambdaAcc={}, Kokkos::View< const int *, CpMem > colorPerm={}, const std::vector< int > *colorOffs=nullptr, const FusedSweepCtx *fused=nullptr, const FusedLoopSpec *loop=nullptr)
Colored Gauss–Seidel XPBD overlap solve: sweep the numColors colour classes in order,...
void scatterOrphanBanksKokkos(Kokkos::View< const unsigned long long *, CpMem > prevKeys, Kokkos::View< const float *, CpMem > prevRestBank, Kokkos::View< const float *, CpMem > prevRestVPeak, Kokkos::View< const unsigned char *, CpMem > matched, int prevCount, Kokkos::View< const float *, CpMem > invMass, Kokkos::View< float *, CpMem > orphan, Kokkos::View< float *, CpMem > orphanVPeak, Kokkos::View< const int *, CpMem > gidSorted={}, Kokkos::View< const int *, CpMem > slotSorted={})
Orphan transfer: previous-ledger entries NOT matched by any current manifold (their pair died this su...
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...
void computeVn0Kokkos(Kokkos::View< const ManifoldC *, CpMem > manifolds, int numManifolds, Kokkos::View< const float *[3], CpMem > velPred, Kokkos::View< const float *[3], CpMem > angVelPred, Kokkos::View< const int *, CpMem > realIdx, float growthRate, Kokkos::View< float *, CpMem > vn0, Kokkos::View< float *[3], CpMem > vt0)
-— Warm-started projected Gauss-Seidel (PGS) velocity solve -— Nonsmooth contact dynamics (Moreau-Jea...
Kokkos::View< int *, CpMem > Vi
int colorContactsKokkos(Kokkos::View< const ContactC *, CpMem > contacts, int numContacts, int numBodies, Kokkos::View< int *, CpMem > cColor, Kokkos::View< long long *, CpMem > bodyWinner, Kokkos::View< std::uint64_t *, CpMem > bodyMask, int &leftover, Kokkos::View< const unsigned char *, CpMem > sleepMask={})
Greedy graph-colour the contacts (raw bodyA/bodyB): no two contacts sharing a body get the same colou...
void solveContactFrictionKokkos(Kokkos::View< const ContactC *, CpMem > contacts, int numContacts, Kokkos::View< const float *, CpMem > invMass, Kokkos::View< const float *[3], CpMem > invInertia, Kokkos::View< const float *[3], CpMem > velPred, Kokkos::View< const float *[3], CpMem > angVelPred, Kokkos::View< const int *, CpMem > realIdx, FrManifoldCounts planeFriction, float frictionDynamic, Kokkos::View< float *[3], CpMem > deltaVel, Kokkos::View< float *[3], CpMem > deltaAngVel)
One count-averaged Coulomb friction sweep.
void buildColorBucketsKokkos(Kokkos::View< const int *, CpMem > colorOf, int n, int numColors, Kokkos::View< int *, CpMem > perm, Kokkos::View< int *, CpMem > cursor, std::vector< int > &offs)
Dense colour buckets (numColors <= 64): perm[offs[c] .
void applyVelocityDeltasKokkos(int n, V3 velPred, V3 angVelPred, V3 deltaVel, V3 deltaAngVel)
Add accumulated velocity/angular deltas onto the predicted velocity, then clear the delta buffers.
void solveVelocityKokkos(Kokkos::View< const ManifoldC *, CpMem > manifolds, int numManifolds, Kokkos::View< const float *, CpMem > invMass, Kokkos::View< const float *[3], CpMem > invInertia, Kokkos::View< const float *[4], CpMem > quat, Kokkos::View< const float *[3], CpMem > velPred, Kokkos::View< const float *[3], CpMem > angVelPred, Kokkos::View< const int *, CpMem > realIdx, float growthRate, float restitutionNormal, float restVelThreshold, Kokkos::View< float *[3], CpMem > deltaVel, Kokkos::View< float *[3], CpMem > deltaAngVel, Kokkos::View< int *, CpMem > velCounts, Kokkos::View< const int *, CpMem > onlyColor={}, int colorFilter=0, Kokkos::View< const unsigned char *, CpMem > persistent={}, Kokkos::View< const float *[3], CpMem > posPred={}, F3 gHat={}, Kokkos::View< const unsigned char *, CpMem > grounded={})
Accumulate normal-restitution velocity deltas for numManifolds manifolds.
void markPersistentManifoldsKokkos(Kokkos::View< const ManifoldC *, CpMem > manifolds, int numManifolds, Kokkos::View< const int *, CpMem > realIdx, Kokkos::View< const int *, CpMem > keyIdx, Kokkos::View< const unsigned long long *, CpMem > prevKeys, int prevCount, Kokkos::View< unsigned long long *, CpMem > outKeys, Kokkos::View< unsigned char *, CpMem > outFlags)
keyIdx maps a body slot to the identity the pair key is built from: the REAL index map on the single-...
void gatherWarmLambdaKokkos(Kokkos::View< const ManifoldC *, CpMem > manifolds, int numManifolds, Kokkos::View< const int *, CpMem > realIdx, Kokkos::View< const int *, CpMem > keyIdx, Kokkos::View< const unsigned long long *, CpMem > prevKeys, Kokkos::View< const float *, CpMem > prevLambda, Kokkos::View< const float *[3], CpMem > prevLambdaT, Kokkos::View< const float *, CpMem > prevPosImpulse, Kokkos::View< const float *, CpMem > prevRestBank, Kokkos::View< const float *, CpMem > prevRestVPeak, int prevCount, Kokkos::View< unsigned long long *, CpMem > outKeys, Kokkos::View< float *, CpMem > outWarm, Kokkos::View< float *[3], CpMem > outWarmT, Kokkos::View< float *, CpMem > outPosImpulse, Kokkos::View< float *, CpMem > outRestBank, Kokkos::View< float *, CpMem > outRestVPeak, Kokkos::View< unsigned char *, CpMem > outMatched={})
Warm-start gather for the PGS velocity solve: per manifold, write its pair key and look up the previo...
void solvePositionKokkos(Kokkos::View< const ContactC *, CpMem > contacts, int numContacts, Kokkos::View< const float *, CpMem > invMass, Kokkos::View< const float *[3], CpMem > posPred, Kokkos::View< const float *[4], CpMem > quatPred, Kokkos::View< const float *[4], CpMem > quatStatic, Kokkos::View< const float *[3], CpMem > invInertia, Kokkos::View< float *[3], CpMem > deltaPos, Kokkos::View< float *[4], CpMem > deltaQuat, Kokkos::View< int *, CpMem > constraintCounts, Kokkos::View< float, CpMem > maxOverlap, Kokkos::View< const int *, CpMem > onlyColor={}, int colorFilter=0)
Accumulate XPBD position corrections for numContacts contacts.
ContactHierarchy buildContactHierarchyKokkos(Kokkos::View< const ManifoldC *, CpMem > manifolds, int numManifolds, Kokkos::View< const int *, CpMem > realIdx, Kokkos::View< const int *, CpMem > mColor, Kokkos::View< const float *, CpMem > vn0, Kokkos::View< const float *[3], CpMem > vt0, Kokkos::View< const unsigned char *, CpMem > persistent, Kokkos::View< const float *[3], CpMem > posPred, F3 gHat, Kokkos::View< const float *, CpMem > invMass, float qsThr, int gateMask, int numReal, MlScratch &S, Kokkos::View< long long *, CpMem > winner, Kokkos::View< std::uint64_t *, CpMem > colorMask, bool excludeImmovable=false, Kokkos::View< const unsigned char *, CpMem > asleep={})
Build the aggregation hierarchy + per-level crossing-manifold colorings.
PGSManifoldSweep makePGSManifoldSweep(Kokkos::View< const ManifoldC *, CpMem > manifolds, Kokkos::View< const float *, CpMem > invMass, Kokkos::View< const float *[3], CpMem > invInertia, Kokkos::View< const float *[4], CpMem > quat, Kokkos::View< float *[3], CpMem > velPred, Kokkos::View< float *[3], CpMem > angVelPred, Kokkos::View< const int *, CpMem > realIdx, float growthRate, float restitutionNormal, float restVelThreshold, Kokkos::View< float, CpMem > maxApproach, Kokkos::View< float, CpMem > maxApproachQS, Kokkos::View< float *, CpMem > lambdaAcc, Kokkos::View< const float *, CpMem > vn0, Kokkos::View< const unsigned char *, CpMem > sideFlag, Kokkos::View< float *[3], CpMem > lambdaT, float frictionDynamic, Kokkos::View< const float *[3], CpMem > vt0, float restitutionTangent, Kokkos::View< const float *, CpMem > posImpulse, Kokkos::View< float *, CpMem > restBank, Kokkos::View< float *, CpMem > restRel, Kokkos::View< const unsigned char *, CpMem > restPersistent, Kokkos::View< const float *, CpMem > restVPeak, F3 restGHat, Kokkos::View< const unsigned char *, CpMem > restGrounded, bool restNewtonOff, bool restOneSided, Kokkos::View< float *, CpMem > restOrphan, Kokkos::View< const float *, CpMem > restOrphanVPeak)
Build the shared per-manifold sweep functor (the colored launch loop, the fused kernels and the fused...
void solveVelocityColoredGSKokkos(Kokkos::View< const ManifoldC *, CpMem > manifolds, int numManifolds, Kokkos::View< const int *, CpMem > mColor, int numColors, Kokkos::View< const float *, CpMem > invMass, Kokkos::View< const float *[3], CpMem > invInertia, Kokkos::View< const float *[4], CpMem > quat, Kokkos::View< float *[3], CpMem > velPred, Kokkos::View< float *[3], CpMem > angVelPred, Kokkos::View< const int *, CpMem > realIdx, float growthRate, float restitutionNormal, float restVelThreshold, Kokkos::View< float, CpMem > maxApproach, Kokkos::View< const unsigned char *, CpMem > persistent={}, Kokkos::View< const float *[3], CpMem > posPred={}, F3 gHat={}, Kokkos::View< const unsigned char *, CpMem > grounded={})
Colored Gauss–Seidel normal-restitution solve: sweep the numColors colour classes in order,...
void decayBodyOrphanKokkos(Kokkos::View< float *, CpMem > orphan, Kokkos::View< float *, CpMem > orphanVPeak, int numOwned, float restVelThreshold)
Orphan-account aging, once per substep over the OWNED bodies: both the balance and the carried event ...
void updateGroundedLevelsKokkos(Kokkos::View< const ManifoldC *, CpMem > manifolds, int numManifolds, Kokkos::View< const int *, CpMem > realIdx, Kokkos::View< const float *[3], CpMem > posPred, F3 gHat, Kokkos::View< unsigned char *, CpMem > grounded, int numReal, int sweeps, int decay)
Guendelman support levels, warm-started: decay every body's level by decay, re-seed 255 at wall/plane...
int readInt(Kokkos::View< int, CpMem > v)
float readFloat(Kokkos::View< float, CpMem > v)
bool solveVelocityPGSKokkos(Kokkos::View< const ManifoldC *, CpMem > manifolds, int numManifolds, Kokkos::View< const int *, CpMem > mColor, int numColors, Kokkos::View< const float *, CpMem > invMass, Kokkos::View< const float *[3], CpMem > invInertia, Kokkos::View< const float *[4], CpMem > quat, Kokkos::View< float *[3], CpMem > velPred, Kokkos::View< float *[3], CpMem > angVelPred, Kokkos::View< const int *, CpMem > realIdx, float growthRate, float restitutionNormal, float restVelThreshold, Kokkos::View< float, CpMem > maxApproach, Kokkos::View< float *, CpMem > lambdaAcc, Kokkos::View< const float *, CpMem > vn0, Kokkos::View< const unsigned char *, CpMem > sideFlag, Kokkos::View< float *[3], CpMem > lambdaT, float frictionDynamic, Kokkos::View< const float *[3], CpMem > vt0={}, float restitutionTangent=0.0f, Kokkos::View< const float *, CpMem > posImpulse={}, Kokkos::View< float, CpMem > maxApproachQS={}, Kokkos::View< float *, CpMem > restBank={}, Kokkos::View< float *, CpMem > restRel={}, Kokkos::View< const unsigned char *, CpMem > restPersistent={}, Kokkos::View< const float *, CpMem > restVPeak={}, F3 restGHat={}, Kokkos::View< const unsigned char *, CpMem > restGrounded={}, bool restNewtonOff=false, bool restOneSided=false, Kokkos::View< float *, CpMem > restOrphan={}, Kokkos::View< const float *, CpMem > restOrphanVPeak={}, Kokkos::View< const int *, CpMem > colorPerm={}, const std::vector< int > *colorOffs=nullptr, const FusedSweepCtx *fused=nullptr, const FusedLoopSpec *loop=nullptr)
Returns true when the sweep (or, with loop, the whole iteration loop) was submitted; false ONLY in lo...
void applyVelocityAndPredictPositionKokkos(int n, V3 pos, Vf invMass, V3 vel, V4 quat, V3 velPred, V3 angVelPred, V3 posPred, V4 quatPred, V3 angVel, float dt)
Re-integration: persist solved velocity, trapezoidal position predict, quaternion integrate.
CpExec::memory_space CpMem
bool demFusedWanted(bool graphReplayAvailable)
Fused-sweep policy (read once).
void buildLevelColorBucketsKokkos(Kokkos::View< const ManifoldC *, CpMem > manifolds, int numManifolds, Kokkos::View< const int *, CpMem > realIdx, Kokkos::View< const int *, CpMem > mColor, Kokkos::View< const int *, CpMem > heights, Kokkos::View< int *, CpMem > keys, Kokkos::View< int *, CpMem > perm, std::vector< std::pair< int, int > > &buckets)
Bucket the active coloured manifolds by (support level, colour) for the level-ordered ("multilevel") ...
int colorContactsIncrementalKokkos(Kokkos::View< const ContactC *, CpMem > contacts, int numContacts, int numBodies, Kokkos::View< const unsigned long long *, CpMem > prevKeys, Kokkos::View< const int *, CpMem > prevColor, int prevCount, Kokkos::View< int *, CpMem > cColor, Kokkos::View< unsigned long long *, CpMem > keysOut, Kokkos::View< long long *, CpMem > bodyWinner, Kokkos::View< std::uint64_t *, CpMem > bodyMask, int &leftover, bool forceFull, Kokkos::View< const unsigned char *, CpMem > sleepMask={})
Incremental (warm-started) contact colouring for the single-GPU PGS position solve.
int colorManifoldsKokkos(Kokkos::View< const ManifoldC *, CpMem > manifolds, int numManifolds, Kokkos::View< const int *, CpMem > realIdx, int numReal, Kokkos::View< int *, CpMem > mColor, Kokkos::View< long long *, CpMem > bodyWinner, Kokkos::View< std::uint64_t *, CpMem > bodyMask, int &leftover, Kokkos::View< const unsigned char *, CpMem > sleepMask={})
Greedy graph-colour the manifolds: no two manifolds sharing a real body get the same colour.
int findCollisionsGrow(Particles &P, float margin)
Broad phase with an automatically-grown pair buffer.
void commitPairKeysLambdaKokkos(Kokkos::View< const unsigned long long *, CpMem > keys, Kokkos::View< const float *, CpMem > lambda, Kokkos::View< const float *[3], CpMem > lambdaT, Kokkos::View< const float *, CpMem > restBank, Kokkos::View< const float *, CpMem > restVPeak, Kokkos::View< unsigned long long *, CpMem > prevKeys, Kokkos::View< float *, CpMem > prevLambda, Kokkos::View< float *[3], CpMem > prevLambdaT, Kokkos::View< float *, CpMem > prevRestBank, Kokkos::View< float *, CpMem > prevRestVPeak, Kokkos::View< int *, CpMem > perm, int numManifolds, Kokkos::View< const int *, CpMem > color={}, Kokkos::View< int *, CpMem > prevColor={})
Save this substep's keys + converged impulses (normal AND tangential) and key-sort them for next subs...
void buildCoarseBucketsKokkos(const ContactHierarchy &H, MlScratch &S, int numManifolds, Kokkos::View< int *, CpMem > colorScratch, Kokkos::View< int *, CpMem > perm, Kokkos::View< int *, CpMem > cursor, std::vector< std::vector< int > > &offs)
One multilevel stabilization cycle over an already-built hierarchy: fine colored smoothing is the cal...
void solveVelocityPGSBucketsKokkos(const PGSManifoldSweep &f, Kokkos::View< const int *, CpMem > perm, const std::vector< std::pair< int, int > > &buckets, bool topDown)
Level-ordered symmetric sweep: launch one PGS kernel per (level, colour) bucket, ascending (bottom-up...
void applyUpdatesKokkos(int n, V3 posPred, V3 velPred, V3 deltaPos, V3 deltaVel, Vi constraintCounts)
Jacobi count-averaged apply of position/velocity deltas, then clear deltas + counts.
int findCollisionsVerlet(Particles &P, float margin, float maxRad)
Verlet-cached impulse broadphase (single-GPU, non-periodic).
MlFusedCtx demMakeMlFusedCtx(CpExec &space, const ContactHierarchy &H, const std::vector< std::vector< int > > &bkOffs, int numManifolds, int numReal, int coarseSweeps, Kokkos::View< int *, CpMem > offsDev, Kokkos::View< unsigned *, CpMem > bar)
Build the fused-coarse-cycle context from an already-built hierarchy + its dense buckets: flatten the...
int findCollisionsArborX(PosV pos, RadV rad, int numParticles, int numReal, float margin, PairsV outPairs, CountV outCount, float boxCap=0.0f)
Emit candidate collision pairs (i<j) for real particles into outPairs/outCount.
void computeSideFlagsKokkos(Kokkos::View< const ManifoldC *, CpMem > manifolds, int numManifolds, Kokkos::View< const int *, CpMem > realIdx, Kokkos::View< const unsigned char *, CpMem > persistent, Kokkos::View< const unsigned char *, CpMem > grounded, Kokkos::View< const float *[3], CpMem > posPred, Kokkos::View< const float *[3], CpMem > velPred, F3 gHat, float riseThr, Kokkos::View< unsigned char *, CpMem > sideFlag, Kokkos::View< const float *, CpMem > vn0, float approachThr)
Decide each persistent contact's treatment ONCE per substep (before any impulse is applied): 0 = symm...
FusedSweepCtx demMakeFusedCtx(CpExec &space, const std::vector< int > &offs, Kokkos::View< int *, CpMem > offsDev, Kokkos::View< unsigned *, CpMem > bar)
Fill a FusedSweepCtx from buildColorBucketsKokkos's host offsets: async-upload them into the pooled d...
void countFrictionContactsKokkos(Kokkos::View< const ContactC *, CpMem > contacts, int numContacts, Kokkos::View< const int *, CpMem > realIdx, FrManifoldCounts planeFriction)
Per-body active-contact count into planeFriction(:,1).
Kokkos::DefaultExecutionSpace CpExec
dem — portable (Kokkos) particle SoA container: the storage the dem flip pivots on.
#define PECLET_DEM_GRAPH_LOOP(useVar, graphVar, emitVar, slotVar)
dem — portable (Kokkos) Coulomb friction cluster (the single dissipative friction path).
dem — multilevel (GraphMG-style) momentum-conserving contact stabilization.
dem — portable (Kokkos) XPBD position solve (pure overlap removal).
dem — portable (Kokkos) manifold velocity solve (normal restitution impulse).
Host-side description of one built hierarchy (offsets into the packed group pools).
bool capture(CpExec &, F &&, void *&)
Device-side iteration loop: run up to maxIters sweeps of the colour classes inside ONE kernel,...
Device-side context for a fused colour sweep: the colour offsets (numColors+1, uploaded from buildCol...
Fused-coarse-cycle context: flat per-level colour offsets on device + barrier + meta.
Device scratch for the multilevel pass, sized once (see Particles::allocate).
One full colored PGS sweep.
Kokkos::View< int *, CpMem > commitPerm
Kokkos::View< unsigned *, CpMem > fusedBar
Kokkos::View< unsigned char *, CpMem > asleep
Kokkos::View< int *, CpMem > bucketCursor
Kokkos::View< unsigned char *, CpMem > sideFlags
Kokkos::View< unsigned long long *, CpMem > prevContactKeys
Kokkos::View< int *, CpMem > mlGrp
Kokkos::View< unsigned char *, CpMem > manifoldPersistent
Definition particles.hpp:73
Kokkos::View< float *[3], CpMem > impRefPos
Kokkos::View< int *, CpMem > posOffsDev
Kokkos::View< float *, CpMem > bodyOrphanVPeak
Kokkos::View< int *, CpMem > velPerm
Kokkos::View< unsigned char *, CpMem > contactSleep
Kokkos::View< float *, CpMem > prevLambda
Definition particles.hpp:86
Kokkos::View< ManifoldC *, CpMem > manifolds
Definition particles.hpp:54
Kokkos::View< float *, CpMem > vn0
Definition particles.hpp:87
Kokkos::View< float *, CpMem > mlMassG
Kokkos::View< int *, CpMem > levelKey
Kokkos::View< int *[2], CpMem > pairs
Definition particles.hpp:52
Kokkos::View< float *[3], CpMem > vt0
Definition particles.hpp:88
Kokkos::View< float *, CpMem > restRel
Kokkos::View< int *, CpMem > levelPerm
Kokkos::View< int *, CpMem > prevManifoldColor
Definition particles.hpp:64
Kokkos::View< unsigned long long *, CpMem > contactKeys
Kokkos::View< int *, CpMem > prevContactColor
Kokkos::View< unsigned char *, CpMem > prevMatched
Kokkos::View< float, CpMem > maxApproach
Kokkos::View< float *, CpMem > prevRestBank
Kokkos::View< float *, CpMem > restVPeak
Kokkos::View< float *, CpMem > restBank
Kokkos::View< std::uint64_t *, CpMem > bodyColorMask
Kokkos::View< int *, CpMem > posCommitPerm
Kokkos::View< int *, CpMem > velOffsDev
Kokkos::View< int *, CpMem > mlOffsDev
Kokkos::View< unsigned long long *, CpMem > pairKeys
Definition particles.hpp:71
Kokkos::View< float *, CpMem > bodyOrphan
Kokkos::View< unsigned long long *, CpMem > prevPairKeys
Definition particles.hpp:72
Kokkos::View< int *, CpMem > posPerm
Kokkos::View< float *, CpMem > prevPosImpulse
Kokkos::View< int *, CpMem > contactColor
Kokkos::View< float *[3], CpMem > mlVelG
Kokkos::View< float *[3], CpMem > mlVelG0
Kokkos::View< int *, CpMem > mlBucketPerm
Kokkos::View< int *, CpMem > mlParent
Kokkos::View< float *[3], CpMem > lambdaT
Definition particles.hpp:92
Kokkos::View< float *[3], CpMem > prevLambdaT
Definition particles.hpp:93
Kokkos::View< float, CpMem > maxOverlap
Kokkos::View< float *, CpMem > posLambdaContact
Definition particles.hpp:99
Kokkos::View< float *, CpMem > posImpulse
Kokkos::View< int *, CpMem > contactSlot
Kokkos::View< float *, CpMem > mlInvMassG
Kokkos::View< int *, CpMem > heightLevel
Kokkos::View< long long *, CpMem > bodyWinner
Kokkos::View< float *, CpMem > lambdaAcc
Definition particles.hpp:85
Kokkos::View< long long *, CpMem > mlColorPacked
Kokkos::View< float, CpMem > maxApproachQS
Kokkos::View< unsigned char *, CpMem > manifoldSleep
Kokkos::View< const float *, CpMem > crad() const
Kokkos::View< float *[2], CpMem > planeFriction
Definition particles.hpp:47
Kokkos::View< float *, CpMem > prevRestVPeak
Kokkos::View< int *, CpMem > mlMate
Kokkos::View< ContactC *, CpMem > contacts
Definition particles.hpp:53
Kokkos::View< int, CpMem > pairCount
Kokkos::View< unsigned char *, CpMem > groundedLevel
Definition particles.hpp:80
Kokkos::View< int *, CpMem > manifoldColor
Definition particles.hpp:58
Single-GPU hooks: no ghost refresh, residuals are already global. Everything inlines away.
static constexpr bool distributed
float allMax(float v) const
void syncVelocities(Particles &) const
void syncPositions(Particles &) const