10#ifndef DEM_SOLVER_POSITION_HPP
11#define DEM_SOLVER_POSITION_HPP
13#include <Kokkos_Core.hpp>
25 return invM + rn.
x * rn.
x * invI.
x + rn.
y * rn.
y * invI.
y + rn.
z * rn.
z * invI.
z;
34 int numContacts, Kokkos::View<int*, CpMem> counts) {
37 "peclet::dem::count_position_jacobi", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
38 KOKKOS_LAMBDA(
int idx) {
40 Kokkos::atomic_add(&counts(c.
bodyA), 1);
42 Kokkos::atomic_add(&counts(c.
bodyB), 1);
56 Kokkos::View<const ContactC*, CpMem> contacts,
int numContacts,
57 Kokkos::View<const float*, CpMem> invMass, Kokkos::View<
const float* [3],
CpMem> posPred,
58 Kokkos::View<
const float* [4],
CpMem> quatPred,
59 Kokkos::View<
const float* [4],
CpMem> quatStatic,
60 Kokkos::View<
const float* [3],
CpMem> invInertia, Kokkos::View<
float* [3],
CpMem> deltaPos,
61 Kokkos::View<
float* [4],
CpMem> deltaQuat, Kokkos::View<int*, CpMem> constraintCounts,
62 Kokkos::View<float, CpMem> maxOverlap, Kokkos::View<const int*, CpMem> onlyColor = {},
63 int colorFilter = 0,
bool massSplit =
false) {
67 const bool filt = onlyColor.extent(0) > 0;
69 "peclet::dem::solve_position", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
70 KOKKOS_LAMBDA(
int idx) {
71 if (filt && onlyColor(idx) != colorFilter)
73 const ContactC c = contacts(idx);
74 const int idA = c.bodyA, idB = c.bodyB;
75 const float invMassA = invMass(idA);
76 const float invMassB = (idB >= 0) ? invMass(idB) : 0.0f;
78 const F3 pA =
ldF3(posPred, idA);
79 const F4 qA =
ldF4(quatPred, idA);
83 pB =
ldF3(posPred, idB);
84 qB =
ldF4(quatPred, idB);
89 F3 rA =
rotateVector(qAdelta, F3{c.rA.x, c.rA.y, c.rA.z});
90 F3 rB{c.rB.
x, c.rB.y, c.rB.z};
91 F3 n{c.normal.x, c.normal.y, c.normal.z};
100 n = F3{c.normal.
x, c.normal.y, c.normal.z};
101 const F3 pAsurf =
add3(pA, rA);
102 C =
dot3(
sub3(pAsurf, F3{c.rB.x, c.rB.y, c.rB.z}), n);
104 const F3 pAc =
add3(pA, rA);
105 const F3 pBc =
add3(pB, rB);
114 const float wA = invMassA;
115 const float wB = invMassB;
116 const float wTotal = wA + wB;
121 massSplit ?
static_cast<float>(constraintCounts(idA)) * wA +
122 ((idB >= 0) ?
static_cast<float>(constraintCounts(idB)) * wB : 0.0f)
125 const float dLambda = -C / wSolve;
128 Kokkos::atomic_add(&deltaPos(idA, 0), n.x * dLambda * invMassA);
129 Kokkos::atomic_add(&deltaPos(idA, 1), n.y * dLambda * invMassA);
130 Kokkos::atomic_add(&deltaPos(idA, 2), n.z * dLambda * invMassA);
132 Kokkos::atomic_add(&deltaPos(idB, 0), -n.x * dLambda * invMassB);
133 Kokkos::atomic_add(&deltaPos(idB, 1), -n.y * dLambda * invMassB);
134 Kokkos::atomic_add(&deltaPos(idB, 2), -n.z * dLambda * invMassB);
136 Kokkos::atomic_add(&constraintCounts(idB), 1);
139 Kokkos::atomic_add(&constraintCounts(idA), 1);
142 Kokkos::atomic_max(&maxOverlap(), -C);
163 const Kokkos::View<const unsigned char*, CpMem>& sleepMask,
165 for (
int k = units.
begin(u); k < units.
end(u); ++k)
166 if (!sleepMask(units.
contact(k)))
181 int numBodies, Kokkos::View<int*, CpMem> cColor,
182 Kokkos::View<long long*, CpMem> bodyWinner,
183 Kokkos::View<std::uint64_t*, CpMem> bodyMask,
int& leftover,
184 Kokkos::View<const unsigned char*, CpMem> sleepMask = {},
185 PosUnits units = {}, SlotOverride ov = {},
int vertexSpan = 0) {
188 if (numContacts <= 0 || numBodies <= 0)
192 const int nV = vertexSpan > 0 ? vertexSpan : numBodies;
193 const bool sleepOn = sleepMask.extent(0) > 0;
194 Kokkos::parallel_for(
195 "peclet::dem::pcolor_init_bodies", Kokkos::RangePolicy<CpExec>(space, 0, nV),
196 KOKKOS_LAMBDA(
int i) { bodyMask(i) = 0; });
197 Kokkos::parallel_for(
198 "peclet::dem::pcolor_init_contacts", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
199 KOKKOS_LAMBDA(
int idx) {
200 cColor(idx) = (sleepOn &&
unitAsleep(units, sleepMask, idx)) ? -2 : -1;
203 int remaining = 1, prevRemaining = -1;
208 const int maxRounds = numContacts + 2;
209 for (
int round = 0; round < maxRounds && remaining > 0; ++round) {
210 Kokkos::parallel_for(
211 "peclet::dem::pcolor_reset_winner", Kokkos::RangePolicy<CpExec>(space, 0, nV),
212 KOKKOS_LAMBDA(
int i) { bodyWinner(i) = -1; });
213 Kokkos::parallel_for(
214 "peclet::dem::pcolor_contend", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
215 KOKKOS_LAMBDA(
int idx) {
216 if (cColor(idx) != -1)
218 const int lead = units.leader(idx);
219 const ContactC c = contacts(lead);
220 const long long key =
colorKey(lead);
221 Kokkos::atomic_max(&bodyWinner(ov.slotA(idx, c.bodyA)), key);
223 Kokkos::atomic_max(&bodyWinner(ov.slotB(idx, c.bodyB)), key);
226 Kokkos::parallel_reduce(
227 "peclet::dem::pcolor_commit", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
228 KOKKOS_LAMBDA(
int idx,
int& acc) {
229 if (cColor(idx) != -1)
231 const int lead = units.leader(idx);
232 const ContactC c = contacts(lead);
233 const int ea = ov.slotA(idx, c.bodyA);
234 const int eb = (c.bodyB >= 0) ? ov.slotB(idx, c.bodyB) : -1;
235 const long long key =
colorKey(lead);
236 if (bodyWinner(ea) != key || (eb >= 0 && bodyWinner(eb) != key)) {
240 std::uint64_t forbidden = bodyMask(ea);
242 forbidden |= bodyMask(eb);
246#if defined(PECLET_DEM_TEST_MUTANT) && PECLET_DEM_TEST_MUTANT == 3
255 const std::uint64_t bit = std::uint64_t(1) << col;
264 if (rem == prevRemaining)
271 Kokkos::parallel_reduce(
272 "peclet::dem::pcolor_max", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
273 KOKKOS_LAMBDA(
int idx,
int& mx) {
274 if (cColor(idx) > mx)
277 Kokkos::Max<int>(maxc));
278 Kokkos::parallel_reduce(
279 "peclet::dem::pcolor_leftover", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
280 KOKKOS_LAMBDA(
int idx,
int& acc) {
299 Kokkos::View<const ContactC*, CpMem> contacts,
int numContacts,
int numBodies,
300 Kokkos::View<const unsigned long long*, CpMem> prevKeys,
301 Kokkos::View<const int*, CpMem> prevColor,
int prevCount, Kokkos::View<int*, CpMem> cColor,
302 Kokkos::View<unsigned long long*, CpMem> keysOut, Kokkos::View<long long*, CpMem> bodyWinner,
303 Kokkos::View<std::uint64_t*, CpMem> bodyMask,
int& leftover,
bool forceFull,
304 Kokkos::View<const unsigned char*, CpMem> sleepMask = {}, PosUnits units = {}) {
307 if (numContacts <= 0 || numBodies <= 0)
309 const bool full0 = forceFull || prevCount <= 0;
310 const bool sleepOn = sleepMask.extent(0) > 0;
313 Kokkos::parallel_for(
314 "peclet::dem::pcolor_i_seed", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
315 KOKKOS_LAMBDA(
int idx) {
318 for (
int k2 = units.begin(idx); k2 < units.end(idx); ++k2) {
319 const int ci = units.contact(k2);
320 keysOut(ci) =
pairKey(contacts(ci));
322 const unsigned long long k = keysOut(units.leader(idx));
323 if (sleepOn &&
unitAsleep(units, sleepMask, idx)) {
329 int lo = 0, hi = prevCount;
331 const int mid = (lo + hi) >> 1;
332 if (prevKeys(mid) < k)
337 if (lo < prevCount && prevKeys(lo) == k) {
338 const int pc = prevColor(lo);
345 Kokkos::parallel_for(
346 "peclet::dem::pcolor_i_init_bodies", Kokkos::RangePolicy<CpExec>(space, 0, numBodies),
347 KOKKOS_LAMBDA(
int i) { bodyMask(i) = 0; });
357 Kokkos::parallel_for(
358 "peclet::dem::pcolor_i_mask", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
359 KOKKOS_LAMBDA(
int idx) {
360 const int col = cColor(idx);
363 const ContactC c = contacts(units.leader(idx));
364 const std::uint64_t bit = std::uint64_t(1) << col;
365 bool conflict = ((Kokkos::atomic_fetch_or(&bodyMask(c.bodyA), bit) >> col) & 1) != 0;
367 conflict |= ((Kokkos::atomic_fetch_or(&bodyMask(c.bodyB), bit) >> col) & 1) != 0;
374 int remaining = 1, prevRemaining = -1;
379 const int maxRounds = numContacts + 2;
380 for (
int round = 0; round < maxRounds && remaining > 0; ++round) {
381 Kokkos::parallel_for(
382 "peclet::dem::pcolor_i_reset_winner", Kokkos::RangePolicy<CpExec>(space, 0, numBodies),
383 KOKKOS_LAMBDA(
int i) { bodyWinner(i) = -1; });
384 Kokkos::parallel_for(
385 "peclet::dem::pcolor_i_contend", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
386 KOKKOS_LAMBDA(
int idx) {
387 if (cColor(idx) != -1)
389 const int lead = units.leader(idx);
390 const ContactC c = contacts(lead);
391 const long long key =
colorKey(lead);
392 Kokkos::atomic_max(&bodyWinner(c.bodyA), key);
394 Kokkos::atomic_max(&bodyWinner(c.bodyB), key);
397 Kokkos::parallel_reduce(
398 "peclet::dem::pcolor_i_commit", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
399 KOKKOS_LAMBDA(
int idx,
int& acc) {
400 if (cColor(idx) != -1)
402 const int lead = units.leader(idx);
403 const ContactC c = contacts(lead);
404 const int ea = c.bodyA;
405 const int eb = c.bodyB;
406 const long long key =
colorKey(lead);
407 if (bodyWinner(ea) != key || (eb >= 0 && bodyWinner(eb) != key)) {
411 std::uint64_t forbidden = bodyMask(ea);
413 forbidden |= bodyMask(eb);
417#if defined(PECLET_DEM_TEST_MUTANT) && PECLET_DEM_TEST_MUTANT == 3
426 const std::uint64_t bit = std::uint64_t(1) << col;
433 if (rem == prevRemaining)
439 Kokkos::parallel_reduce(
440 "peclet::dem::pcolor_i_max", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
441 KOKKOS_LAMBDA(
int idx,
int& mx) {
442 if (cColor(idx) > mx)
445 Kokkos::Max<int>(maxc));
446 Kokkos::parallel_reduce(
447 "peclet::dem::pcolor_i_leftover", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
448 KOKKOS_LAMBDA(
int idx,
int& acc) {
459 Kokkos::View<const int*, CpMem> unitColor,
460 Kokkos::View<int*, CpMem> contactColor) {
464 Kokkos::parallel_for(
465 "peclet::dem::pcolor_expand", Kokkos::RangePolicy<CpExec>(space, 0, numUnits),
466 KOKKOS_LAMBDA(
int u) {
467 const int col = unitColor(u);
468 for (
int k = units.
begin(u); k < units.
end(u); ++k)
469 contactColor(units.
contact(k)) = col;
475 Kokkos::View<const int*, CpMem> color,
476 Kokkos::View<unsigned long long*, CpMem> prevKeys,
477 Kokkos::View<int*, CpMem> prevColor,
478 Kokkos::View<int*, CpMem> perm,
int numContacts) {
479 if (numContacts <= 0)
482 const int n = numContacts;
483 const auto rng = Kokkos::pair<int, int>(0, n);
484 auto kd = Kokkos::subview(prevKeys, rng);
485 Kokkos::deep_copy(space, kd, Kokkos::subview(keys, rng));
486 Kokkos::parallel_for(
487 "peclet::dem::pcolor_commit_iota", Kokkos::RangePolicy<CpExec>(space, 0, n),
488 KOKKOS_LAMBDA(
int i) { perm(i) = i; });
490 auto pd = Kokkos::subview(perm, rng);
491 Kokkos::Experimental::sort_by_key(space, kd, pd);
493 Kokkos::View<int*, CpMem> pc = prevColor;
494 Kokkos::View<const int*, CpMem> c = color;
495 Kokkos::parallel_for(
496 "peclet::dem::pcolor_commit_gather", Kokkos::RangePolicy<CpExec>(space, 0, n),
497 KOKKOS_LAMBDA(
int i) { pc(i) = c(perm(i)); });
539 if (
ov.
a.extent(0) > 0) {
547 const float invMassA =
invMass(idA);
548 const float invMassB = (idB >= 0) ?
invMass(idB) : 0.0f;
572 const F3 pAsurf =
add3(pA, rA);
575 const F3 pAc =
add3(pA, rA);
576 const F3 pBc =
add3(pB, rB);
580 const float lam = accumulated ?
posLambdaAcc(idx) : 0.0f;
581 if (C >= 0.0f && lam <= 0.0f)
588 const float wTotal = invMassA + invMassB;
595#if defined(PECLET_DEM_TEST_MUTANT) && PECLET_DEM_TEST_MUTANT == 7
597 const float lamNew = Kokkos::fmax(lam, lam -
omega * C / wTotal);
599 const float lamNew = Kokkos::fmax(0.0f, lam -
omega * C / wTotal);
601 dLambda = lamNew - lam;
605 Kokkos::atomic_max(&
posResidual(), Kokkos::fabs(dLambda) * wTotal);
607 dLambda = -C / wTotal;
617 posPred(idA, 0) += n.x * dLambda * invMassA;
618 posPred(idA, 1) += n.y * dLambda * invMassA;
619 posPred(idA, 2) += n.z * dLambda * invMassA;
621 posPred(idB, 0) += -n.x * dLambda * invMassB;
622 posPred(idB, 1) += -n.y * dLambda * invMassB;
623 posPred(idB, 2) += -n.z * dLambda * invMassB;
637 Kokkos::View<const ContactC*, CpMem> contacts,
const PosUnits& units,
int numContacts,
638 Kokkos::View<const int*, CpMem> cColor,
int numColors,
639 Kokkos::View<const float*, CpMem> invMass, Kokkos::View<
float* [3],
CpMem> posPred,
640 Kokkos::View<
const float* [4],
CpMem> quatPred,
641 Kokkos::View<
const float* [4],
CpMem> quatStatic,
642 Kokkos::View<
const float* [3],
CpMem> invInertia, Kokkos::View<float, CpMem> maxOverlap,
643 Kokkos::View<float*, CpMem> posLambdaAcc = {}, Kokkos::View<const int*, CpMem> colorPerm = {},
644 const std::vector<int>* colorOffs =
nullptr,
const FusedSweepCtx* fused =
nullptr,
645 const FusedLoopSpec* loop =
nullptr, SlotOverride ov = {},
646 Kokkos::View<float, CpMem> posResidual = {},
float omega = 1.0f) {
648 const PositionContactSweep f{contacts, invMass, posPred, quatPred, quatStatic, invInertia,
649 maxOverlap, posLambdaAcc, units, ov, posResidual, omega};
650#ifdef KOKKOS_ENABLE_CUDA
654 if (fused && fused->maxBucket > 0 && colorOffs)
655 return demLaunchFusedSweepLoop(
656 space, f, colorPerm, *fused, numColors, *loop,
657 posResidual.extent(0) > 0 ? posResidual.data() : maxOverlap.data());
660 if (fused && fused->maxBucket > 0 && colorOffs &&
661 demLaunchFusedColorSweep(space, f, colorPerm, *fused, numColors))
668 for (
int color = 0; color < numColors; ++color) {
670 const int b = (*colorOffs)[color], e = (*colorOffs)[color + 1];
673 Kokkos::parallel_for(
674 "peclet::dem::solve_position_gs", Kokkos::RangePolicy<CpExec>(space, b, e),
675 KOKKOS_LAMBDA(
int i2) { f.solveOne(colorPerm(i2)); });
677 Kokkos::parallel_for(
678 "peclet::dem::solve_position_gs", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
679 KOKKOS_LAMBDA(
int idx) {
680 if (cColor(idx) == color)
dem — portable POD types + math + analytic SDFs shared by the Kokkos kernel ports.
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.
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, bool massSplit=false)
Accumulate XPBD position corrections for numContacts contacts.
std::uint64_t pairKey(const ContactC &c)
Canonical pair key: (min<<32)|max, or (idA<<32)|0xFFFFFFFF for a boundary (idB<0) contact.
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={}, PosUnits units={}, SlotOverride ov={}, int vertexSpan=0)
Greedy graph-colour the contacts (raw bodyA/bodyB): no two contacts sharing a body get the same colou...
bool unitAsleep(const PosUnits &units, const Kokkos::View< const unsigned char *, CpMem > &sleepMask, int u)
A position unit is sleep-inactive iff all its contacts are sleep-masked (§4.3).
F3 ldF3(const V &v, int i)
F3 rotateVector(F4 q, F3 v)
CpExec::memory_space CpMem
F4 ldF4(const V &v, int i)
void countPositionJacobiKokkos(Kokkos::View< const ContactC *, CpMem > contacts, int numContacts, Kokkos::View< int *, CpMem > counts)
Mass-split Jacobi count pass (docs/contact_solve_framework.md §3.1, D3): per body slot,...
long long colorKey(int idx)
splitmix32 finalizer: a well-mixed pseudo-random priority per edge index.
constexpr int kColorUncolourable
constexpr int kColorPalette
Colouring palette (docs/contact_solve_framework.md §4.2 item 1): the greedy takes the lowest colour f...
bool normalOfA(const ContactC &c)
Which body's surface a contact's normal belongs to.
void expandUnitColorsKokkos(const PosUnits &units, int numUnits, Kokkos::View< const int *, CpMem > unitColor, Kokkos::View< int *, CpMem > contactColor)
Copy each position unit's colour onto its contacts (contactColor(i) = unitColor(unit of i)).
bool solvePositionColoredGSKokkos(Kokkos::View< const ContactC *, CpMem > contacts, const PosUnits &units, 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, SlotOverride ov={}, Kokkos::View< float, CpMem > posResidual={}, float omega=1.0f)
Colored Gauss–Seidel XPBD overlap solve: sweep the numColors colour classes in order,...
Kokkos::DefaultExecutionSpace CpExec
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={}, PosUnits units={})
Incremental (warm-started) contact colouring for the single-GPU PGS position solve.
dem — fused colour sweeps: one persistent kernel per sweep instead of one kernel launch per colour.
The position units of a contact list as a CSR (§4.3): unit u owns the contacts list(start(u)) .
Per-edge slot overrides of a phase's hub copies (docs/contact_solve_framework.md §4....
int slotA(int e, int def) const
int slotB(int e, int def) const
Kokkos::View< const int *, CpMem > a