peclet-dem
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 so
41 // the solvers fall back to the global material and a zero wall velocity (unchanged behaviour). ---
42 F4 boundaryVel{0.0f, 0.0f, 0.0f, 0.0f}; // wall surface velocity at the contact point (xyz)
43 float boundaryRestitution{-1.0f}; // per-wall normal restitution; < 0 => use the global one
44 float boundaryFriction{-1.0f}; // per-wall Coulomb friction; < 0 => use the global one
45};
46
48struct ManifoldC {
49 int bodyA;
50 int bodyB;
57 // Σ over the manifold's active contacts of the boundary (idB<0) extension; the velocity solve
58 // averages by num_points. wallVel_sum -> the wall's surface velocity seen by this contact patch;
59 // restitution_sum -> per-wall restitution (a < 0 average keeps the global material).
60 F4 wallVel_sum{0.0f, 0.0f, 0.0f, 0.0f};
61 float restitution_sum{0.0f};
62};
63
65KOKKOS_INLINE_FUNCTION std::uint64_t pairKey(const ContactC& c) {
66 const int idA = c.bodyA, idB = c.bodyB;
67 if (idB < 0)
68 return (static_cast<std::uint64_t>(static_cast<unsigned>(idA)) << 32) | 0xFFFFFFFFu;
69 const unsigned u = (idA < idB) ? idA : idB;
70 const unsigned v = (idA < idB) ? idB : idA;
71 return (static_cast<std::uint64_t>(u) << 32) | v;
72}
73
75KOKKOS_INLINE_FUNCTION void decodeKey(std::uint64_t key, int& bodyA, int& bodyB) {
76 const unsigned v = static_cast<unsigned>(key & 0xFFFFFFFFu);
77 bodyA = static_cast<int>(key >> 32);
78 bodyB = (v == 0xFFFFFFFFu) ? -1 : static_cast<int>(v);
79}
80
84KOKKOS_INLINE_FUNCTION ManifoldC transformContact(const ContactC& c) {
85 ManifoldC m{};
86 const int idA = c.bodyA, idB = c.bodyB;
87 if (c.dist > 0.0f) {
88 m.num_points = 0;
89 return m; // inactive (sums already zero)
90 }
91 const bool flip = (idB >= 0 && idB < idA);
92 m.num_points = 1;
93
94 const F4 n_vec = c.normal;
95 F4 n_aligned = flip ? n_vec : F4{-n_vec.x, -n_vec.y, -n_vec.z, 0.0f};
96
97 const F4 shift{c.normal.x * c.dist * 0.5f, c.normal.y * c.dist * 0.5f, c.normal.z * c.dist * 0.5f,
98 0.0f};
99 const F4 rA_mid{c.rA.x - shift.x, c.rA.y - shift.y, c.rA.z - shift.z, 0.0f};
100 const F4 rB_mid{c.rB.x + shift.x, c.rB.y + shift.y, c.rB.z + shift.z, 0.0f};
101
102 const F4 r_can = flip ? rB_mid : rA_mid;
103 const F4 r_other = flip ? rA_mid : rB_mid;
104
105 const F4 tau_can = cross3(r_can, n_aligned);
106 const F4 tau_other = cross3(r_other, F4{-n_aligned.x, -n_aligned.y, -n_aligned.z, 0.0f});
107
108 m.normal_sum = F4{n_aligned.x, n_aligned.y, n_aligned.z, 0.0f};
109 m.torque_armA_sum = F4{tau_can.x, tau_can.y, tau_can.z, 0.0f};
110 m.torque_armB_sum = F4{tau_other.x, tau_other.y, tau_other.z, 0.0f};
111 m.rA_sum = F4{r_can.x, r_can.y, r_can.z, 0.0f};
112 m.rB_sum = F4{r_other.x, r_other.y, r_other.z, 0.0f};
113 // Boundary (idB<0) moving-wall extension: carry the wall velocity + per-wall restitution through
114 // to the (count-averaged) velocity solve. Zero / -1 sentinel for body-body & static planes.
115 m.wallVel_sum = F4{c.boundaryVel.x, c.boundaryVel.y, c.boundaryVel.z, 0.0f};
116 m.restitution_sum = c.boundaryRestitution;
117 return m;
118}
119
122inline int reduceContactsToManifoldsKokkos(Kokkos::View<const ContactC*, CpMem> contacts, int n,
123 Kokkos::View<ManifoldC*, CpMem> outManifolds,
124 Kokkos::View<int, CpMem> outCount) {
125 CpExec space;
126 if (n == 0) {
127 Kokkos::deep_copy(space, outCount, 0);
128 return 0;
129 }
130
131 // 1. Key every contact and seed an identity permutation.
132 Kokkos::View<std::uint64_t*, CpMem> keys(
133 Kokkos::view_alloc(space, "peclet::dem::cp::keys", Kokkos::WithoutInitializing), n);
134 Kokkos::View<int*, CpMem> perm(
135 Kokkos::view_alloc(space, "peclet::dem::cp::perm", Kokkos::WithoutInitializing), n);
136 Kokkos::parallel_for(
137 "peclet::dem::cp::key", Kokkos::RangePolicy<CpExec>(space, 0, n), KOKKOS_LAMBDA(int i) {
138 keys(i) = pairKey(contacts(i));
139 perm(i) = i;
140 });
141
142 // 2. Sort the permutation by key (groups equal pairs contiguously).
143 Kokkos::Experimental::sort_by_key(space, keys, perm);
144
145 // 3. Segment id per sorted position: inclusive scan of "key changed" minus 1.
146 Kokkos::View<int*, CpMem> segId(
147 Kokkos::view_alloc(space, "peclet::dem::cp::segId", Kokkos::WithoutInitializing), n);
148 int numSeg = 0;
149 Kokkos::parallel_scan(
150 "peclet::dem::cp::segscan", Kokkos::RangePolicy<CpExec>(space, 0, n),
151 KOKKOS_LAMBDA(int p, int& run, const bool final) {
152 const bool isNew = (p == 0) || (keys(p) != keys(p - 1));
153 if (isNew)
154 ++run;
155 if (final)
156 segId(p) = run - 1; // 0-based segment index
157 },
158 numSeg);
159
160 // 4. Initialise one manifold per segment (canonical ids from the key, sums zero).
161 Kokkos::View<std::uint64_t*, CpMem> k = keys;
162 Kokkos::View<int*, CpMem> sid = segId;
163 Kokkos::View<ManifoldC*, CpMem> out = outManifolds;
164 Kokkos::parallel_for(
165 "peclet::dem::cp::init", Kokkos::RangePolicy<CpExec>(space, 0, n), KOKKOS_LAMBDA(int p) {
166 const bool leader = (p == 0) || (k(p) != k(p - 1));
167 if (leader) {
168 ManifoldC m{};
169 decodeKey(k(p), m.bodyA, m.bodyB);
170 out(sid(p)) = m;
171 }
172 });
173
174 // 5. Accumulate each contact's transformed manifold into its segment (atomic; order-independent).
175 Kokkos::View<const ContactC*, CpMem> ct = contacts;
176 Kokkos::View<int*, CpMem> pm = perm;
177 Kokkos::parallel_for(
178 "peclet::dem::cp::accum", Kokkos::RangePolicy<CpExec>(space, 0, n), KOKKOS_LAMBDA(int p) {
179 const ManifoldC m = transformContact(ct(pm(p)));
180 const int s = sid(p);
181 Kokkos::atomic_add(&out(s).num_points, m.num_points);
182 Kokkos::atomic_add(&out(s).normal_sum.x, m.normal_sum.x);
183 Kokkos::atomic_add(&out(s).normal_sum.y, m.normal_sum.y);
184 Kokkos::atomic_add(&out(s).normal_sum.z, m.normal_sum.z);
185 Kokkos::atomic_add(&out(s).torque_armA_sum.x, m.torque_armA_sum.x);
186 Kokkos::atomic_add(&out(s).torque_armA_sum.y, m.torque_armA_sum.y);
187 Kokkos::atomic_add(&out(s).torque_armA_sum.z, m.torque_armA_sum.z);
188 Kokkos::atomic_add(&out(s).torque_armB_sum.x, m.torque_armB_sum.x);
189 Kokkos::atomic_add(&out(s).torque_armB_sum.y, m.torque_armB_sum.y);
190 Kokkos::atomic_add(&out(s).torque_armB_sum.z, m.torque_armB_sum.z);
191 Kokkos::atomic_add(&out(s).rA_sum.x, m.rA_sum.x);
192 Kokkos::atomic_add(&out(s).rA_sum.y, m.rA_sum.y);
193 Kokkos::atomic_add(&out(s).rA_sum.z, m.rA_sum.z);
194 Kokkos::atomic_add(&out(s).rB_sum.x, m.rB_sum.x);
195 Kokkos::atomic_add(&out(s).rB_sum.y, m.rB_sum.y);
196 Kokkos::atomic_add(&out(s).rB_sum.z, m.rB_sum.z);
197 Kokkos::atomic_add(&out(s).wallVel_sum.x, m.wallVel_sum.x);
198 Kokkos::atomic_add(&out(s).wallVel_sum.y, m.wallVel_sum.y);
199 Kokkos::atomic_add(&out(s).wallVel_sum.z, m.wallVel_sum.z);
200 Kokkos::atomic_add(&out(s).restitution_sum, m.restitution_sum);
201 });
202 space.fence();
203
204 Kokkos::deep_copy(space, outCount, numSeg);
205 return numSeg;
206}
207
208} // namespace peclet::dem
209
210#endif // DEM_CONTACT_PREPROCESSING_HPP
dem — portable POD types + math + analytic SDFs shared by the Kokkos kernel ports.
int reduceContactsToManifoldsKokkos(Kokkos::View< const ContactC *, CpMem > contacts, int n, Kokkos::View< ManifoldC *, CpMem > outManifolds, Kokkos::View< int, CpMem > outCount)
Reduce n contacts to manifolds (one per unique canonical pair).
std::uint64_t pairKey(const ContactC &c)
Canonical pair key: (min<<32)|max, or (idA<<32)|0xFFFFFFFF for a boundary (idB<0) contact.
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).
CpExec::memory_space CpMem
F4 cross3(F4 a, F4 b)
Kokkos::DefaultExecutionSpace CpExec
Portable mirror of ParticleSystem.cuh ContactConstraint (the fields this reduction touches).
Portable mirror of ManifoldConstraint.