peclet-dem 0.4.0
Performance-portable XPBD Discrete Element Method (Kokkos + ArborX)
Loading...
Searching...
No Matches
contact_preprocessing.hpp
Go to the documentation of this file.
1
16#ifndef DEM_CONTACT_PREPROCESSING_HPP
17#define DEM_CONTACT_PREPROCESSING_HPP
18
19#include <cstdint>
20#include <Kokkos_Core.hpp>
21#include <Kokkos_Sort.hpp>
22
23#include "dem_portable.hpp" // F4, cross3
24
25namespace peclet::dem {
26
27using CpExec = Kokkos::DefaultExecutionSpace;
28using CpMem = CpExec::memory_space;
29
31struct ContactC {
32 int bodyA;
33 int bodyB; // < 0 => boundary/static
34 F4 normal; // .xyz = world normal
35 F4 rA; // lever arm on A
36 F4 rB; // lever arm on B
37 float dist; // signed penetration (>0 => inactive)
39 float weight;
40 // --- moving / per-material boundary (idB<0) extension; sentinels for body-body & static planes
41 // so the solvers fall back to the global material and a zero wall velocity (unchanged behaviour).
42 // ---
43 F4 boundaryVel{0.0f, 0.0f, 0.0f, 0.0f}; // wall surface velocity at the contact point (xyz)
44 float boundaryRestitution{-1.0f}; // per-wall normal restitution; < 0 => use the global one
45 float boundaryFriction{-1.0f}; // per-wall Coulomb friction; < 0 => use the global one
46};
47
49struct ManifoldC {
50 int bodyA;
51 int bodyB;
58 // Σ over the manifold's active contacts of the boundary (idB<0) extension; the velocity solve
59 // averages by num_points. wallVel_sum -> the wall's surface velocity seen by this contact patch;
60 // restitution_sum -> per-wall restitution (a < 0 average keeps the global material).
61 F4 wallVel_sum{0.0f, 0.0f, 0.0f, 0.0f};
62 float restitution_sum{0.0f};
63 float friction_sum{0.0f}; // per-contact mu (pair table / wall); a < 0 average = global material
64};
65
71KOKKOS_INLINE_FUNCTION unsigned long long pairKeyOf(const ManifoldC& m,
72 Kokkos::View<const int*, CpMem> realIdx) {
73 const unsigned a = static_cast<unsigned>(realIdx(m.bodyA));
74 const unsigned b = (m.bodyB >= 0) ? static_cast<unsigned>(realIdx(m.bodyB)) : 0xFFFFFFFFu;
75 const unsigned hi = a < b ? a : b, lo = a < b ? b : a;
76 return (static_cast<unsigned long long>(hi) << 32) | lo;
77}
78
82inline void markPersistentManifoldsKokkos(Kokkos::View<const ManifoldC*, CpMem> manifolds,
83 int numManifolds, Kokkos::View<const int*, CpMem> realIdx,
84 Kokkos::View<const int*, CpMem> keyIdx,
85 Kokkos::View<const unsigned long long*, CpMem> prevKeys,
86 int prevCount,
87 Kokkos::View<unsigned long long*, CpMem> outKeys,
88 Kokkos::View<unsigned char*, CpMem> outFlags) {
89 (void)realIdx;
90 CpExec space;
91 Kokkos::parallel_for(
92 "peclet::dem::mark_persistent", Kokkos::RangePolicy<CpExec>(space, 0, numManifolds),
93 KOKKOS_LAMBDA(int idx) {
94 const ManifoldC m = manifolds(idx);
95 if (m.num_points <= 0) {
96 outKeys(idx) = ~0ull; // hi = 0xFFFFFFFF: can never match a live manifold's key
97 outFlags(idx) = 0;
98 return;
99 }
100 const unsigned long long k = pairKeyOf(m, keyIdx);
101 outKeys(idx) = k;
102 int lo = 0, hi = prevCount;
103 while (lo < hi) { // lower_bound on the sorted previous-substep keys
104 const int mid = (lo + hi) >> 1;
105 if (prevKeys(mid) < k)
106 lo = mid + 1;
107 else
108 hi = mid;
109 }
110 outFlags(idx) = (lo < prevCount && prevKeys(lo) == k) ? 1 : 0;
111 });
112}
113
120 Kokkos::View<const ManifoldC*, CpMem> manifolds, int numManifolds,
121 Kokkos::View<const int*, CpMem> realIdx, Kokkos::View<const int*, CpMem> keyIdx,
122 Kokkos::View<const unsigned long long*, CpMem> prevKeys,
123 Kokkos::View<const float*, CpMem> prevLambda, Kokkos::View<const float* [3], CpMem> prevLambdaT,
124 Kokkos::View<const float*, CpMem> prevPosImpulse,
125 Kokkos::View<const float*, CpMem> prevRestBank, Kokkos::View<const float*, CpMem> prevRestVPeak,
126 int prevCount, Kokkos::View<unsigned long long*, CpMem> outKeys,
127 Kokkos::View<float*, CpMem> outWarm, Kokkos::View<float* [3], CpMem> outWarmT,
128 Kokkos::View<float*, CpMem> outPosImpulse, Kokkos::View<float*, CpMem> outRestBank,
129 Kokkos::View<float*, CpMem> outRestVPeak, Kokkos::View<unsigned char*, CpMem> outMatched = {}) {
130 CpExec space;
131 Kokkos::parallel_for(
132 "peclet::dem::gather_warm", Kokkos::RangePolicy<CpExec>(space, 0, numManifolds),
133 KOKKOS_LAMBDA(int idx) {
134 const ManifoldC m = manifolds(idx);
135 bool dup = false;
136 if (m.num_points > 0 && m.bodyB >= 0 && realIdx(m.bodyA) > realIdx(m.bodyB))
137 dup = true;
138 if (m.num_points <= 0 || dup) {
139 outKeys(idx) = ~0ull;
140 outWarm(idx) = 0.0f;
141 outWarmT(idx, 0) = outWarmT(idx, 1) = outWarmT(idx, 2) = 0.0f;
142 outPosImpulse(idx) = 0.0f;
143 outRestBank(idx) = 0.0f;
144 outRestVPeak(idx) = 0.0f;
145 return;
146 }
147 const unsigned long long k = pairKeyOf(m, keyIdx);
148 outKeys(idx) = k;
149 int lo = 0, hi = prevCount;
150 while (lo < hi) {
151 const int mid = (lo + hi) >> 1;
152 if (prevKeys(mid) < k)
153 lo = mid + 1;
154 else
155 hi = mid;
156 }
157 const bool hit = (lo < prevCount && prevKeys(lo) == k);
158 if (hit && outMatched.extent(0) > 0)
159 outMatched(lo) = 1; // prev entry survives; unmatched entries orphan their bank
160 outWarm(idx) = hit ? prevLambda(lo) : 0.0f;
161 outWarmT(idx, 0) = hit ? prevLambdaT(lo, 0) : 0.0f;
162 outWarmT(idx, 1) = hit ? prevLambdaT(lo, 1) : 0.0f;
163 outWarmT(idx, 2) = hit ? prevLambdaT(lo, 2) : 0.0f;
164 outPosImpulse(idx) = hit ? prevPosImpulse(lo) : 0.0f;
165 outRestBank(idx) = hit ? prevRestBank(lo) : 0.0f;
166 outRestVPeak(idx) = hit ? prevRestVPeak(lo) : 0.0f;
167 });
168}
169
173 Kokkos::View<const unsigned long long*, CpMem> keys, Kokkos::View<const float*, CpMem> lambda,
174 Kokkos::View<const float* [3], CpMem> lambdaT, Kokkos::View<const float*, CpMem> restBank,
175 Kokkos::View<const float*, CpMem> restVPeak, Kokkos::View<unsigned long long*, CpMem> prevKeys,
176 Kokkos::View<float*, CpMem> prevLambda, Kokkos::View<float* [3], CpMem> prevLambdaT,
177 Kokkos::View<float*, CpMem> prevRestBank, Kokkos::View<float*, CpMem> prevRestVPeak,
178 Kokkos::View<int*, CpMem> perm, // pooled scratch, >= n
179 int numManifolds,
180 // Optional: carry the per-manifold colour by pair key too
181 // (single-GPU incremental colouring; empty views = skip).
182 Kokkos::View<const int*, CpMem> color = {}, Kokkos::View<int*, CpMem> prevColor = {}) {
183 if (numManifolds <= 0)
184 return;
185 CpExec space;
186 const int n = numManifolds;
187 const auto rng = Kokkos::pair<int, int>(0, n);
188 auto kd = Kokkos::subview(prevKeys, rng);
189 Kokkos::deep_copy(space, kd, Kokkos::subview(keys, rng));
190 Kokkos::parallel_for(
191 "peclet::dem::commit_iota", Kokkos::RangePolicy<CpExec>(space, 0, n),
192 KOKKOS_LAMBDA(int i) { perm(i) = i; });
193 {
194 auto pd = Kokkos::subview(perm, rng);
195 Kokkos::Experimental::sort_by_key(space, kd, pd);
196 }
197 Kokkos::View<float*, CpMem> pl = prevLambda;
198 Kokkos::View<float* [3], CpMem> plt = prevLambdaT;
199 Kokkos::View<float*, CpMem> prb = prevRestBank;
200 Kokkos::View<float*, CpMem> prv = prevRestVPeak;
201 const bool carryColor = color.extent(0) > 0 && prevColor.extent(0) > 0;
202 Kokkos::View<const int*, CpMem> col = color;
203 Kokkos::View<int*, CpMem> pcol = prevColor;
204 Kokkos::parallel_for(
205 "peclet::dem::commit_gather", Kokkos::RangePolicy<CpExec>(space, 0, n), KOKKOS_LAMBDA(int i) {
206 const int j = perm(i);
207 pl(i) = lambda(j);
208 plt(i, 0) = lambdaT(j, 0);
209 plt(i, 1) = lambdaT(j, 1);
210 plt(i, 2) = lambdaT(j, 2);
211 prb(i) = restBank(j);
212 prv(i) = restVPeak(j);
213 if (carryColor)
214 pcol(i) = col(j);
215 });
216 space.fence();
217}
218
220inline void commitPairKeysKokkos(Kokkos::View<const unsigned long long*, CpMem> keys,
221 Kokkos::View<unsigned long long*, CpMem> prevKeys,
222 int numManifolds) {
223 if (numManifolds <= 0)
224 return;
225 CpExec space;
226 auto src = Kokkos::subview(keys, Kokkos::pair<int, int>(0, numManifolds));
227 auto dst = Kokkos::subview(prevKeys, Kokkos::pair<int, int>(0, numManifolds));
228 Kokkos::deep_copy(space, dst, src);
229 Kokkos::sort(space, dst);
230 space.fence();
231}
232
238inline void updateGroundedLevelsKokkos(Kokkos::View<const ManifoldC*, CpMem> manifolds,
239 int numManifolds, Kokkos::View<const int*, CpMem> realIdx,
240 Kokkos::View<const float* [3], CpMem> posPred, F3 gHat,
241 Kokkos::View<unsigned char*, CpMem> grounded, int numReal,
242 int sweeps, int decay) {
243 CpExec space;
244 Kokkos::parallel_for(
245 "peclet::dem::grounded_decay", Kokkos::RangePolicy<CpExec>(space, 0, numReal),
246 KOKKOS_LAMBDA(int i) {
247 const int g = static_cast<int>(grounded(i)) - decay;
248 grounded(i) = static_cast<unsigned char>(g > 0 ? g : 0);
249 });
250 for (int s = 0; s < sweeps; ++s) {
251 Kokkos::parallel_for(
252 "peclet::dem::grounded_sweep", Kokkos::RangePolicy<CpExec>(space, 0, numManifolds),
253 KOKKOS_LAMBDA(int idx) {
254 const ManifoldC m = manifolds(idx);
255 if (m.num_points <= 0)
256 return;
257 const int realA = realIdx(m.bodyA);
258 if (m.bodyB < 0) {
259 Kokkos::atomic_max(&grounded(realA), static_cast<unsigned char>(255));
260 return;
261 }
262 const int realB = realIdx(m.bodyB);
263 const F3 dx = sub3(ldF3(posPred, m.bodyA), ldF3(posPred, m.bodyB));
264 const float up = -(dx.x * gHat.x + dx.y * gHat.y + dx.z * gHat.z); // >0: A above B
265 const float thr = 0.3f * Kokkos::sqrt(dot3(dx, dx));
266 if (up > thr) { // B supports A
267 const int lvl = static_cast<int>(grounded(realB)) - 1;
268 if (lvl > 0)
269 Kokkos::atomic_max(&grounded(realA), static_cast<unsigned char>(lvl));
270 } else if (up < -thr) { // A supports B
271 const int lvl = static_cast<int>(grounded(realA)) - 1;
272 if (lvl > 0)
273 Kokkos::atomic_max(&grounded(realB), static_cast<unsigned char>(lvl));
274 }
275 });
276 }
277}
278
286inline constexpr int kLevelInf = 1 << 28;
287inline void computeHeightLevelsKokkos(Kokkos::View<const ManifoldC*, CpMem> manifolds,
288 int numManifolds, Kokkos::View<const int*, CpMem> realIdx,
289 Kokkos::View<const float* [3], CpMem> posPred, F3 gHat,
290 Kokkos::View<int*, CpMem> heights, int numReal) {
291 CpExec space;
292 Kokkos::parallel_for(
293 "peclet::dem::height_init", Kokkos::RangePolicy<CpExec>(space, 0, numReal),
294 KOKKOS_LAMBDA(int i) { heights(i) = kLevelInf; });
295 const int maxSweeps = 1024; // >= deepest supported column; early exit ends real runs sooner
296 for (int s = 0; s < maxSweeps; ++s) {
297 int changed = 0;
298 Kokkos::parallel_reduce(
299 "peclet::dem::height_sweep", Kokkos::RangePolicy<CpExec>(space, 0, numManifolds),
300 KOKKOS_LAMBDA(int idx, int& acc) {
301 const ManifoldC m = manifolds(idx);
302 if (m.num_points <= 0)
303 return;
304 const int realA = realIdx(m.bodyA);
305 if (m.bodyB < 0) {
306 if (Kokkos::atomic_fetch_min(&heights(realA), 0) > 0)
307 acc += 1;
308 return;
309 }
310 const int realB = realIdx(m.bodyB);
311 const F3 dx = sub3(ldF3(posPred, m.bodyA), ldF3(posPred, m.bodyB));
312 const float up = -(dx.x * gHat.x + dx.y * gHat.y + dx.z * gHat.z); // >0: A above B
313 const float thr = 0.3f * Kokkos::sqrt(dot3(dx, dx));
314 if (up > thr) { // B supports A
315 const int cand = heights(realB);
316 if (cand < kLevelInf && Kokkos::atomic_fetch_min(&heights(realA), cand + 1) > cand + 1)
317 acc += 1;
318 } else if (up < -thr) { // A supports B
319 const int cand = heights(realA);
320 if (cand < kLevelInf && Kokkos::atomic_fetch_min(&heights(realB), cand + 1) > cand + 1)
321 acc += 1;
322 }
323 },
324 changed);
325 if (changed == 0)
326 break;
327 }
328 space.fence();
329}
330
338inline void buildColorBucketsKokkos(Kokkos::View<const int*, CpMem> colorOf, int n, int numColors,
339 Kokkos::View<int*, CpMem> perm,
340 Kokkos::View<int*, CpMem> cursor, // scratch, >= 64 ints
341 std::vector<int>& offs) {
342 offs.assign(static_cast<std::size_t>(numColors) + 1, 0);
343 if (n <= 0 || numColors <= 0)
344 return;
345 CpExec space;
346 auto cnt = Kokkos::subview(cursor, Kokkos::pair<int, int>(0, numColors));
347 Kokkos::deep_copy(space, cnt, 0);
348 Kokkos::parallel_for(
349 "peclet::dem::bucket_hist", Kokkos::RangePolicy<CpExec>(space, 0, n), KOKKOS_LAMBDA(int i) {
350 const int c = colorOf(i);
351 if (c >= 0)
352 Kokkos::atomic_add(&cursor(c), 1);
353 });
354 auto hCnt = Kokkos::create_mirror_view(cnt);
355 Kokkos::deep_copy(space, hCnt, cnt);
356 space.fence();
357 for (int c = 0; c < numColors; ++c)
358 offs[static_cast<std::size_t>(c) + 1] = offs[static_cast<std::size_t>(c)] + hCnt(c);
359 for (int c = 0; c < numColors; ++c)
360 hCnt(c) = offs[static_cast<std::size_t>(c)];
361 Kokkos::deep_copy(space, cnt, hCnt);
362 Kokkos::parallel_for(
363 "peclet::dem::bucket_scatter", Kokkos::RangePolicy<CpExec>(space, 0, n),
364 KOKKOS_LAMBDA(int i) {
365 const int c = colorOf(i);
366 if (c >= 0)
367 perm(Kokkos::atomic_fetch_add(&cursor(c), 1)) = i;
368 });
369}
370
374KOKKOS_INLINE_FUNCTION long long colorKey(int idx) {
375 unsigned int z = static_cast<unsigned>(idx) + 0x9e3779b9u;
376 z = (z ^ (z >> 16)) * 0x21f0aaadu;
377 z = (z ^ (z >> 15)) * 0x735a2d97u;
378 z ^= (z >> 15);
379 // priority in the high word, unique index in the low word (unique key per edge; >= 0)
380 return (static_cast<long long>(z & 0x7fffffffu) << 32) | static_cast<unsigned>(idx);
381}
382
384KOKKOS_INLINE_FUNCTION std::uint64_t pairKey(const ContactC& c) {
385 const int idA = c.bodyA, idB = c.bodyB;
386 if (idB < 0)
387 return (static_cast<std::uint64_t>(static_cast<unsigned>(idA)) << 32) | 0xFFFFFFFFu;
388 const unsigned u = (idA < idB) ? idA : idB;
389 const unsigned v = (idA < idB) ? idB : idA;
390 return (static_cast<std::uint64_t>(u) << 32) | v;
391}
392
394KOKKOS_INLINE_FUNCTION void decodeKey(std::uint64_t key, int& bodyA, int& bodyB) {
395 const unsigned v = static_cast<unsigned>(key & 0xFFFFFFFFu);
396 bodyA = static_cast<int>(key >> 32);
397 bodyB = (v == 0xFFFFFFFFu) ? -1 : static_cast<int>(v);
398}
399
403KOKKOS_INLINE_FUNCTION ManifoldC transformContact(const ContactC& c) {
404 ManifoldC m{};
405 const int idA = c.bodyA, idB = c.bodyB;
406 if (c.dist > 0.0f) {
407 m.num_points = 0;
408 return m; // inactive (sums already zero)
409 }
410 const bool flip = (idB >= 0 && idB < idA);
411 m.num_points = 1;
412
413 const F4 n_vec = c.normal;
414 F4 n_aligned = flip ? n_vec : F4{-n_vec.x, -n_vec.y, -n_vec.z, 0.0f};
415
416 const F4 shift{c.normal.x * c.dist * 0.5f, c.normal.y * c.dist * 0.5f, c.normal.z * c.dist * 0.5f,
417 0.0f};
418 const F4 rA_mid{c.rA.x - shift.x, c.rA.y - shift.y, c.rA.z - shift.z, 0.0f};
419 const F4 rB_mid{c.rB.x + shift.x, c.rB.y + shift.y, c.rB.z + shift.z, 0.0f};
420
421 const F4 r_can = flip ? rB_mid : rA_mid;
422 const F4 r_other = flip ? rA_mid : rB_mid;
423
424 const F4 tau_can = cross3(r_can, n_aligned);
425 const F4 tau_other = cross3(r_other, F4{-n_aligned.x, -n_aligned.y, -n_aligned.z, 0.0f});
426
427 m.normal_sum = F4{n_aligned.x, n_aligned.y, n_aligned.z, 0.0f};
428 m.torque_armA_sum = F4{tau_can.x, tau_can.y, tau_can.z, 0.0f};
429 m.torque_armB_sum = F4{tau_other.x, tau_other.y, tau_other.z, 0.0f};
430 m.rA_sum = F4{r_can.x, r_can.y, r_can.z, 0.0f};
431 m.rB_sum = F4{r_other.x, r_other.y, r_other.z, 0.0f};
432 // Boundary (idB<0) moving-wall extension: carry the wall velocity + per-wall restitution through
433 // to the (count-averaged) velocity solve. Zero / -1 sentinel for body-body & static planes.
434 m.wallVel_sum = F4{c.boundaryVel.x, c.boundaryVel.y, c.boundaryVel.z, 0.0f};
435 m.restitution_sum = c.boundaryRestitution;
436 m.friction_sum = c.boundaryFriction;
437 return m;
438}
439
442inline int reduceContactsToManifoldsKokkos(Kokkos::View<const ContactC*, CpMem> contacts, int n,
443 Kokkos::View<ManifoldC*, CpMem> outManifolds,
444 Kokkos::View<int, CpMem> outCount,
445 Kokkos::View<int*, CpMem> contactSlot = {}) {
446 CpExec space;
447 if (n == 0) {
448 Kokkos::deep_copy(space, outCount, 0);
449 return 0;
450 }
451
452 // 1. Key every contact and seed an identity permutation.
453 Kokkos::View<std::uint64_t*, CpMem> keys(
454 Kokkos::view_alloc(space, "peclet::dem::cp::keys", Kokkos::WithoutInitializing), n);
455 Kokkos::View<int*, CpMem> perm(
456 Kokkos::view_alloc(space, "peclet::dem::cp::perm", Kokkos::WithoutInitializing), n);
457 Kokkos::parallel_for(
458 "peclet::dem::cp::key", Kokkos::RangePolicy<CpExec>(space, 0, n), KOKKOS_LAMBDA(int i) {
459 keys(i) = pairKey(contacts(i));
460 perm(i) = i;
461 });
462
463 // 2. Sort the permutation by key (groups equal pairs contiguously).
464 Kokkos::Experimental::sort_by_key(space, keys, perm);
465
466 // 3. Segment id per sorted position: inclusive scan of "key changed" minus 1.
467 Kokkos::View<int*, CpMem> segId(
468 Kokkos::view_alloc(space, "peclet::dem::cp::segId", Kokkos::WithoutInitializing), n);
469 int numSeg = 0;
470 Kokkos::parallel_scan(
471 "peclet::dem::cp::segscan", Kokkos::RangePolicy<CpExec>(space, 0, n),
472 KOKKOS_LAMBDA(int p, int& run, const bool final) {
473 const bool isNew = (p == 0) || (keys(p) != keys(p - 1));
474 if (isNew)
475 ++run;
476 if (final)
477 segId(p) = run - 1; // 0-based segment index
478 },
479 numSeg);
480
481 // 4. Initialise one manifold per segment (canonical ids from the key, sums zero).
482 Kokkos::View<std::uint64_t*, CpMem> k = keys;
483 Kokkos::View<int*, CpMem> sid = segId;
484 Kokkos::View<ManifoldC*, CpMem> out = outManifolds;
485 Kokkos::parallel_for(
486 "peclet::dem::cp::init", Kokkos::RangePolicy<CpExec>(space, 0, n), KOKKOS_LAMBDA(int p) {
487 const bool leader = (p == 0) || (k(p) != k(p - 1));
488 if (leader) {
489 ManifoldC m{};
490 decodeKey(k(p), m.bodyA, m.bodyB);
491 out(sid(p)) = m;
492 }
493 });
494
495 // 5. Accumulate each contact's transformed manifold into its segment (atomic; order-independent).
496 Kokkos::View<const ContactC*, CpMem> ct = contacts;
497 Kokkos::View<int*, CpMem> pm = perm;
498 Kokkos::parallel_for(
499 "peclet::dem::cp::accum", Kokkos::RangePolicy<CpExec>(space, 0, n), KOKKOS_LAMBDA(int p) {
500 const ManifoldC m = transformContact(ct(pm(p)));
501 const int s = sid(p);
502 Kokkos::atomic_add(&out(s).num_points, m.num_points);
503 Kokkos::atomic_add(&out(s).normal_sum.x, m.normal_sum.x);
504 Kokkos::atomic_add(&out(s).normal_sum.y, m.normal_sum.y);
505 Kokkos::atomic_add(&out(s).normal_sum.z, m.normal_sum.z);
506 Kokkos::atomic_add(&out(s).torque_armA_sum.x, m.torque_armA_sum.x);
507 Kokkos::atomic_add(&out(s).torque_armA_sum.y, m.torque_armA_sum.y);
508 Kokkos::atomic_add(&out(s).torque_armA_sum.z, m.torque_armA_sum.z);
509 Kokkos::atomic_add(&out(s).torque_armB_sum.x, m.torque_armB_sum.x);
510 Kokkos::atomic_add(&out(s).torque_armB_sum.y, m.torque_armB_sum.y);
511 Kokkos::atomic_add(&out(s).torque_armB_sum.z, m.torque_armB_sum.z);
512 Kokkos::atomic_add(&out(s).rA_sum.x, m.rA_sum.x);
513 Kokkos::atomic_add(&out(s).rA_sum.y, m.rA_sum.y);
514 Kokkos::atomic_add(&out(s).rA_sum.z, m.rA_sum.z);
515 Kokkos::atomic_add(&out(s).rB_sum.x, m.rB_sum.x);
516 Kokkos::atomic_add(&out(s).rB_sum.y, m.rB_sum.y);
517 Kokkos::atomic_add(&out(s).rB_sum.z, m.rB_sum.z);
518 Kokkos::atomic_add(&out(s).wallVel_sum.x, m.wallVel_sum.x);
519 Kokkos::atomic_add(&out(s).wallVel_sum.y, m.wallVel_sum.y);
520 Kokkos::atomic_add(&out(s).wallVel_sum.z, m.wallVel_sum.z);
521 Kokkos::atomic_add(&out(s).restitution_sum, m.restitution_sum);
522 Kokkos::atomic_add(&out(s).friction_sum, m.friction_sum);
523 });
524 // Optional contact -> manifold slot map (PGS friction bound reads lambdaAcc through it).
525 if (contactSlot.extent(0) >= (size_t)n) {
526 Kokkos::View<int*, CpMem> cs = contactSlot;
527 Kokkos::parallel_for(
528 "peclet::dem::cp::slotmap", Kokkos::RangePolicy<CpExec>(space, 0, n),
529 KOKKOS_LAMBDA(int p) { cs(pm(p)) = sid(p); });
530 }
531 space.fence();
532
533 Kokkos::deep_copy(space, outCount, numSeg);
534 return numSeg;
535}
536
541inline void frictionBoundFromLambdaKokkos(Kokkos::View<ContactC*, CpMem> contacts, int numContacts,
542 Kokkos::View<const int*, CpMem> contactSlot,
543 Kokkos::View<const ManifoldC*, CpMem> manifolds,
544 Kokkos::View<const float*, CpMem> lambdaAcc) {
545 CpExec space;
546 Kokkos::parallel_for(
547 "peclet::dem::cp::pgs_friction_bound", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
548 KOKKOS_LAMBDA(int i) {
549 const int s = contactSlot(i);
550 const ManifoldC m = manifolds(s);
551 const int np = (m.num_points > 0) ? m.num_points : 1;
552 contacts(i).friction_lambda_n = lambdaAcc(s) / static_cast<float>(np);
553 });
554 space.fence();
555}
556
564 Kokkos::View<const float*, CpMem> posLambdaContact, int numContacts,
565 Kokkos::View<const int*, CpMem> contactSlot, Kokkos::View<const ManifoldC*, CpMem> manifolds,
566 int numManifolds, Kokkos::View<const unsigned long long*, CpMem> keys,
567 Kokkos::View<const unsigned long long*, CpMem> prevKeysSorted, int prevCount, float dt,
568 Kokkos::View<float*, CpMem> scratchManifold, Kokkos::View<float*, CpMem> prevPosImpulse) {
569 CpExec space;
570 auto sm = Kokkos::subview(scratchManifold, Kokkos::pair<int, int>(0, numManifolds));
571 Kokkos::deep_copy(space, sm, 0.0f);
572 Kokkos::parallel_for(
573 "peclet::dem::pos_load_reduce", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
574 KOKKOS_LAMBDA(int i) {
575 const float l = posLambdaContact(i);
576 if (l != 0.0f)
577 Kokkos::atomic_add(&scratchManifold(contactSlot(i)), l / dt);
578 });
579 Kokkos::parallel_for(
580 "peclet::dem::pos_load_scatter", Kokkos::RangePolicy<CpExec>(space, 0, numManifolds),
581 KOKKOS_LAMBDA(int idx) {
582 const unsigned long long k = keys(idx);
583 if (k == ~0ull)
584 return;
585 int lo = 0, hi = prevCount;
586 while (lo < hi) {
587 const int m = (lo + hi) >> 1;
588 if (prevKeysSorted(m) < k)
589 lo = m + 1;
590 else
591 hi = m;
592 }
593 if (lo < prevCount && prevKeysSorted(lo) == k)
594 prevPosImpulse(lo) = scratchManifold(idx);
595 });
596 space.fence();
597}
598
599} // namespace peclet::dem
600
601#endif // DEM_CONTACT_PREPROCESSING_HPP
dem — portable POD types + math + analytic SDFs shared by the Kokkos kernel ports.
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 frictionBoundFromLambdaKokkos(Kokkos::View< ContactC *, CpMem > contacts, int numContacts, Kokkos::View< const int *, CpMem > contactSlot, Kokkos::View< const ManifoldC *, CpMem > manifolds, Kokkos::View< const float *, CpMem > lambdaAcc)
PGS friction bound: overwrite each contact's friction_lambda_n with its manifold's converged PGS push...
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)
std::uint64_t pairKey(const ContactC &c)
Canonical pair key: (min<<32)|max, or (idA<<32)|0xFFFFFFFF for a boundary (idB<0) contact.
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={})
Reduce n contacts to manifolds (one per unique canonical pair).
ManifoldC transformContact(const ContactC &c)
Per-contact transform to a single-point manifold, aligned to the canonical pair.
void decodeKey(std::uint64_t key, int &bodyA, int &bodyB)
Decode the canonical (bodyA, bodyB) from a pair key (bodyB = -1 for boundary).
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] .
F3 ldF3(const V &v, int i)
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 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...
float dot3(F3 a, F3 b)
F3 sub3(F3 a, F3 b)
CpExec::memory_space CpMem
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...
long long colorKey(int idx)
splitmix32 finalizer: a well-mixed pseudo-random priority per edge index.
constexpr int kLevelInf
Height-from-floor BFS levels for the level-ordered ("multilevel") stabilization pass: 0 at a wall/pla...
void commitPairKeysKokkos(Kokkos::View< const unsigned long long *, CpMem > keys, Kokkos::View< unsigned long long *, CpMem > prevKeys, int numManifolds)
Copy this substep's keys into prevKeys and sort them for next substep's binary search.
F4 cross3(F4 a, F4 b)
Kokkos::DefaultExecutionSpace CpExec
unsigned long long pairKeyOf(const ManifoldC &m, Kokkos::View< const int *, CpMem > realIdx)
Persistent-contact detection for the gravity-gated restitution rule.
Portable mirror of ParticleSystem.cuh ContactConstraint (the fields this reduction touches).
Portable mirror of ManifoldConstraint.