peclet-dem 0.4.0
Performance-portable XPBD Discrete Element Method (Kokkos + ArborX)
Loading...
Searching...
No Matches
particles.hpp
Go to the documentation of this file.
1
10#ifndef DEM_PARTICLES_HPP
11#define DEM_PARTICLES_HPP
12
13#include <cstdint>
14#include <Kokkos_Core.hpp>
15
16#include "contact_preprocessing.hpp" // ContactC, ManifoldC
17#include "integration.hpp" // Domain, V3/V4/Vf/Vi
18#include "narrowphase.hpp" // ShapeDesc, PlaneP
19
20namespace peclet::dem {
21
22struct Particles {
23 // --- per-particle state (size = capacity) ---
43 // Global particle id (stable across halo rebuilds and MPI ownership migration; identity == local
44 // index on the single-GPU path). The distributed step builds the persistent-contact pair keys
45 // from it — local slots are not stable identities there. Ghost slots carry the owner's gid.
47 Kokkos::View<float* [2], CpMem> planeFriction;
48 Vf rad; // effective broadphase radius scratch (scale * globalScale)
49 V3 extForce; // per-particle external FORCE (e.g. fluid drag); F=ma => dv = extForce*invMass*dt
50
51 // --- collision/contact/manifold buffers ---
52 Kokkos::View<int* [2], CpMem> pairs; // broadphase candidates (maxPairs)
53 Kokkos::View<ContactC*, CpMem> contacts; // narrowphase output (maxContacts)
54 Kokkos::View<ManifoldC*, CpMem> manifolds; // reduction output (maxContacts)
55 // Graph-colouring scratch for the single-GPU colored Gauss–Seidel velocity solve: per-manifold
56 // colour (maxContacts; -2 inactive, -1 uncoloured, >=0 colour), plus per-body arbitration winner
57 // and committed-colour bitmask (both indexed by REAL body index, sized capacity).
58 Kokkos::View<int*, CpMem> manifoldColor; // per-manifold colour (velocity solve)
59 // Incremental (warm-started) colouring (single-GPU PGS path): the previous substep's per-manifold
60 // colour, sorted alongside prevPairKeys (committed by pair key exactly like prevLambda). A
61 // surviving pair keeps its colour so the Jones-Plassmann arbitration only re-runs over the NEW
62 // manifolds; velLastFullColors is the colour count at the last FULL recolour (creep-recompaction
63 // reference). See colorManifoldsIncrementalKokkos.
64 Kokkos::View<int*, CpMem> prevManifoldColor;
66 // Persistent-contact restitution (gravity-gated): pair keys of this/last substep's manifolds +
67 // the per-manifold "existed last substep" flag. A persistent contact is LOADED, not a fresh
68 // impact — it gets e = 0 (the impulse still cancels the approach: pure inelastic support), which
69 // is how the velocity solve carries a pile's static weight through impulse chains. Restitution
70 // stays reserved for newly formed contacts (genuine impacts). |g| = 0 leaves all of this idle.
71 Kokkos::View<unsigned long long*, CpMem> pairKeys;
72 Kokkos::View<unsigned long long*, CpMem> prevPairKeys;
73 Kokkos::View<unsigned char*, CpMem> manifoldPersistent;
75 // Grounded level per REAL body (Guendelman support levels, warm-started + decayed): 255 at a
76 // wall/plane contact, propagated lower -> upper through the contact graph a few sweeps per
77 // substep. One-sided (shock-propagation) impulses require the LOWER body grounded > 0, so a
78 // gas-borne emulsion or lifted slug (no path to the floor) keeps momentum-conserving impulses
79 // and its weight stays on the gas -- only genuinely supported chains drain into the ground.
80 Kokkos::View<unsigned char*, CpMem> groundedLevel;
81 // Warm-started PGS (projected Gauss-Seidel) velocity solve: per-manifold accumulated push
82 // impulse (this substep), the previous substep's converged impulses (sorted alongside
83 // prevPairKeys for the warm-start gather), and the pre-solve approach velocity (restitution
84 // bias). At convergence lambdaAcc IS the contact force network (x dt).
85 Kokkos::View<float*, CpMem> lambdaAcc;
86 Kokkos::View<float*, CpMem> prevLambda;
87 Kokkos::View<float*, CpMem> vn0;
88 Kokkos::View<float* [3], CpMem> vt0; // pre-solve tangential surface velocity (beta reference)
89 // Friction-cone PGS: per-manifold accumulated tangential impulse (world frame, kept in the
90 // contact tangent plane by projection) and the previous substep's converged values (sorted
91 // alongside prevPairKeys for the warm-start gather). |lambdaT| <= mu * lambdaAcc at all times.
92 Kokkos::View<float* [3], CpMem> lambdaT;
93 Kokkos::View<float* [3], CpMem> prevLambdaT;
94 // Position-channel normal load (see solvePositionColoredGSKokkos): per-contact positional
95 // lambda this substep, its per-manifold impulse-equivalent, the previous substep's value
96 // (warm-gathered) that tops up the friction cone's Coulomb bound, and the sorted store.
97 Kokkos::View<float*, CpMem> hertzSnPair; // per cached pair: last step's patch stiffness sum
98 Kokkos::View<float*, CpMem> hertzSnWall; // per (particle, wall): same, for wall patches
99 Kokkos::View<float*, CpMem> posLambdaContact;
100 Kokkos::View<float*, CpMem> posImpulse; // gathered: LAST substep's position-channel load
101 Kokkos::View<float*, CpMem> prevPosImpulse; // sorted alongside prevPairKeys
102 // Event-level (Poisson) restitution (restitutionModel == 1): per-pair OWED separation impulse
103 // (physical units) — e x the event's banked kinetic-compression impulse, minus what has already
104 // been returned. Carried by pair key like lambdaT (gathered into restBank, committed to
105 // prevRestBank); restRel is the per-substep release accumulator (clamped 0..owed inside the PGS
106 // sweep — the friction-cone-shaped budget cap). See updateRestitutionBankKokkos.
107 Kokkos::View<float*, CpMem> restBank;
108 Kokkos::View<float*, CpMem> prevRestBank; // sorted alongside prevPairKeys
109 Kokkos::View<float*, CpMem> restRel; // this substep's released impulse (lambda units)
110 // Event state: peak physical approach speed of the pair's current impact event (> 0 = event
111 // active). Set/refreshed by kinetic approaches, decays 1/256 per substep, cleared once it ages
112 // below the resting threshold — so a buried/absorbed event's bank evaporates instead of popping.
113 // While active the contact banks its applied normal-impulse flux EVERY substep (the co-moving
114 // compression plateau has vn0 ~ 0, which a per-substep kinetic gate would miss), and the release
115 // separation velocity is capped at e x vPeak (sustained unloading push, never an impulsive dump).
116 Kokkos::View<float*, CpMem> restVPeak;
117 Kokkos::View<float*, CpMem> prevRestVPeak; // sorted alongside prevPairKeys
118 // Orphan transfer (pair-churn fix): a pair that DIES with owed budget credits it to its
119 // endpoint bodies mass-weighted (per-body balance + the event peak speed it carried, both
120 // decayed 1/256 per substep so stranded credit evaporates). Live releasing pairs draw the
121 // balance back on demand inside the PGS sweep — a penetrating impactor's event budget then
122 // survives the ~20-substep turnover of its contact partners instead of dying with each pair.
123 // Indexed by REAL body slot (owned range authoritative; MPI ghosts mirrored owner->ghost).
124 Kokkos::View<float*, CpMem> bodyOrphan;
125 Kokkos::View<float*, CpMem> bodyOrphanVPeak;
126 Kokkos::View<unsigned char*, CpMem> prevMatched; // scratch: prev-ledger entry seen this substep
127 // Side flags for the STABILIZATION pass (0 = symmetric): zeroed for the main momentum-
128 // conserving sweeps, filled from persistence+grounding only if statics fail to converge.
129 Kokkos::View<unsigned char*, CpMem> sideFlags;
130 // Level-ordered ("ordered") stabilization: per-REAL-body height-from-floor BFS level
131 // (recomputed fresh each time the pass triggers) + the per-manifold (level, colour) bucket
132 // key and permutation the ordered sweeps iterate through.
133 Kokkos::View<int*, CpMem> heightLevel;
134 Kokkos::View<int*, CpMem> levelKey;
135 Kokkos::View<int*, CpMem> levelPerm;
136 // Multilevel (GraphMG) stabilization scratch (solver_multilevel.hpp): packed per-manifold
137 // 6-bit colour per level, pooled parent maps / group mass / group velocity arrays (levels
138 // shrink >= 10% each, so the pools hold every level), the composed body -> group map, and
139 // the matching scratch.
140 Kokkos::View<long long*, CpMem> mlColorPacked;
141 Kokkos::View<int*, CpMem> mlParent;
142 Kokkos::View<float*, CpMem> mlInvMassG;
143 Kokkos::View<float* [3], CpMem> mlVelG;
144 Kokkos::View<float* [3], CpMem> mlVelG0;
145 Kokkos::View<float*, CpMem> mlMassG;
146 Kokkos::View<int*, CpMem> mlGrp;
147 Kokkos::View<int*, CpMem> mlMate;
148 // --- Hertz-Mindlin soft-sphere engine (solver_hertz.hpp): cached Verlet pair list state ---
149 Kokkos::View<float* [3], CpMem> hertzXi; // per cached pair: Mindlin shear history
150 Kokkos::View<unsigned long long*, CpMem> hertzKeys; // keys of the cached pairs
151 Kokkos::View<unsigned long long*, CpMem> hertzPrevKeys; // sorted keys of the PREVIOUS list
152 Kokkos::View<float* [3], CpMem> hertzPrevXi; // xi aligned with hertzPrevKeys
153 Kokkos::View<float* [3], CpMem> hertzXiWall; // per (particle, wall) shear history
154 Kokkos::View<int*, CpMem> hertzWallCand; // near-wall candidate slots (i * 8 + wallIdx)
155 Kokkos::View<int, CpMem> hertzWallCandCount;
157 // Effective Hertz contact-curvature radius for non-spherical shapes, as a fraction of the
158 // bounding radius (true curvature is undefined at faces/edges; spheres use their real radius).
160 Kokkos::View<float* [3], CpMem> hertzRefPos; // positions at the last pair build
161 Kokkos::View<float, CpMem> hertzDispMax; // max |pos-ref|^2 since the build
162 Kokkos::View<float*, CpMem> hertzE, hertzNu; // per-material Young / Poisson
163 int hertzNumPairs = -1; // -1: no valid cached list
165 static constexpr int kHertzMaxWalls = 4;
166 // Stabilization mode of the staged velocity solve (Phase B; see sim.hpp): 0 = off (pure
167 // symmetric PGS), 1 = one-sided grounded pass (default), 2 = multilevel (level-ordered
168 // symmetric sweeps -- momentum is transported, never deleted), 3 = escalate (extra symmetric
169 // sweeps; diagnostic/fallback).
171 // Restitution model of the PGS velocity solve: 0 = newton (default; per-substep restitution on
172 // the pre-solve approach — unchanged behaviour), 1 = poisson (event-level: kinetic compression
173 // impulse banked per pair, released as a budget-capped separation-velocity target during
174 // unloading — restores the multi-substep-impact rebound per-substep Newton cannot return).
176 // Per-particle material id + flat pair-material table [kMaxMaterials^2 * 2] of (restitution,
177 // friction) rows; zero-length pairMaterials = feature off (global material everywhere).
178 Kokkos::View<unsigned char*, CpMem> materialId;
179 Kokkos::View<float*, CpMem> pairMaterials;
180 Kokkos::View<int*, CpMem> contactSlot; // contact -> manifold slot (PGS friction bound)
181 Kokkos::View<int*, CpMem> contactColor; // per-contact colour (position solve)
182 // Incremental (warm-started) position colouring (single-GPU PGS path): this substep's per-contact
183 // pair keys + the previous substep's (sorted keys, colour) ledger, so a surviving contact keeps
184 // its colour and only NEW contacts re-arbitrate. Unlike the manifold graph a pair CAN own several
185 // contacts (non-spherical multi-point patches), so carried colours are conflict-CHECKED (a shared
186 // colour on a shared body -> full recolour that substep). posLastFullColors = colours at the last
187 // full recolour (creep-recompaction reference). See colorContactsIncrementalKokkos.
188 Kokkos::View<unsigned long long*, CpMem> contactKeys;
189 Kokkos::View<unsigned long long*, CpMem> prevContactKeys;
190 Kokkos::View<int*, CpMem> prevContactColor;
191 Kokkos::View<int*, CpMem> posCommitPerm;
194 // Per-body round-winner key for the colouring arbitration. 64-bit: hashed-random priority in the
195 // high word (splitmix32 of the edge index), the unique edge index in the low word. Random
196 // priorities give O(log n) arbitration rounds w.h.p.; RAW indices are adversarial for poured
197 // lattice beds (monotone index chains -> O(chain) rounds -> minutes per step at 1M grains).
198 // Dense colour-bucket scratch (buildColorBucketsKokkos): per-manifold permutation for the PGS
199 // velocity sweeps, the pooled commit permutation, a 64-int cursor, and the lazily-grown
200 // per-level coarse-cycle permutation (numLevels x numManifolds segments).
201 Kokkos::View<int*, CpMem> velPerm;
202 Kokkos::View<int*, CpMem> commitPerm;
203 Kokkos::View<int*, CpMem> bucketCursor;
204 Kokkos::View<int*, CpMem> mlBucketPerm;
205 // Fused colour sweeps (solver_fused.hpp): position-colour bucket permutation, pooled device
206 // copies of the host colour offsets (velocity / position / flat multilevel), and the software
207 // grid-barrier arrival counter (memset per fused launch, shared by all fused kernels — they
208 // are stream-ordered).
209 Kokkos::View<int*, CpMem> posPerm;
210 Kokkos::View<int*, CpMem> velOffsDev;
211 Kokkos::View<int*, CpMem> posOffsDev;
212 Kokkos::View<int*, CpMem> mlOffsDev;
213 Kokkos::View<unsigned*, CpMem> fusedBar;
214 // Cross-step CUDA-graph executable cache (opaque cudaGraphExec_t per iteration loop:
215 // velocity / onesided / multilevel / position). Owned here; leaked at teardown by design
216 // (freeing needs the CUDA context, which Kokkos may already have torn down).
217 void* graphCache[4] = {nullptr, nullptr, nullptr, nullptr};
218 Kokkos::View<long long*, CpMem> bodyWinner;
219 Kokkos::View<std::uint64_t*, CpMem> bodyColorMask; // per-body committed-colour bitmask
220 // --- island sleeping / freezing (single-GPU statics path; see sleeping.hpp) ---
221 // Per-REAL-body asleep flag + low-motion counter; effective inverse mass for the solve (asleep
222 // bodies + their ghosts -> 0, swapped in for the solve so a sleeper is immovable); the
223 // moving-wall "never sleep" flag + this-substep live contact count (contact-set-change wake); the
224 // stored count. Per-manifold / per-contact "both endpoints asleep" masks exclude a frozen island
225 // from the colouring / sweeps / multilevel hierarchy (the ledger still carries their force
226 // network).
227 Kokkos::View<unsigned char*, CpMem> asleep;
228 Kokkos::View<unsigned char*, CpMem> sleepCounter;
229 Kokkos::View<unsigned char*, CpMem> sleepMovingWall;
230 Kokkos::View<int*, CpMem> sleepCurCount;
231 Kokkos::View<int*, CpMem> sleepPrevCount;
232 Kokkos::View<float*, CpMem> invMassEff;
233 Kokkos::View<unsigned char*, CpMem> manifoldSleep;
234 Kokkos::View<unsigned char*, CpMem> contactSleep;
235 bool sleepingEnabled = true; // set_sleeping / PECLET_DEM_SLEEP=0 disables; default ON
236 float sleepScale = 2.0f; // cSleep: sleep threshold = sleepScale * vRest
237 float wakeScale = 40.0f; // cWake: wake if an awake neighbour exceeds wakeScale * vRest
238 // (hysteresis: >> the residual settling jitter so a frozen bed
239 // stays frozen; only a genuine impact/disturbance wakes it)
240 int sleepK = 64; // substeps below threshold before sleeping (high enough that an
241 // impact's unloading/rebound completes before the network re-sleeps)
242 bool sleepWakeLostContact = false; // rule (b): wake on a LOST contact (support removed)
243 // Effective inverse-mass fraction of a sleeper for the solve (0 = exactly immovable). A small
244 // POSITIVE value keeps the sleeper very heavy but not infinitely rigid, so an awake body wedged at
245 // a frozen-pocket boundary can relieve against it instead of the PGS normal impulse diverging
246 // (trapped-between-two-rigid-constraints blow-up that a settling column reliably hit, ejected via
247 // the friction cone to NaN); the sleeper's velocity is re-zeroed each substep so no momentum
248 // accumulates and both-asleep interior manifolds are still fully excluded (the speed win). 0.01 =
249 // sleeper 100x a grain's mass: stable through the 96k column + violent pour, case3 penetration
250 // and the settled-bed freeze both preserved. PECLET_DEM_SLEEP_INVMASS_FRAC overrides.
251 float sleepImmovableFrac = 0.01f;
252 bool extForceActive = false; // CFD-DEM drag present -> sleeping disabled this step
253 // --- Verlet-cached broadphase for the impulse step (single-GPU, non-periodic; see demStep) ---
254 // The impulse broadphase rebuilds the ArborX pair list every step; between rebuilds no new pair
255 // can appear if no particle has moved more than skin/2 (with the list built at margin + skin).
256 // impRefPos = posPred at the last build; impNumPairs = the cached candidate count (-1 = invalid,
257 // forces a build). Periodic ghosts are regenerated per step (unstable slot ids), so this is used
258 // only when the domain is non-periodic. Off by default (rebuild every step).
259 Kokkos::View<float* [3], CpMem> impRefPos;
260 Kokkos::View<float, CpMem> impDispMax;
261 int impNumPairs = -1;
262 float impRefMaxRad = 0.0f; // max effective radius at the last build (growth bound)
263 float verletSkinFrac = 0.0f; // PECLET_DEM_VERLET_SKIN; 0 = off
264
265 // --- atomic counters / scalars (rank-0 Views) ---
266 Kokkos::View<int, CpMem> pairCount, contactCount, manifoldCount, topGhost;
267 Kokkos::View<float, CpMem> maxOverlap;
268 // Max physical approach speed among approaching manifolds in the last velocity sweep — drives the
269 // colored-GS velocity loop's adaptive stop (converged once no pair approaches above the resting
270 // threshold). maxOverlap plays the same role for the position loop.
271 Kokkos::View<float, CpMem> maxApproach;
272 // Quasi-static share of maxApproach (corrections on contacts with |vn0| <= 4 vRest): the
273 // multilevel stabilization loop's stop criterion -- flowing scenes keep ballistic churn out
274 // of it, so the pass ends after ~one cycle instead of burning its full budget as an
275 // over-convergence brake on discharge.
276 Kokkos::View<float, CpMem> maxApproachQS;
277
278 // --- static geometry ---
279 Kokkos::View<ShapeDesc*, CpMem> shapes;
280 Kokkos::View<float* [3], CpMem> shell;
281 Kokkos::View<PlaneP*, CpMem> planes;
282 Kokkos::View<float*, CpMem> sdfGrid; // concatenated grid-SDF samples (imported shapes)
283 // static world-space SDF walls (drum barrel, hopper, vibrating tray) + their concatenated
284 // samples.
285 Kokkos::View<WallSdf*, CpMem> walls;
286 Kokkos::View<float*, CpMem> wallGrid;
287
288 // --- sizes & params (host) ---
289 int capacity = 0, numReal = 0, numParticles = 0;
290 int maxPairs = 0, maxContacts = 0, numPlanes = 0, numWalls = 0;
291 // max wall friction (host) — gates the friction path so a frictional wall works even with a
292 // frictionless body-body material (global frictionDynamic == 0).
293 float wallFrictionMax = 0.0f;
295 F3 gravity{0, 0, 0};
296 float dt = 1e-3f, globalScale = 1.0f, growthRate = 0.0f, growthFactor = -1.0f;
298 1.0f; // shape canonical radius; effective radius = scale * globalScale * baseRadius
299 float thermostatTau = 0.0f, thermostatTemp = 0.0f,
300 thermostatKB = 1.0f; // Berendsen (tau>0 enables)
301 float frictionDynamic = 0.0f, restitutionNormal = 0.0f, skin = 0.1f;
302 // Walton tangential restitution beta (0 = tangentially dead stick, the pre-beta behaviour;
303 // > 0 reverses the pre-collision tangential surface velocity of COLLIDING contacts).
304 float restitutionTangent = 0.0f;
306 // Single-GPU collision solves: true = colored Gauss–Seidel for BOTH the restitution (velocity)
307 // and the overlap (position) solve — correct coupled multi-contact impulses + non-penetration,
308 // default; false = count-averaged Jacobi (the legacy robust path, still used by step_mpi).
309 bool velocityUseGS = true;
310
311 // nPlanes is the plane-array CAPACITY; numPlanes (the live count) stays 0 until planes are added.
312 void allocate(int cap, int maxPairs_, int maxContacts_, int nShapes, int nShell, int nPlanes) {
313 capacity = cap;
314 maxPairs = maxPairs_;
315 maxContacts = maxContacts_;
316 numPlanes = 0;
317 pos = V3("pos", cap);
318 invMass = Vf("invMass", cap);
319 quat = V4("quat", cap);
320 vel = V3("vel", cap);
321 angVel = V3("angVel", cap);
322 invInertia = V3("invInertia", cap);
323 scale = Vf("scale", cap);
324 targetScale = Vf("targetScale", cap);
325 shapeId = Vi("shapeId", cap);
326 posPred = V3("posPred", cap);
327 quatPred = V4("quatPred", cap);
328 velPred = V3("velPred", cap);
329 angVelPred = V3("angVelPred", cap);
330 deltaPos = V3("deltaPos", cap);
331 deltaQuat = V4("deltaQuat", cap);
332 deltaVel = V3("deltaVel", cap);
333 deltaAngVel = V3("deltaAngVel", cap);
334 constraintCounts = Vi("constraintCounts", cap);
335 realIndices = Vi("realIndices", cap);
336 gid = Vi("gid", cap);
337 planeFriction = Kokkos::View<float* [2], CpMem>("planeFriction", cap);
338 rad = Vf("rad", cap);
339 extForce = V3("extForce", cap); // zero-initialised => no external force by default
340 pairs = Kokkos::View<int* [2], CpMem>("pairs", maxPairs);
341 contacts = Kokkos::View<ContactC*, CpMem>("contacts", maxContacts);
342 manifolds = Kokkos::View<ManifoldC*, CpMem>("manifolds", maxContacts);
343 manifoldColor = Kokkos::View<int*, CpMem>("manifoldColor", maxContacts);
344 prevManifoldColor = Kokkos::View<int*, CpMem>("prevManifoldColor", maxContacts);
345 pairKeys = Kokkos::View<unsigned long long*, CpMem>("pairKeys", maxContacts);
346 prevPairKeys = Kokkos::View<unsigned long long*, CpMem>("prevPairKeys", maxContacts);
347 manifoldPersistent = Kokkos::View<unsigned char*, CpMem>("manifoldPersistent", maxContacts);
348 prevPairCount = 0;
349 contactColor = Kokkos::View<int*, CpMem>("contactColor", maxContacts);
350 contactKeys = Kokkos::View<unsigned long long*, CpMem>("contactKeys", maxContacts);
351 prevContactKeys = Kokkos::View<unsigned long long*, CpMem>("prevContactKeys", maxContacts);
352 prevContactColor = Kokkos::View<int*, CpMem>("prevContactColor", maxContacts);
353 posCommitPerm = Kokkos::View<int*, CpMem>("posCommitPerm", maxContacts);
354 velPerm = Kokkos::View<int*, CpMem>("velPerm", maxContacts);
355 commitPerm = Kokkos::View<int*, CpMem>("commitPerm", maxContacts);
356 bucketCursor = Kokkos::View<int*, CpMem>("bucketCursor", 64);
357 mlBucketPerm = Kokkos::View<int*, CpMem>("mlBucketPerm", 0);
358 posPerm = Kokkos::View<int*, CpMem>("posPerm", maxContacts);
359 velOffsDev = Kokkos::View<int*, CpMem>("velOffsDev", 65);
360 posOffsDev = Kokkos::View<int*, CpMem>("posOffsDev", 65);
361 mlOffsDev = Kokkos::View<int*, CpMem>("mlOffsDev", 65 * 10); // 10 = kMlMaxLevels
362 fusedBar = Kokkos::View<unsigned*, CpMem>("fusedBar", 32769); // 4096 blocks x 8 + 1
363 bodyWinner = Kokkos::View<long long*, CpMem>("bodyWinner", cap);
364 bodyColorMask = Kokkos::View<std::uint64_t*, CpMem>("bodyColorMask", cap);
365 asleep = Kokkos::View<unsigned char*, CpMem>("asleep", cap);
366 sleepCounter = Kokkos::View<unsigned char*, CpMem>("sleepCounter", cap);
367 sleepMovingWall = Kokkos::View<unsigned char*, CpMem>("sleepMovingWall", cap);
368 sleepCurCount = Kokkos::View<int*, CpMem>("sleepCurCount", cap);
369 sleepPrevCount = Kokkos::View<int*, CpMem>("sleepPrevCount", cap);
370 invMassEff = Kokkos::View<float*, CpMem>("invMassEff", cap);
371 manifoldSleep = Kokkos::View<unsigned char*, CpMem>("manifoldSleep", maxContacts);
372 contactSleep = Kokkos::View<unsigned char*, CpMem>("contactSleep", maxContacts);
373 impRefPos = Kokkos::View<float* [3], CpMem>("impRefPos", cap);
374 impDispMax = Kokkos::View<float, CpMem>("impDispMax");
375 groundedLevel = Kokkos::View<unsigned char*, CpMem>("groundedLevel", cap);
376 materialId = Kokkos::View<unsigned char*, CpMem>("materialId", cap);
377 lambdaAcc = Kokkos::View<float*, CpMem>("lambdaAcc", maxContacts);
378 lambdaT = Kokkos::View<float* [3], CpMem>("lambdaT", maxContacts);
379 posLambdaContact = Kokkos::View<float*, CpMem>("posLambdaContact", maxContacts);
380 posImpulse = Kokkos::View<float*, CpMem>("posImpulse", maxContacts);
381 prevPosImpulse = Kokkos::View<float*, CpMem>("prevPosImpulse", maxContacts);
382 restBank = Kokkos::View<float*, CpMem>("restBank", maxContacts);
383 prevRestBank = Kokkos::View<float*, CpMem>("prevRestBank", maxContacts);
384 restRel = Kokkos::View<float*, CpMem>("restRel", maxContacts);
385 restVPeak = Kokkos::View<float*, CpMem>("restVPeak", maxContacts);
386 prevRestVPeak = Kokkos::View<float*, CpMem>("prevRestVPeak", maxContacts);
387 bodyOrphan = Kokkos::View<float*, CpMem>("bodyOrphan", cap);
388 bodyOrphanVPeak = Kokkos::View<float*, CpMem>("bodyOrphanVPeak", cap);
389 prevMatched = Kokkos::View<unsigned char*, CpMem>("prevMatched", maxContacts);
390 sideFlags = Kokkos::View<unsigned char*, CpMem>("sideFlags", maxContacts);
391 heightLevel = Kokkos::View<int*, CpMem>("heightLevel", cap);
392 levelKey = Kokkos::View<int*, CpMem>("levelKey", maxContacts);
393 levelPerm = Kokkos::View<int*, CpMem>("levelPerm", maxContacts);
394 mlColorPacked = Kokkos::View<long long*, CpMem>("mlColorPacked", maxContacts);
395 mlParent = Kokkos::View<int*, CpMem>("mlParent", 5 * cap);
396 mlInvMassG = Kokkos::View<float*, CpMem>("mlInvMassG", 4 * cap);
397 mlVelG = Kokkos::View<float* [3], CpMem>("mlVelG", 4 * cap);
398 mlVelG0 = Kokkos::View<float* [3], CpMem>("mlVelG0", 4 * cap);
399 mlMassG = Kokkos::View<float*, CpMem>("mlMassG", 4 * cap);
400 mlGrp = Kokkos::View<int*, CpMem>("mlGrp", cap);
401 mlMate = Kokkos::View<int*, CpMem>("mlMate", cap);
402 hertzXiWall = Kokkos::View<float* [3], CpMem>("hertzXiWall", cap * kHertzMaxWalls);
403 hertzWallCand = Kokkos::View<int*, CpMem>("hertzWallCand", cap * 2);
404 hertzSnWall = Kokkos::View<float*, CpMem>("hertzSnWall", cap * kHertzMaxWalls);
405 hertzWallCandCount = Kokkos::View<int, CpMem>("hertzWallCandCount");
406 hertzRefPos = Kokkos::View<float* [3], CpMem>("hertzRefPos", cap);
407 hertzDispMax = Kokkos::View<float, CpMem>("hertzDispMax");
408 hertzE = Kokkos::View<float*, CpMem>("hertzE", 8);
409 hertzNu = Kokkos::View<float*, CpMem>("hertzNu", 8);
410 {
411 auto he = Kokkos::create_mirror_view(hertzE);
412 auto hn = Kokkos::create_mirror_view(hertzNu);
413 for (int i = 0; i < 8; ++i) {
414 he(i) = 1.0e9f;
415 hn(i) = 0.25f;
416 }
417 Kokkos::deep_copy(hertzE, he);
418 Kokkos::deep_copy(hertzNu, hn);
419 }
420 prevLambdaT = Kokkos::View<float* [3], CpMem>("prevLambdaT", maxContacts);
421 contactSlot = Kokkos::View<int*, CpMem>("contactSlot", maxContacts);
422 prevLambda = Kokkos::View<float*, CpMem>("prevLambda", maxContacts);
423 vn0 = Kokkos::View<float*, CpMem>("vn0", maxContacts);
424 vt0 = Kokkos::View<float* [3], CpMem>("vt0", maxContacts);
425 pairCount = Kokkos::View<int, CpMem>("pairCount");
426 contactCount = Kokkos::View<int, CpMem>("contactCount");
427 manifoldCount = Kokkos::View<int, CpMem>("manifoldCount");
428 topGhost = Kokkos::View<int, CpMem>("topGhost");
429 maxOverlap = Kokkos::View<float, CpMem>("maxOverlap");
430 maxApproach = Kokkos::View<float, CpMem>("maxApproach");
431 maxApproachQS = Kokkos::View<float, CpMem>("maxApproachQS");
432 shapes = Kokkos::View<ShapeDesc*, CpMem>("shapes", nShapes > 0 ? nShapes : 1);
433 shell = Kokkos::View<float* [3], CpMem>("shell", nShell > 0 ? nShell : 1);
434 planes = Kokkos::View<PlaneP*, CpMem>("planes", nPlanes > 0 ? nPlanes : 1);
435 sdfGrid = Kokkos::View<float*, CpMem>("sdfGrid", 1); // resized by setSdfShape
436 walls = Kokkos::View<WallSdf*, CpMem>("walls", 1); // resized by addSdfWall
437 wallGrid = Kokkos::View<float*, CpMem>("wallGrid", 1); // concatenated wall samples
438 numWalls = 0;
439 }
440
441 // Grow the per-particle SoA to hold at least `newCap` particles (real + periodic-ghost headroom),
442 // preserving the existing [0,numReal) state (Kokkos::resize copies the overlapping subextent).
443 // The single-GPU step sizes this from the domain before generating ghosts; without the headroom
444 // every ghost slot overflows `capacity` and cross-boundary contacts silently vanish. A no-op when
445 // the SoA is already large enough (e.g. the MPI path, whose caller pre-sizes capacity for the
446 // worst-case ghost band). The collision buffers (pairs/contacts/manifolds) keep their
447 // construction-time sizing — each real particle still issues one broad-phase query.
448 void ensureCapacity(int newCap) {
449 if (newCap <= capacity)
450 return;
451 Kokkos::resize(pos, newCap);
452 Kokkos::resize(invMass, newCap);
453 Kokkos::resize(quat, newCap);
454 Kokkos::resize(vel, newCap);
455 Kokkos::resize(angVel, newCap);
456 Kokkos::resize(invInertia, newCap);
457 Kokkos::resize(scale, newCap);
458 Kokkos::resize(targetScale, newCap);
459 Kokkos::resize(shapeId, newCap);
460 Kokkos::resize(posPred, newCap);
461 Kokkos::resize(quatPred, newCap);
462 Kokkos::resize(velPred, newCap);
463 Kokkos::resize(angVelPred, newCap);
464 Kokkos::resize(deltaPos, newCap);
465 Kokkos::resize(deltaQuat, newCap);
466 Kokkos::resize(deltaVel, newCap);
467 Kokkos::resize(deltaAngVel, newCap);
468 Kokkos::resize(constraintCounts, newCap);
469 Kokkos::resize(realIndices, newCap);
470 Kokkos::resize(gid, newCap);
471 Kokkos::resize(bodyWinner, newCap);
472 Kokkos::resize(bodyColorMask, newCap);
473 Kokkos::resize(groundedLevel, newCap);
474 Kokkos::resize(heightLevel, newCap);
475 Kokkos::resize(planeFriction, newCap);
476 Kokkos::resize(rad, newCap);
477 Kokkos::resize(extForce, newCap);
478 // materialId is written per GHOST slot by generateGhostsKokkos (guarded by `capacity`),
479 // so it MUST track the padded capacity like every other per-slot array. Its absence here
480 // was a silent out-of-bounds write into the neighbouring allocation — harmless or
481 // catastrophic depending on the device allocator's layout (the 2026-08 H100 packing
482 // corruption: contacts silently unresolved, phi_voxel 0.40 instead of 0.50).
483 Kokkos::resize(materialId, newCap);
484 // Not ghost-indexed today, but per-particle and cap-sized at construction — keep them in
485 // lockstep so a future ghost-slot consumer cannot reintroduce the same class of bug.
486 Kokkos::resize(asleep, newCap);
487 Kokkos::resize(sleepCounter, newCap);
488 Kokkos::resize(sleepMovingWall, newCap);
489 capacity = newCap;
490 }
491
492 // Const views for the read-only kernel inputs.
493 Kokkos::View<const float* [3], CpMem> cpos() const { return pos; }
494 Kokkos::View<const float*, CpMem> crad() const { return rad; }
495};
496
497} // namespace peclet::dem
498
499#endif // DEM_PARTICLES_HPP
dem — portable (Kokkos) contact->manifold reduction, replacing the thrust-based reduce_contacts_to_ma...
dem — portable (Kokkos) time integration kernels (integration.cu).
Kokkos::View< float *, CpMem > Vf
Kokkos::View< int *, CpMem > Vi
Kokkos::View< float *[3], CpMem > V3
Kokkos::View< float *[4], CpMem > V4
dem — portable (Kokkos) narrow-phase: SDF point-shell collision + boundary planes.
Kokkos::View< WallSdf *, CpMem > walls
Kokkos::View< const float *[3], CpMem > cpos() const
Kokkos::View< float *, CpMem > hertzE
Kokkos::View< int *, CpMem > commitPerm
Kokkos::View< float *[3], CpMem > hertzXi
Kokkos::View< unsigned *, CpMem > fusedBar
Kokkos::View< unsigned char *, CpMem > asleep
Kokkos::View< int *, CpMem > bucketCursor
Kokkos::View< unsigned char *, CpMem > materialId
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< int, CpMem > hertzWallCandCount
Kokkos::View< float *[3], CpMem > impRefPos
Kokkos::View< int, CpMem > manifoldCount
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
void ensureCapacity(int newCap)
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 *, CpMem > sleepCurCount
Kokkos::View< int *[2], CpMem > pairs
Definition particles.hpp:52
Kokkos::View< int, CpMem > contactCount
Kokkos::View< int *, CpMem > hertzWallCand
Kokkos::View< unsigned char *, CpMem > sleepMovingWall
Kokkos::View< float, CpMem > hertzDispMax
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
static constexpr int kHertzMaxWalls
Kokkos::View< float, CpMem > impDispMax
Kokkos::View< unsigned long long *, CpMem > contactKeys
Kokkos::View< float *, CpMem > pairMaterials
Kokkos::View< ShapeDesc *, CpMem > shapes
Kokkos::View< unsigned long long *, CpMem > hertzPrevKeys
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 > hertzNu
Kokkos::View< unsigned long long *, CpMem > hertzKeys
Kokkos::View< int, CpMem > topGhost
Kokkos::View< float *, CpMem > bodyOrphan
Kokkos::View< unsigned long long *, CpMem > prevPairKeys
Definition particles.hpp:72
Kokkos::View< float *, CpMem > hertzSnWall
Definition particles.hpp:98
Kokkos::View< int *, CpMem > posPerm
Kokkos::View< float *, CpMem > invMassEff
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 > sleepPrevCount
Kokkos::View< int *, CpMem > mlBucketPerm
Kokkos::View< float *[3], CpMem > hertzRefPos
Kokkos::View< int *, CpMem > mlParent
Kokkos::View< float *, CpMem > sdfGrid
Kokkos::View< float *[3], CpMem > lambdaT
Definition particles.hpp:92
Kokkos::View< float *, CpMem > hertzSnPair
Definition particles.hpp:97
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< unsigned char *, CpMem > sleepCounter
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 > wallGrid
Kokkos::View< float, CpMem > maxApproachQS
Kokkos::View< unsigned char *, CpMem > manifoldSleep
Kokkos::View< const float *, CpMem > crad() const
void allocate(int cap, int maxPairs_, int maxContacts_, int nShapes, int nShell, int nPlanes)
Kokkos::View< float *[2], CpMem > planeFriction
Definition particles.hpp:47
Kokkos::View< float *[3], CpMem > shell
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< float *[3], CpMem > hertzXiWall
Kokkos::View< PlaneP *, CpMem > planes
Kokkos::View< float *[3], CpMem > hertzPrevXi
Kokkos::View< int *, CpMem > manifoldColor
Definition particles.hpp:58