peclet-dem 0.4.0
Performance-portable XPBD Discrete Element Method (Kokkos + ArborX)
Loading...
Searching...
No Matches
solver_position.hpp
Go to the documentation of this file.
1
10#ifndef DEM_SOLVER_POSITION_HPP
11#define DEM_SOLVER_POSITION_HPP
12
13#include <Kokkos_Core.hpp>
14#include <vector>
15
16#include "contact_preprocessing.hpp" // ContactC, CpExec/CpMem
17#include "dem_portable.hpp"
18#include "solver_fused.hpp"
19
20namespace peclet::dem {
21
22namespace detail {
23KOKKOS_INLINE_FUNCTION float computeW(F3 r, F3 dir, float invM, F3 invI) {
24 const F3 rn = cross3v(r, dir);
25 return invM + rn.x * rn.x * invI.x + rn.y * rn.y * invI.y + rn.z * rn.z * invI.z;
26}
27KOKKOS_INLINE_FUNCTION F4 deltaQuat(F3 dTheta, F4 q) {
28 return F4{0.5f * (dTheta.x * q.w + dTheta.y * q.z - dTheta.z * q.y),
29 0.5f * (dTheta.y * q.w + dTheta.z * q.x - dTheta.x * q.z),
30 0.5f * (dTheta.z * q.w + dTheta.x * q.y - dTheta.y * q.x),
31 0.5f * (-dTheta.x * q.x - dTheta.y * q.y - dTheta.z * q.z)};
32}
33} // namespace detail
34
37 Kokkos::View<const ContactC*, CpMem> contacts, int numContacts,
38 Kokkos::View<const float*, CpMem> invMass, Kokkos::View<const float* [3], CpMem> posPred,
39 Kokkos::View<const float* [4], CpMem> quatPred,
40 Kokkos::View<const float* [4], CpMem> quatStatic,
41 Kokkos::View<const float* [3], CpMem> invInertia, Kokkos::View<float* [3], CpMem> deltaPos,
42 Kokkos::View<float* [4], CpMem> deltaQuat, Kokkos::View<int*, CpMem> constraintCounts,
43 Kokkos::View<float, CpMem> maxOverlap, Kokkos::View<const int*, CpMem> onlyColor = {},
44 int colorFilter = 0) {
45 using detail::computeW;
46 CpExec space;
47 const bool filt = onlyColor.extent(0) > 0;
48 Kokkos::parallel_for(
49 "peclet::dem::solve_position", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
50 KOKKOS_LAMBDA(int idx) {
51 if (filt && onlyColor(idx) != colorFilter)
52 return; // Jacobi fallback pass: only the contacts the colouring could not place
53 const ContactC c = contacts(idx);
54 const int idA = c.bodyA, idB = c.bodyB;
55 const float invMassA = invMass(idA);
56 const float invMassB = (idB >= 0) ? invMass(idB) : 0.0f;
57
58 const F3 pA = ldF3(posPred, idA);
59 const F4 qA = ldF4(quatPred, idA);
60 F3 pB{0, 0, 0};
61 F4 qB{0, 0, 0, 1};
62 if (idB >= 0) {
63 pB = ldF3(posPred, idB);
64 qB = ldF4(quatPred, idB);
65 }
66
67 // Delta-rotate the stored lever arms / normal from the static frame to the predicted one.
68 const F4 qAdelta = quatMult(qA, quatInverse(ldF4(quatStatic, idA)));
69 F3 rA = rotateVector(qAdelta, F3{c.rA.x, c.rA.y, c.rA.z});
70 F3 rB{c.rB.x, c.rB.y, c.rB.z};
71 F3 n{c.normal.x, c.normal.y, c.normal.z};
72 if (idB >= 0) {
73 const F4 qBdelta = quatMult(qB, quatInverse(ldF4(quatStatic, idB)));
74 rB = rotateVector(qBdelta, rB);
75 n = rotateVector(qBdelta, n);
76 }
77
78 float C;
79 if (idB < 0) {
80 n = F3{c.normal.x, c.normal.y, c.normal.z}; // wall normal is static
81 const F3 pAsurf = add3(pA, rA);
82 C = dot3(sub3(pAsurf, F3{c.rB.x, c.rB.y, c.rB.z}), n);
83 } else {
84 const F3 pAc = add3(pA, rA);
85 const F3 pBc = add3(pB, rB);
86 C = dot3(sub3(pAc, pBc), n);
87 }
88 if (C >= 0.0f)
89 return;
90
91 const F3 invIA = ldF3(invInertia, idA);
92 const F3 invIB = (idB >= 0) ? ldF3(invInertia, idB) : F3{0, 0, 0};
93 const float wTotal = computeW(rA, n, invMassA, invIA) + computeW(rB, n, invMassB, invIB);
94 if (wTotal < 1e-6f)
95 return;
96
97 const float dLambda = -C / wTotal;
98
99 // Linear + angular correction on A.
100 Kokkos::atomic_add(&deltaPos(idA, 0), n.x * dLambda * invMassA);
101 Kokkos::atomic_add(&deltaPos(idA, 1), n.y * dLambda * invMassA);
102 Kokkos::atomic_add(&deltaPos(idA, 2), n.z * dLambda * invMassA);
103 {
104 const F3 rn = cross3v(rA, n);
105 const F3 dTheta{rn.x * invIA.x * dLambda, rn.y * invIA.y * dLambda,
106 rn.z * invIA.z * dLambda};
107 const F4 dq = detail::deltaQuat(dTheta, qA);
108 Kokkos::atomic_add(&deltaQuat(idA, 0), dq.x);
109 Kokkos::atomic_add(&deltaQuat(idA, 1), dq.y);
110 Kokkos::atomic_add(&deltaQuat(idA, 2), dq.z);
111 Kokkos::atomic_add(&deltaQuat(idA, 3), dq.w);
112 }
113 if (idB >= 0) {
114 Kokkos::atomic_add(&deltaPos(idB, 0), -n.x * dLambda * invMassB);
115 Kokkos::atomic_add(&deltaPos(idB, 1), -n.y * dLambda * invMassB);
116 Kokkos::atomic_add(&deltaPos(idB, 2), -n.z * dLambda * invMassB);
117 const F3 rn = cross3v(rB, n);
118 const F3 dTheta{-rn.x * invIB.x * dLambda, -rn.y * invIB.y * dLambda,
119 -rn.z * invIB.z * dLambda};
120 const F4 dq = detail::deltaQuat(dTheta, qB);
121 Kokkos::atomic_add(&deltaQuat(idB, 0), dq.x);
122 Kokkos::atomic_add(&deltaQuat(idB, 1), dq.y);
123 Kokkos::atomic_add(&deltaQuat(idB, 2), dq.z);
124 Kokkos::atomic_add(&deltaQuat(idB, 3), dq.w);
125 Kokkos::atomic_add(&constraintCounts(idB), 1);
126 }
127 Kokkos::atomic_add(&constraintCounts(idA), 1);
128
129 if (C < 0.0f)
130 Kokkos::atomic_max(&maxOverlap(), -C);
131 });
132 space.fence();
133}
134
135// ============================ colored Gauss–Seidel position solve ============================
136// The overlap solve above is the position-side twin of the Jacobi restitution solve: one thread per
137// contact scatters an XPBD non-penetration correction, and the caller relaxes the per-body SUM by
138// the contact count (applyUpdatesKokkos, 1/count). Same trade-off — stable but under-converged, so
139// a body wedged by many neighbours keeps residual overlap. The colored path removes the averaging:
140// graph- colour the CONTACT graph (vertices = body slots, edges = contacts; raw bodyA/bodyB, the
141// same indices the solve writes) so no two contacts sharing a body get one colour, then sweep
142// colour-by- colour applying each correction IN PLACE. Within a colour the contacts are an
143// independent set, so the read-modify-write is race-free without atomics, and each sweep sees the
144// previous colours' moves — a true sequential projection that resolves stacked contacts far better
145// per iteration. Translation only, matching the Jacobi path (applyUpdatesKokkos applies deltaPos,
146// not deltaQuat; the angular contact response lives in the velocity solve), and it never touches
147// velocity — overlap removal stays decoupled from the velocity update.
148
154inline int colorContactsKokkos(Kokkos::View<const ContactC*, CpMem> contacts, int numContacts,
155 int numBodies, Kokkos::View<int*, CpMem> cColor,
156 Kokkos::View<long long*, CpMem> bodyWinner,
157 Kokkos::View<std::uint64_t*, CpMem> bodyMask, int& leftover,
158 Kokkos::View<const unsigned char*, CpMem> sleepMask = {}) {
159 leftover = 0;
160 CpExec space;
161 if (numContacts <= 0 || numBodies <= 0)
162 return 0;
163 const bool sleepOn = sleepMask.extent(0) > 0;
164 Kokkos::parallel_for(
165 "peclet::dem::pcolor_init_bodies", Kokkos::RangePolicy<CpExec>(space, 0, numBodies),
166 KOKKOS_LAMBDA(int i) { bodyMask(i) = 0; });
167 Kokkos::parallel_for(
168 "peclet::dem::pcolor_init_contacts", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
169 KOKKOS_LAMBDA(int idx) { cColor(idx) = (sleepOn && sleepMask(idx)) ? -2 : -1; });
170
171 int remaining = 1, prevRemaining = -1;
172 const int maxRounds = numBodies + 2;
173 for (int round = 0; round < maxRounds && remaining > 0; ++round) {
174 Kokkos::parallel_for(
175 "peclet::dem::pcolor_reset_winner", Kokkos::RangePolicy<CpExec>(space, 0, numBodies),
176 KOKKOS_LAMBDA(int i) { bodyWinner(i) = -1; });
177 Kokkos::parallel_for(
178 "peclet::dem::pcolor_contend", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
179 KOKKOS_LAMBDA(int idx) {
180 if (cColor(idx) != -1)
181 return;
182 const ContactC c = contacts(idx);
183 const long long key = colorKey(idx); // hashed priority (see solver_velocity.hpp)
184 Kokkos::atomic_max(&bodyWinner(c.bodyA), key);
185 if (c.bodyB >= 0)
186 Kokkos::atomic_max(&bodyWinner(c.bodyB), key);
187 });
188 int rem = 0;
189 Kokkos::parallel_reduce(
190 "peclet::dem::pcolor_commit", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
191 KOKKOS_LAMBDA(int idx, int& acc) {
192 if (cColor(idx) != -1)
193 return;
194 const ContactC c = contacts(idx);
195 const int ea = c.bodyA;
196 const int eb = c.bodyB; // <0 for a wall/boundary contact
197 const long long key = colorKey(idx);
198 if (bodyWinner(ea) != key || (eb >= 0 && bodyWinner(eb) != key)) {
199 acc += 1;
200 return;
201 }
202 std::uint64_t forbidden = bodyMask(ea);
203 if (eb >= 0)
204 forbidden |= bodyMask(eb);
205 int col = 0;
206 while (col < 62 && (forbidden & (std::uint64_t(1) << col)))
207 ++col;
208 cColor(idx) = col;
209 const std::uint64_t bit = std::uint64_t(1) << col;
210 bodyMask(ea) |= bit;
211 if (eb >= 0)
212 bodyMask(eb) |= bit;
213 },
214 rem);
215 space.fence();
216 // Stall detection: a body whose 62-colour mask fills (interpenetration degree > 62) can never
217 // host a new colour — without this break the loop would spin maxRounds (~numBodies) times doing
218 // nothing. The stuck contacts stay -1 and are handled by the Jacobi fallback in the solve.
219 if (rem == prevRemaining)
220 break;
221 prevRemaining = rem;
222 remaining = rem;
223 }
224
225 int maxc = -1;
226 Kokkos::parallel_reduce(
227 "peclet::dem::pcolor_max", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
228 KOKKOS_LAMBDA(int idx, int& mx) {
229 if (cColor(idx) > mx)
230 mx = cColor(idx);
231 },
232 Kokkos::Max<int>(maxc));
233 Kokkos::parallel_reduce(
234 "peclet::dem::pcolor_leftover", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
235 KOKKOS_LAMBDA(int idx, int& acc) {
236 if (cColor(idx) == -1)
237 acc += 1;
238 },
239 leftover);
240 space.fence();
241 return maxc + 1;
242}
243
254 Kokkos::View<const ContactC*, CpMem> contacts, int numContacts, int numBodies,
255 Kokkos::View<const unsigned long long*, CpMem> prevKeys,
256 Kokkos::View<const int*, CpMem> prevColor, int prevCount, Kokkos::View<int*, CpMem> cColor,
257 Kokkos::View<unsigned long long*, CpMem> keysOut, Kokkos::View<long long*, CpMem> bodyWinner,
258 Kokkos::View<std::uint64_t*, CpMem> bodyMask, int& leftover, bool forceFull,
259 Kokkos::View<const unsigned char*, CpMem> sleepMask = {}) {
260 leftover = 0;
261 CpExec space;
262 if (numContacts <= 0 || numBodies <= 0)
263 return 0;
264 const bool full0 = forceFull || prevCount <= 0;
265 const bool sleepOn = sleepMask.extent(0) > 0;
266 // Key every contact and seed its colour from the carried ledger (or -1 on a full recolour, or
267 // -2 for a frozen both-asleep contact — excluded from the sweeps like an inactive one).
268 Kokkos::parallel_for(
269 "peclet::dem::pcolor_i_seed", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
270 KOKKOS_LAMBDA(int idx) {
271 const ContactC c = contacts(idx);
272 const unsigned long long k = pairKey(c);
273 keysOut(idx) = k;
274 if (sleepOn && sleepMask(idx)) {
275 cColor(idx) = -2;
276 return;
277 }
278 int col = -1;
279 if (!full0) {
280 int lo = 0, hi = prevCount;
281 while (lo < hi) {
282 const int mid = (lo + hi) >> 1;
283 if (prevKeys(mid) < k)
284 lo = mid + 1;
285 else
286 hi = mid;
287 }
288 if (lo < prevCount && prevKeys(lo) == k) {
289 const int pc = prevColor(lo);
290 if (pc >= 0)
291 col = pc;
292 }
293 }
294 cColor(idx) = col;
295 });
296 Kokkos::parallel_for(
297 "peclet::dem::pcolor_i_init_bodies", Kokkos::RangePolicy<CpExec>(space, 0, numBodies),
298 KOKKOS_LAMBDA(int i) { bodyMask(i) = 0; });
299 if (!full0) {
300 // Seed the per-body masks from carried colours, self-healing any conflict WITHOUT a host sync
301 // (a host readback here stalls the async submission pipeline and measured net-slower than the
302 // launches it saves). atomic_fetch_or serialises the claim: a contact that finds its colour bit
303 // already set at either endpoint DEMOTES itself to -1 and is re-arbitrated in the rounds below.
304 // A demoted contact may leave a spurious set bit at its other endpoint, but a spurious bit only
305 // over-constrains (forbids one colour there) — it never lets two same-colour contacts share a
306 // body, so the colouring stays valid. The sphere / non-periodic bed has no conflicts (one
307 // contact per pair, stable slots) and hits the pure carry path.
308 Kokkos::parallel_for(
309 "peclet::dem::pcolor_i_mask", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
310 KOKKOS_LAMBDA(int idx) {
311 const int col = cColor(idx);
312 if (col < 0)
313 return;
314 const ContactC c = contacts(idx);
315 const std::uint64_t bit = std::uint64_t(1) << col;
316 bool conflict = ((Kokkos::atomic_fetch_or(&bodyMask(c.bodyA), bit) >> col) & 1) != 0;
317 if (c.bodyB >= 0)
318 conflict |= ((Kokkos::atomic_fetch_or(&bodyMask(c.bodyB), bit) >> col) & 1) != 0;
319 if (conflict)
320 cColor(idx) = -1; // re-arbitrate in the rounds
321 });
322 }
323 // Jones-Plassmann rounds over the uncoloured (-1) contacts only (identical to
324 // colorContactsKokkos).
325 int remaining = 1, prevRemaining = -1;
326 const int maxRounds = numBodies + 2;
327 for (int round = 0; round < maxRounds && remaining > 0; ++round) {
328 Kokkos::parallel_for(
329 "peclet::dem::pcolor_i_reset_winner", Kokkos::RangePolicy<CpExec>(space, 0, numBodies),
330 KOKKOS_LAMBDA(int i) { bodyWinner(i) = -1; });
331 Kokkos::parallel_for(
332 "peclet::dem::pcolor_i_contend", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
333 KOKKOS_LAMBDA(int idx) {
334 if (cColor(idx) != -1)
335 return;
336 const ContactC c = contacts(idx);
337 const long long key = colorKey(idx);
338 Kokkos::atomic_max(&bodyWinner(c.bodyA), key);
339 if (c.bodyB >= 0)
340 Kokkos::atomic_max(&bodyWinner(c.bodyB), key);
341 });
342 int rem = 0;
343 Kokkos::parallel_reduce(
344 "peclet::dem::pcolor_i_commit", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
345 KOKKOS_LAMBDA(int idx, int& acc) {
346 if (cColor(idx) != -1)
347 return;
348 const ContactC c = contacts(idx);
349 const int ea = c.bodyA;
350 const int eb = c.bodyB;
351 const long long key = colorKey(idx);
352 if (bodyWinner(ea) != key || (eb >= 0 && bodyWinner(eb) != key)) {
353 acc += 1;
354 return;
355 }
356 std::uint64_t forbidden = bodyMask(ea);
357 if (eb >= 0)
358 forbidden |= bodyMask(eb);
359 int col = 0;
360 while (col < 62 && (forbidden & (std::uint64_t(1) << col)))
361 ++col;
362 cColor(idx) = col;
363 const std::uint64_t bit = std::uint64_t(1) << col;
364 bodyMask(ea) |= bit;
365 if (eb >= 0)
366 bodyMask(eb) |= bit;
367 },
368 rem);
369 space.fence();
370 if (rem == prevRemaining)
371 break;
372 prevRemaining = rem;
373 remaining = rem;
374 }
375 int maxc = -1;
376 Kokkos::parallel_reduce(
377 "peclet::dem::pcolor_i_max", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
378 KOKKOS_LAMBDA(int idx, int& mx) {
379 if (cColor(idx) > mx)
380 mx = cColor(idx);
381 },
382 Kokkos::Max<int>(maxc));
383 Kokkos::parallel_reduce(
384 "peclet::dem::pcolor_i_leftover", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
385 KOKKOS_LAMBDA(int idx, int& acc) {
386 if (cColor(idx) == -1)
387 acc += 1;
388 },
389 leftover);
390 space.fence();
391 return maxc + 1;
392}
393
395inline void commitContactColorKokkos(Kokkos::View<const unsigned long long*, CpMem> keys,
396 Kokkos::View<const int*, CpMem> color,
397 Kokkos::View<unsigned long long*, CpMem> prevKeys,
398 Kokkos::View<int*, CpMem> prevColor,
399 Kokkos::View<int*, CpMem> perm, int numContacts) {
400 if (numContacts <= 0)
401 return;
402 CpExec space;
403 const int n = numContacts;
404 const auto rng = Kokkos::pair<int, int>(0, n);
405 auto kd = Kokkos::subview(prevKeys, rng);
406 Kokkos::deep_copy(space, kd, Kokkos::subview(keys, rng));
407 Kokkos::parallel_for(
408 "peclet::dem::pcolor_commit_iota", Kokkos::RangePolicy<CpExec>(space, 0, n),
409 KOKKOS_LAMBDA(int i) { perm(i) = i; });
410 {
411 auto pd = Kokkos::subview(perm, rng);
412 Kokkos::Experimental::sort_by_key(space, kd, pd);
413 }
414 Kokkos::View<int*, CpMem> pc = prevColor;
415 Kokkos::View<const int*, CpMem> c = color;
416 Kokkos::parallel_for(
417 "peclet::dem::pcolor_commit_gather", Kokkos::RangePolicy<CpExec>(space, 0, n),
418 KOKKOS_LAMBDA(int i) { pc(i) = c(perm(i)); });
419 space.fence();
420}
421
426 Kokkos::View<const ContactC*, CpMem> contacts;
427 Kokkos::View<const float*, CpMem> invMass;
428 Kokkos::View<float* [3], CpMem> posPred;
429 Kokkos::View<const float* [4], CpMem> quatPred;
430 Kokkos::View<const float* [4], CpMem> quatStatic;
431 Kokkos::View<const float* [3], CpMem> invInertia;
432 Kokkos::View<float, CpMem> maxOverlap;
433 Kokkos::View<float*, CpMem> posLambdaAcc;
434
435 KOKKOS_FUNCTION void solveOne(int idx) const {
436 using detail::computeW;
437 const ContactC c = contacts(idx);
438 const int idA = c.bodyA, idB = c.bodyB;
439 const float invMassA = invMass(idA);
440 const float invMassB = (idB >= 0) ? invMass(idB) : 0.0f;
441
442 const F3 pA = ldF3(posPred, idA);
443 const F4 qA = ldF4(quatPred, idA);
444 F3 pB{0, 0, 0};
445 F4 qB{0, 0, 0, 1};
446 if (idB >= 0) {
447 pB = ldF3(posPred, idB);
448 qB = ldF4(quatPred, idB);
449 }
450
451 const F4 qAdelta = quatMult(qA, quatInverse(ldF4(quatStatic, idA)));
452 F3 rA = rotateVector(qAdelta, F3{c.rA.x, c.rA.y, c.rA.z});
453 F3 rB{c.rB.x, c.rB.y, c.rB.z};
454 F3 n{c.normal.x, c.normal.y, c.normal.z};
455 if (idB >= 0) {
456 const F4 qBdelta = quatMult(qB, quatInverse(ldF4(quatStatic, idB)));
457 rB = rotateVector(qBdelta, rB);
458 n = rotateVector(qBdelta, n);
459 }
460
461 float C;
462 if (idB < 0) {
463 n = F3{c.normal.x, c.normal.y, c.normal.z};
464 const F3 pAsurf = add3(pA, rA);
465 C = dot3(sub3(pAsurf, F3{c.rB.x, c.rB.y, c.rB.z}), n);
466 } else {
467 const F3 pAc = add3(pA, rA);
468 const F3 pBc = add3(pB, rB);
469 C = dot3(sub3(pAc, pBc), n);
470 }
471 if (C >= 0.0f)
472 return;
473
474 const F3 invIA = ldF3(invInertia, idA);
475 const F3 invIB = (idB >= 0) ? ldF3(invInertia, idB) : F3{0, 0, 0};
476 const float wTotal = computeW(rA, n, invMassA, invIA) + computeW(rB, n, invMassB, invIB);
477 if (wTotal < 1e-6f)
478 return;
479 const float dLambda = -C / wTotal;
480 // Position-channel normal load bookkeeping: the friction cone must see the TOTAL normal
481 // force; whatever de-penetration flows through this projection (instead of the velocity
482 // impulses) is accumulated here, converted to impulse units by the caller, and carried
483 // into the next substep's Coulomb bound (else a jostled bed's bound under-counts and
484 // stick leaks -- measured as 99% sliding wall contacts in the benchmark drum).
485 if (posLambdaAcc.extent(0) > 0)
486 Kokkos::atomic_add(&posLambdaAcc(idx), dLambda);
487
488 // Translation-only correction, in place (rotation discarded to match applyUpdatesKokkos).
489 posPred(idA, 0) += n.x * dLambda * invMassA;
490 posPred(idA, 1) += n.y * dLambda * invMassA;
491 posPred(idA, 2) += n.z * dLambda * invMassA;
492 if (idB >= 0) {
493 posPred(idB, 0) += -n.x * dLambda * invMassB;
494 posPred(idB, 1) += -n.y * dLambda * invMassB;
495 posPred(idB, 2) += -n.z * dLambda * invMassB;
496 }
497 Kokkos::atomic_max(&maxOverlap(), -C);
498 }
499};
500
510 Kokkos::View<const ContactC*, CpMem> contacts, int numContacts,
511 Kokkos::View<const int*, CpMem> cColor, int numColors,
512 Kokkos::View<const float*, CpMem> invMass, Kokkos::View<float* [3], CpMem> posPred,
513 Kokkos::View<const float* [4], CpMem> quatPred,
514 Kokkos::View<const float* [4], CpMem> quatStatic,
515 Kokkos::View<const float* [3], CpMem> invInertia, Kokkos::View<float, CpMem> maxOverlap,
516 Kokkos::View<float*, CpMem> posLambdaAcc = {}, Kokkos::View<const int*, CpMem> colorPerm = {},
517 const std::vector<int>* colorOffs = nullptr, const FusedSweepCtx* fused = nullptr,
518 const FusedLoopSpec* loop = nullptr) {
519 CpExec space;
520 const PositionContactSweep f{contacts, invMass, posPred, quatPred,
521 quatStatic, invInertia, maxOverlap, posLambdaAcc};
522#ifdef KOKKOS_ENABLE_CUDA
523 if (loop) {
524 if (fused && fused->maxBucket > 0 && colorOffs)
525 return demLaunchFusedSweepLoop(space, f, colorPerm, *fused, numColors, *loop,
526 maxOverlap.data());
527 return false;
528 }
529 if (fused && fused->maxBucket > 0 && colorOffs &&
530 demLaunchFusedColorSweep(space, f, colorPerm, *fused, numColors))
531 return true;
532#else
533 (void)fused;
534 if (loop)
535 return false;
536#endif
537 for (int color = 0; color < numColors; ++color) {
538 if (colorOffs) {
539 const int b = (*colorOffs)[color], e = (*colorOffs)[color + 1];
540 if (b == e)
541 continue;
542 Kokkos::parallel_for(
543 "peclet::dem::solve_position_gs", Kokkos::RangePolicy<CpExec>(space, b, e),
544 KOKKOS_LAMBDA(int i2) { f.solveOne(colorPerm(i2)); });
545 } else {
546 Kokkos::parallel_for(
547 "peclet::dem::solve_position_gs", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
548 KOKKOS_LAMBDA(int idx) {
549 if (cColor(idx) == color)
550 f.solveOne(idx);
551 });
552 }
553 // Stream-ordered on the device, so colour c+1 already sees colour c's moves — no host fence per
554 // colour (that would only stall the host). No trailing fence either: the caller's residual
555 // readback synchronizes, and a fence here would break CUDA-graph capture of the sweep.
556 }
557 return true;
558}
559
560} // namespace peclet::dem
561
562#endif // DEM_SOLVER_POSITION_HPP
dem — portable (Kokkos) contact->manifold reduction, replacing the thrust-based reduce_contacts_to_ma...
dem — portable POD types + math + analytic SDFs shared by the Kokkos kernel ports.
F4 deltaQuat(F3 dTheta, F4 q)
float computeW(F3 r, F3 dir, float invM, F3 invI)
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.
F4 quatMult(F4 a, F4 b)
std::uint64_t pairKey(const ContactC &c)
Canonical pair key: (min<<32)|max, or (idA<<32)|0xFFFFFFFF for a boundary (idB<0) contact.
F4 quatInverse(F4 q)
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,...
F3 cross3v(F3 a, F3 b)
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...
F3 ldF3(const V &v, int i)
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.
F3 rotateVector(F4 q, F3 v)
float dot3(F3 a, F3 b)
F3 sub3(F3 a, F3 b)
CpExec::memory_space CpMem
F4 ldF4(const V &v, int i)
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.
long long colorKey(int idx)
splitmix32 finalizer: a well-mixed pseudo-random priority per edge index.
F3 add3(F3 a, F3 b)
Kokkos::DefaultExecutionSpace CpExec
dem — fused colour sweeps: one persistent kernel per sweep instead of one kernel launch per colour.
Portable mirror of ParticleSystem.cuh ContactConstraint (the fields this reduction touches).
The per-contact overlap-projection body lives in PositionContactSweep so the colored launch loop and ...
Kokkos::View< float *, CpMem > posLambdaAcc
Kokkos::View< const float *[4], CpMem > quatPred
Kokkos::View< const float *[4], CpMem > quatStatic
Kokkos::View< float, CpMem > maxOverlap
Kokkos::View< const float *[3], CpMem > invInertia
Kokkos::View< const ContactC *, CpMem > contacts
Kokkos::View< float *[3], CpMem > posPred
Kokkos::View< const float *, CpMem > invMass