peclet-dem 1.1.0
Performance-portable XPBD Discrete Element Method (Kokkos + ArborX)
Loading...
Searching...
No Matches
step_solve.hpp
Go to the documentation of this file.
1
7#ifndef DEM_STEP_SOLVE_HPP
8#define DEM_STEP_SOLVE_HPP
9
10#include <algorithm>
11#include <cmath>
12#include <Kokkos_Core.hpp>
13
15#include "integration.hpp"
16#include "narrowphase.hpp"
17#include "particles.hpp"
18#include "periodicity.hpp"
19#include "sleeping.hpp" // island sleeping / freezing (single-GPU statics)
20#include "solve_driver.hpp" // demSolveContacts + SoloSolveHooks + readInt/readFloat
21
22namespace peclet::dem {
23
42inline void growContactBuffers(Particles& P, long nBodies, long floorWant = 0) {
43 // Two-way shell detection probes a shell pair from both sides: up to twice the contacts.
44 const int perParticle = std::max(16, P.shellPoints) * (P.shellDetection == 1 ? 2 : 1);
45 const long want = std::max(
46 floorWant, nBodies * perParticle + nBodies * std::max(1, P.shellPoints) * P.numWalls);
47 if (want > P.maxContacts) {
48 P.maxContacts = static_cast<int>(want);
49 // Reallocate EVERY maxContacts-sized view, not just contacts/manifolds: the solve writes all
50 // of them up to the live contact/manifold count, so any view left at the old size is an
51 // out-of-bounds write once the count grows past it (silent device corruption on GPU, heap
52 // corruption on host backends). Warm-start history is cleared by the fresh zeroed views —
53 // growth happens at setup (shape/wall registration), so nothing warm is lost mid-run.
54 P.contacts = Kokkos::View<ContactC*, CpMem>("contacts", want);
55 P.manifolds = Kokkos::View<ManifoldC*, CpMem>("manifolds", want);
56 P.manifoldColor = Kokkos::View<int*, CpMem>("manifoldColor", want);
57 P.pairKeys = Kokkos::View<unsigned long long*, CpMem>("pairKeys", want);
58 P.prevPairKeys = Kokkos::View<unsigned long long*, CpMem>("prevPairKeys", want);
59 P.manifoldPersistent = Kokkos::View<unsigned char*, CpMem>("manifoldPersistent", want);
60 P.contactColor = Kokkos::View<int*, CpMem>("contactColor", want);
61 P.unitStart = Kokkos::View<int*, CpMem>("unitStart", want + 1);
62 P.unitContacts = Kokkos::View<int*, CpMem>("unitContacts", want);
63 P.unitColor = Kokkos::View<int*, CpMem>("unitColor", want);
64 P.lambdaAcc = Kokkos::View<float*, CpMem>("lambdaAcc", want);
65 P.lambdaT = Kokkos::View<float* [3], CpMem>("lambdaT", want);
66 P.posLambdaContact = Kokkos::View<float*, CpMem>("posLambdaContact", want);
67 P.posImpulse = Kokkos::View<float*, CpMem>("posImpulse", want);
68 P.prevPosImpulse = Kokkos::View<float*, CpMem>("prevPosImpulse", want);
69 P.restBank = Kokkos::View<float*, CpMem>("restBank", want);
70 P.prevRestBank = Kokkos::View<float*, CpMem>("prevRestBank", want);
71 P.restRel = Kokkos::View<float*, CpMem>("restRel", want);
72 P.restVPeak = Kokkos::View<float*, CpMem>("restVPeak", want);
73 P.prevRestVPeak = Kokkos::View<float*, CpMem>("prevRestVPeak", want);
74 P.prevMatched = Kokkos::View<unsigned char*, CpMem>("prevMatched", want);
75 P.velPerm = Kokkos::View<int*, CpMem>("velPerm", want);
76 P.commitPerm = Kokkos::View<int*, CpMem>("commitPerm", want);
77 P.sideFlags = Kokkos::View<unsigned char*, CpMem>("sideFlags", want);
78 P.prevLambdaT = Kokkos::View<float* [3], CpMem>("prevLambdaT", want);
79 P.contactSlot = Kokkos::View<int*, CpMem>("contactSlot", want);
80 P.prevLambda = Kokkos::View<float*, CpMem>("prevLambda", want);
81 P.vn0 = Kokkos::View<float*, CpMem>("vn0", want);
82 P.vt0 = Kokkos::View<float* [3], CpMem>("vt0", want);
83 P.levelKey = Kokkos::View<int*, CpMem>("levelKey", want);
84 P.levelPerm = Kokkos::View<int*, CpMem>("levelPerm", want);
85 P.mlColorPacked = Kokkos::View<long long*, CpMem>("mlColorPacked", want);
86 // Incremental-colouring ledgers + fused position permutation + sleeping masks are all
87 // maxContacts-sized and the solve indexes them up to the live contact/manifold count too —
88 // they MUST grow with the buffer or nc > extent is an out-of-bounds write (NaN / heap
89 // corruption in dense multi-contact scenes such as the statics column/pour).
90 P.prevManifoldColor = Kokkos::View<int*, CpMem>("prevManifoldColor", want);
91 P.contactKeys = Kokkos::View<unsigned long long*, CpMem>("contactKeys", want);
92 P.prevContactKeys = Kokkos::View<unsigned long long*, CpMem>("prevContactKeys", want);
93 P.prevContactColor = Kokkos::View<int*, CpMem>("prevContactColor", want);
94 P.posCommitPerm = Kokkos::View<int*, CpMem>("posCommitPerm", want);
95 P.posPerm = Kokkos::View<int*, CpMem>("posPerm", want);
96 P.manifoldSleep = Kokkos::View<unsigned char*, CpMem>("manifoldSleep", want);
97 P.contactSleep = Kokkos::View<unsigned char*, CpMem>("contactSleep", want);
98 P.posPrevContactCount = 0; // the cleared position ledger must not be gathered against
99 P.prevPairCount = 0; // the cleared prevPairKeys must not be gathered against
100 }
101}
102
118inline int narrowPhaseGrow(Particles& P, int np, float margin) {
119 CpExec space;
120 auto detect = [&]() {
121 Kokkos::deep_copy(space, P.contactCount, 0);
123 P.globalScale, margin, P.contacts, P.contactCount, P.maxOverlap, P.sdfGrid,
124 P.materialId, P.pairMaterials, /*twoWay=*/P.shellDetection == 1);
126 P.shapes, P.shell, P.planes, P.globalScale, margin, P.contacts,
128 if (P.numWalls > 0)
130 P.shapes, P.shell, P.walls, P.wallGrid, P.globalScale, margin, P.contacts,
132 /*wallIdBase=*/P.numPlanes);
133 return readInt(P.contactCount);
134 };
135 int nc = detect();
136 if (nc > P.maxContacts) {
137 growContactBuffers(P, 0, static_cast<long>(nc) + nc / 2 + 64); // 1.5x + slack
138 nc = detect();
139 }
140 return std::min(nc, P.maxContacts);
141}
142
144inline void demStep(Particles& P) {
145 CpExec space;
146
147 // growth ramp (faithful to CUDA Simulation::step): factor *= exp(rate*dt), capped at 1, then
148 // scale = targetScale * factor. (CUDA stores the unscaled target in d_target_scales.)
149 if (P.growthFactor != -1.0f && P.growthRate != 0.0f) {
150 P.growthFactor *= std::exp(P.growthRate * P.dt);
151 if (P.growthFactor > 1.0f)
152 P.growthFactor = 1.0f;
153 }
154 if (P.growthFactor > 0.0f)
156
157 // ghost band + broadphase margin sized off the ACTUAL max grain radius (post-growth), so SI-unit
158 // particles just work; identical to the old 0.1/1.0*globalScale when globalScale ~ the grain
159 // size.
160 const float maxRad = maxOwnedRadius(P);
161 const float margin = 0.1f * maxRad;
162
166 P.extTorque);
167
168 // Island sleeping (single-GPU statics, opt-in, gravity on, no external drag): freeze the
169 // currently-asleep bodies so gravity/prediction do not move them; the wake pass + both-asleep
170 // exclusion + effective-inverse-mass swap happen after the narrow phase (below). See
171 // sleeping.hpp.
172 const float gMag =
173 std::sqrt(P.gravity.x * P.gravity.x + P.gravity.y * P.gravity.y + P.gravity.z * P.gravity.z);
174 const bool sleepStep =
175 P.sleepingEnabled && gMag > 0.0f && !P.extForceActive && !P.extTorqueActive;
176 const float sleepVRest = 2.0f * P.dt * gMag;
177 if (sleepStep)
179 P.angVelPred);
180
181 {
182 auto ri = P.realIndices;
183 Kokkos::parallel_for(
184 "self", Kokkos::RangePolicy<CpExec>(space, 0, P.numReal),
185 KOKKOS_LAMBDA(int i) { ri(i) = i; });
186 }
187 Kokkos::deep_copy(space, P.topGhost, P.numReal);
188 // Periodic ghost band: every partner of every pair the narrow phase can report gets an image.
189 // A wrap pair with distances a, b to its two faces is reported iff a + b < rA + rB + margin,
190 // so the FARTHER partner sits within 2 R_max + margin of its face. A band of one R_max (the old
191 // choice -- enough to DETECT every pair from the near side) left every pair with
192 // max(a, b) > R_max resolved one-sidedly. With both imaged the wrap pair is two twin manifolds,
193 // exactly like a pair symmetric about the face always was, and the distributed step's halo band
194 // (rcut + skin). In the position phase an image slot is a local mass-split COPY of its body
195 // (docs/contact_solve_framework.md §WO-4 item 2): its corrections are folded onto the body every
196 // iteration (demSolveContacts), so the body's CoM moves by exactly the corrections applied.
197 const float ghostBand = 2.0f * maxRad + margin;
198 // Size the SoA for the ghost boundary layer BEFORE emitting (CUDA did this in initialize() via
199 // calculate_capacity). Without it a Simulation(numReal) leaves capacity==numReal, so every ghost
200 // overflows P.capacity in generateGhostsKokkos and cross-boundary contacts are never detected.
202 generateGhostsKokkos(P.numReal, P.capacity, P.domain, ghostBand, P.pos, P.invMass, P.posPred,
203 P.vel, P.velPred, P.quat, P.quatPred, P.angVel, P.angVelPred, P.scale,
206 if (P.numParticles > P.numReal) {
207 // Each image's periodic shift at generation (the position-phase copy seed, §WO-4 item 2).
208 growCopyView(P.imageShift, static_cast<std::size_t>(P.capacity), "peclet::dem::imageShift");
209 auto sh = P.imageShift;
210 auto pp = P.posPred;
211 auto ri = P.realIndices;
212 Kokkos::parallel_for(
213 "peclet::dem::image_shift", Kokkos::RangePolicy<CpExec>(space, P.numReal, P.numParticles),
214 KOKKOS_LAMBDA(int q) {
215 const int i = ri(q);
216 for (int c = 0; c < 3; ++c)
217 sh(q, c) = pp(q, c) - pp(i, c);
218 });
219 }
220
222 // Collision detection runs on the PREDICTED state (speculative positions/orientations), matching
223 // the CUDA solver — the position solve then corrects posPred against these contacts.
224 // findCollisionsGrow fences + reads the pair count back to host and guarantees np ≤ P.pairs
225 // extent (growing the buffer on overflow) so the narrowphase never reads P.pairs out of bounds.
226 // Verlet-cached broadphase (opt-in, non-periodic only: periodic ghosts are regenerated per step
227 // with unstable slot ids, which cached pairs would reference). Skips the ArborX rebuild while
228 // nothing moved more than skin/2 — composes with sleeping (a frozen bed never rebuilds).
229 const bool verletOK = P.verletSkinFrac > 0.0f && !P.domain.periodic_x && !P.domain.periodic_y &&
231 const int np = verletOK ? findCollisionsVerlet(P, margin, maxRad) : findCollisionsGrow(P, margin);
232
233 Kokkos::deep_copy(space, P.contactCount, 0);
234 Kokkos::deep_copy(space, P.maxOverlap, 0.0f);
235 const int nc = narrowPhaseGrow(P, np, margin);
236
238 const int nm = readInt(P.manifoldCount);
239
240 // Island sleeping: wake any sleeper actually disturbed (fast approaching neighbour, moving wall,
241 // or a change in its contact set), then flag the frozen (both-asleep) manifolds/contacts and give
242 // every sleeper effective inverse mass 0 for the solve. Swapping P.invMass -> P.invMassEff makes
243 // a sleeper immovable everywhere in the driver (both-asleep constraints become no-ops; the
244 // exclusion just skips their now-wasted colouring/sweeps/hierarchy work), so the solve is
245 // unchanged for awake bodies and the ledger still carries the frozen force network.
246 Vf savedInvMass = P.invMass;
247 if (sleepStep) {
248 wakeDisturbedKokkos(P.manifolds, nm, P.realIndices, P.velPred, P.wakeScale * sleepVRest,
256 P.invMass = P.invMassEff;
257 }
258
259 // Full modern velocity + position solve (warm-started colored PGS, gravity statics /
260 // stabilization, friction, colored-GS overlap projection) — shared with the distributed step.
262
263 if (sleepStep) {
264 // Hub copies may have grown the SoA inside the solve (ensureCapacity): keep the restored view
265 // capacity-sized.
266 if (static_cast<int>(savedInvMass.extent(0)) < P.capacity)
267 Kokkos::resize(savedInvMass, P.capacity);
268 P.invMass = savedInvMass; // restore the real inverse mass for the commit / next step
269 }
271
272 // Sleep detection: a grounded body whose motion stayed below the resting floor for K substeps
273 // goes to sleep (velocity zeroed). Runs on the committed velocities + the solve's grounded
274 // levels.
275 if (sleepStep)
277 P.asleep, P.sleepCounter, P.sleepScale * sleepVRest, P.sleepK, P.vel,
278 P.angVel);
279
280 // Berendsen thermostat at the end of the step (CUDA Simulation::step), tau>0 enables.
281 if (P.thermostatTau > 0.0f && P.dt > 0.0f)
284}
285
291 CpExec space;
292 const float maxRad = maxOwnedRadius(P);
293 const float margin = 0.1f * maxRad;
294 P.numParticles = P.numReal;
295 Kokkos::deep_copy(P.posPred, P.pos);
296 Kokkos::deep_copy(P.quatPred, P.quat);
297 {
298 auto ri = P.realIndices;
299 Kokkos::parallel_for(
300 "self", Kokkos::RangePolicy<CpExec>(space, 0, P.numReal),
301 KOKKOS_LAMBDA(int i) { ri(i) = i; });
302 }
303 Kokkos::deep_copy(space, P.topGhost, P.numReal);
304 const float ghostBand = 2.0f * maxRad + margin; // the demStep band (see there)
305 // Match demStep: ensure ghost-boundary-layer headroom so cross-boundary overlaps are counted (a
306 // Simulation(numReal) otherwise has capacity==numReal and every ghost overflows). See demStep.
308 generateGhostsKokkos(P.numReal, P.capacity, P.domain, ghostBand, P.pos, P.invMass, P.posPred,
309 P.vel, P.velPred, P.quat, P.quatPred, P.angVel, P.angVelPred, P.scale,
313 const int np = findCollisionsGrow(P, margin);
314 Kokkos::deep_copy(space, P.contactCount, 0);
315 Kokkos::deep_copy(space, P.maxOverlap, 0.0f);
316 (void)narrowPhaseGrow(P, np, margin);
317 P.numParticles = P.numReal;
318 float h;
319 Kokkos::deep_copy(h, P.maxOverlap);
320 return h;
321}
322
323} // namespace peclet::dem
324
325#endif // DEM_STEP_SOLVE_HPP
dem — portable (Kokkos) contact->manifold reduction, replacing the thrust-based reduce_contacts_to_ma...
dem — portable (Kokkos) time integration kernels (integration.cu).
int narrowPhaseGrow(Particles &P, int np, float margin)
Narrow phase with an automatically-grown contact buffer.
void computeContactSleepKokkos(Kokkos::View< const ContactC *, CpMem > contacts, int numContacts, Kokkos::View< const int *, CpMem > realIdx, Kokkos::View< const unsigned char *, CpMem > asleep, Kokkos::View< unsigned char *, CpMem > contactSleep)
Per-contact twin of computeManifoldSleepKokkos for the position colouring.
Definition sleeping.hpp:95
float maxOwnedRadius(const Particles &P)
Largest effective particle radius over the owned set (= max scale × globalScale, growth included).
void generateGhostsKokkos(int numReal, int capacity, Domain dom, float skin, V3 pos, Vf invMass, V3 posPred, V3 vel, V3 velPred, V4 quat, V4 quatPred, V3 angVel, V3 angVelPred, Vf scale, Vi shapeId, Vi realIndices, Kokkos::View< int, CpMem > topGhost, Vi gid=Vi{}, Kokkos::View< unsigned char *, CpMem > materialId={})
Generate periodic ghosts for particles [0,numReal).
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...
Kokkos::View< float *, CpMem > Vf
void detectBoundaryKokkos(int numReal, int numPlanes, PosView pos, QuatView quat, ScalarF scale, ScalarI shapeId, Kokkos::View< const ShapeDesc *, CpMem > shapes, ShellView shell, Kokkos::View< const PlaneP *, CpMem > planes, float globalScale, float margin, Kokkos::View< ContactC *, CpMem > outContacts, Kokkos::View< int, CpMem > outCount, Kokkos::View< float, CpMem > maxOverlap)
Per-real-particle contacts against explicit planes (point-shell shapes test each surface point; analy...
void updateSleepKokkos(int numReal, Kokkos::View< const float *[3], CpMem > vel, Kokkos::View< const float *[3], CpMem > angVel, Kokkos::View< const float *, CpMem > rad, Kokkos::View< const unsigned char *, CpMem > grounded, Kokkos::View< const unsigned char *, CpMem > movingWall, Kokkos::View< unsigned char *, CpMem > asleep, Kokkos::View< unsigned char *, CpMem > sleepCounter, float sleepSpeed, int K, Kokkos::View< float *[3], CpMem > velOut, Kokkos::View< float *[3], CpMem > angVelOut)
Sleep detection (after the commit): an AWAKE, grounded body whose linear AND angular motion has staye...
Definition sleeping.hpp:205
void freezeAsleepKokkos(int numReal, Kokkos::View< const unsigned char *, CpMem > asleep, Kokkos::View< const float *[3], CpMem > pos, Kokkos::View< const float *[4], CpMem > quat, Kokkos::View< float *[3], CpMem > posPred, Kokkos::View< float *[4], CpMem > quatPred, Kokkos::View< float *[3], CpMem > velPred, Kokkos::View< float *[3], CpMem > angVelPred)
After predictVelocity: re-freeze the currently-asleep bodies so gravity/prediction do not move them —...
Definition sleeping.hpp:31
int readInt(Kokkos::View< int, CpMem > v)
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).
void detectWallSdfKokkos(int numReal, int numWalls, PosView pos, QuatView quat, ScalarF scale, ScalarI shapeId, Kokkos::View< const ShapeDesc *, CpMem > shapes, ShellView shell, Kokkos::View< const WallSdf *, CpMem > walls, GridView wallGrid, float globalScale, float margin, Kokkos::View< ContactC *, CpMem > outContacts, Kokkos::View< int, CpMem > outCount, Kokkos::View< float, CpMem > maxOverlap, MatIdView matId=MatIdView{}, PairTableView pairTable=PairTableView{}, int wallIdBase=0)
Per-real-particle contacts against a static world-space wall SDF set (a drum barrel,...
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 growContactBuffers(Particles &P, long nBodies, long floorWant=0)
Grow every maxContacts-sized collision buffer to match the CURRENT particle capacity.
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 detectContactsKokkos(Kokkos::View< const int *[2], CpMem > pairs, int numPairs, PosView pos, QuatView quat, ScalarF scale, ScalarI shapeId, Kokkos::View< const ShapeDesc *, CpMem > shapes, ShellView shell, float globalScale, float margin, Kokkos::View< ContactC *, CpMem > outContacts, Kokkos::View< int, CpMem > outCount, Kokkos::View< float, CpMem > maxOverlap, GridView sdfGrid=GridView{}, MatIdView matId=MatIdView{}, PairTableView pairTable=PairTableView{}, bool twoWay=false)
Pair point-shell vs SDF contacts.
float computeOverlapsKokkos(Particles &P)
Max pair interpenetration on the committed state (faithful to CUDA Simulation::compute_overlaps): cop...
void demStep(Particles &P)
One full XPBD DEM substep over the particle SoA (mirrors simulation.cpp Simulation::step()).
void buildInvMassEffKokkos(int numBodies, Kokkos::View< const unsigned char *, CpMem > asleep, Kokkos::View< const int *, CpMem > realIdx, Kokkos::View< const float *, CpMem > invMass, Kokkos::View< float *, CpMem > invMassEff, float sleeperFrac)
Effective inverse mass for the solve: a sleeping body (real, or a periodic ghost whose real is asleep...
Definition sleeping.hpp:58
void wakeDisturbedKokkos(Kokkos::View< const ManifoldC *, CpMem > manifolds, int numManifolds, Kokkos::View< const int *, CpMem > realIdx, Kokkos::View< const float *[3], CpMem > velPred, float wakeSpeed, Kokkos::View< unsigned char *, CpMem > asleep, Kokkos::View< unsigned char *, CpMem > sleepCounter, Kokkos::View< unsigned char *, CpMem > movingWall, Kokkos::View< int *, CpMem > curCount, int numReal)
Wake pass (before the solve): a sleeper is woken when actually disturbed.
Definition sleeping.hpp:117
int findCollisionsVerlet(Particles &P, float margin, float maxRad)
Verlet-cached impulse broadphase (single-GPU, non-periodic).
void computeManifoldSleepKokkos(Kokkos::View< const ManifoldC *, CpMem > manifolds, int numManifolds, Kokkos::View< const int *, CpMem > realIdx, Kokkos::View< const unsigned char *, CpMem > asleep, Kokkos::View< unsigned char *, CpMem > manifoldSleep)
Per-manifold "both endpoints asleep" flag (a static wall, bodyB < 0, counts as asleep).
Definition sleeping.hpp:74
void growCopyView(V &v, std::size_t n, const char *label)
Grow-only (re)allocation of a copy-machinery view to at least n entries.
void wakeContactChangeKokkos(int numReal, Kokkos::View< const int *, CpMem > curCount, Kokkos::View< int *, CpMem > prevCount, Kokkos::View< const unsigned char *, CpMem > movingWall, Kokkos::View< unsigned char *, CpMem > asleep, Kokkos::View< unsigned char *, CpMem > sleepCounter, bool wakeOnChange)
Contact-set-change wake rule (b): wake any still-asleep body whose live contact count differs from th...
Definition sleeping.hpp:176
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
int calculateGhostCapacity(int nReal, Domain dom, float skin)
Padded particle-array capacity that leaves room for the periodic ghosts generateGhostsKokkos will emi...
dem — portable (Kokkos) narrow-phase: SDF point-shell collision + boundary planes.
dem — portable (Kokkos) particle SoA container: the storage the dem flip pivots on.
dem — portable (Kokkos) periodic ghost generation (periodicity.cu / integration.cu).
dem — island sleeping / freezing for the single-GPU PGS statics path.
dem — the shared contact-solve driver: the full modern velocity + position solve sequence (warm-start...
Kokkos::View< WallSdf *, CpMem > walls
Kokkos::View< int *, CpMem > commitPerm
Kokkos::View< unsigned char *, CpMem > asleep
Kokkos::View< unsigned char *, CpMem > materialId
Kokkos::View< unsigned char *, CpMem > sideFlags
Kokkos::View< unsigned long long *, CpMem > prevContactKeys
Kokkos::View< unsigned char *, CpMem > manifoldPersistent
Kokkos::View< int, CpMem > manifoldCount
Kokkos::View< int *, CpMem > velPerm
Kokkos::View< unsigned char *, CpMem > contactSleep
Kokkos::View< float *, CpMem > prevLambda
void ensureCapacity(int newCap)
Kokkos::View< int *, CpMem > unitStart
Kokkos::View< ManifoldC *, CpMem > manifolds
Kokkos::View< float *, CpMem > vn0
Kokkos::View< int *, CpMem > levelKey
Kokkos::View< int *, CpMem > sleepCurCount
Kokkos::View< int *[2], CpMem > pairs
Kokkos::View< float *[3], CpMem > imageShift
Kokkos::View< int, CpMem > contactCount
Kokkos::View< unsigned char *, CpMem > sleepMovingWall
Kokkos::View< float *[3], CpMem > vt0
Kokkos::View< float *, CpMem > restRel
Kokkos::View< int *, CpMem > levelPerm
Kokkos::View< int *, CpMem > prevManifoldColor
Kokkos::View< unsigned long long *, CpMem > contactKeys
Kokkos::View< float *, CpMem > pairMaterials
Kokkos::View< ShapeDesc *, CpMem > shapes
Kokkos::View< int *, CpMem > prevContactColor
Kokkos::View< unsigned char *, CpMem > prevMatched
Kokkos::View< float *, CpMem > prevRestBank
Kokkos::View< float *, CpMem > restVPeak
Kokkos::View< float *, CpMem > restBank
Kokkos::View< int *, CpMem > posCommitPerm
Kokkos::View< unsigned long long *, CpMem > pairKeys
Kokkos::View< int, CpMem > topGhost
Kokkos::View< unsigned long long *, CpMem > prevPairKeys
Kokkos::View< int *, CpMem > posPerm
Kokkos::View< float *, CpMem > invMassEff
Kokkos::View< float *, CpMem > prevPosImpulse
Kokkos::View< int *, CpMem > contactColor
Kokkos::View< int *, CpMem > sleepPrevCount
Kokkos::View< float *, CpMem > sdfGrid
Kokkos::View< float *[3], CpMem > lambdaT
Kokkos::View< float *[3], CpMem > prevLambdaT
Kokkos::View< int *, CpMem > unitColor
Kokkos::View< float, CpMem > maxOverlap
Kokkos::View< float *, CpMem > posLambdaContact
Kokkos::View< float *, CpMem > posImpulse
Kokkos::View< int *, CpMem > contactSlot
Kokkos::View< unsigned char *, CpMem > sleepCounter
Kokkos::View< float *, CpMem > lambdaAcc
Kokkos::View< long long *, CpMem > mlColorPacked
Kokkos::View< float *, CpMem > wallGrid
Kokkos::View< unsigned char *, CpMem > manifoldSleep
Kokkos::View< float *[3], CpMem > shell
Kokkos::View< float *, CpMem > prevRestVPeak
Kokkos::View< ContactC *, CpMem > contacts
Kokkos::View< unsigned char *, CpMem > groundedLevel
Kokkos::View< int *, CpMem > unitContacts
Kokkos::View< PlaneP *, CpMem > planes
Kokkos::View< int *, CpMem > manifoldColor
Single-GPU hooks: no ghost refresh, residuals are already global. Everything inlines away.