peclet-dem 1.1.0
Performance-portable XPBD Discrete Element Method (Kokkos + ArborX)
Loading...
Searching...
No Matches
solver_friction.hpp
Go to the documentation of this file.
1
15#ifndef DEM_SOLVER_FRICTION_HPP
16#define DEM_SOLVER_FRICTION_HPP
17
18#include <Kokkos_Core.hpp>
19
20#include "dem_portable.hpp"
21#include "solver_position.hpp" // ContactC, detail::computeW, CpExec/CpMem
22#include "solver_velocity.hpp" // detail::genInvMass (the world-frame inverse inertia)
23
24namespace peclet::dem {
25
26using FrManifoldCounts = Kokkos::View<float* [2], CpMem>; // per body: .x = plane load, .y = count
27
33KOKKOS_INLINE_FUNCTION void contactArmsMid(const ContactC& c, F3& rA, F3& rB) {
34 const F4 shift{c.normal.x * c.dist * 0.5f, c.normal.y * c.dist * 0.5f, c.normal.z * c.dist * 0.5f,
35 0.0f};
36 rA = F3{c.rA.x - shift.x, c.rA.y - shift.y, c.rA.z - shift.z};
37 rB = F3{c.rB.x + shift.x, c.rB.y + shift.y, c.rB.z + shift.z};
38}
39
45inline void accumulateNormalImpulseKokkos(Kokkos::View<ContactC*, CpMem> contacts, int numContacts,
46 Kokkos::View<const float*, CpMem> invMass,
47 Kokkos::View<const float* [3], CpMem> invInertia,
48 Kokkos::View<const float* [3], CpMem> velPred,
49 Kokkos::View<const float* [3], CpMem> angVelPred,
50 Kokkos::View<const int*, CpMem> realIdx, float growthRate,
51 bool dedupTwins = true) {
52 using detail::computeW;
53 CpExec space;
54 Kokkos::parallel_for(
55 "peclet::dem::fric_accum", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
56 KOKKOS_LAMBDA(int idx) {
57 ContactC c = contacts(idx);
58 if (c.bodyB < 0) {
59 // Wall (boundary): accumulate the transmitted normal load on the owned grain (immovable
60 // wall -> A term only), relative to the wall's surface velocity. Indexed like
61 // computePlaneLoad (c.bodyA directly reads the ghost-augmented velPred slot).
62 const int idA = c.bodyA;
63 const float invMA = invMass(idA);
64 if (invMA <= 0.0f)
65 return;
66 const F3 invIA = ldF3(invInertia, idA);
67 const F3 rA{c.rA.x, c.rA.y, c.rA.z}, n{c.normal.x, c.normal.y, c.normal.z};
68 const F3 vAc = add3(ldF3(velPred, idA), cross3v(ldF3(angVelPred, idA), rA));
69 const F3 vWall{c.boundaryVel.x, c.boundaryVel.y, c.boundaryVel.z};
70 const float approach = -dot3(sub3(vAc, vWall), n);
71 if (approach <= 0.0f)
72 return;
73 const float w_n = computeW(rA, n, invMA, invIA);
74 if (w_n > 1e-6f)
75 contacts(idx).friction_lambda_n += approach / w_n;
76 return;
77 }
78 const int realA = realIdx(c.bodyA), realB = realIdx(c.bodyB);
79 if (dedupTwins && realA > realB)
80 return; // periodic dedup (single rank; §4.2 item 2)
81 const float invMA = invMass(realA), invMB = invMass(realB);
82 const F3 invIA = ldF3(invInertia, realA), invIB = ldF3(invInertia, realB);
83 F3 rA, rB;
84 contactArmsMid(c, rA, rB); // single application point
85 const F3 n{c.normal.x, c.normal.y, c.normal.z};
86 const F3 vAc = add3(ldF3(velPred, realA), cross3v(ldF3(angVelPred, realA), rA));
87 const F3 vBc = add3(ldF3(velPred, realB), cross3v(ldF3(angVelPred, realB), rB));
88 const F3 vrel = add3(sub3(vAc, vBc), scale3(sub3(rA, rB), growthRate));
89 const float approach = -dot3(vrel, n);
90 if (approach <= 0.0f)
91 return;
92 const float w_n = computeW(rA, n, invMA, invIA) + computeW(rB, n, invMB, invIB);
93 if (w_n > 1e-6f)
94 contacts(idx).friction_lambda_n += approach / w_n;
95 });
96 space.fence();
97}
98
101inline void computePlaneLoadKokkos(Kokkos::View<ContactC*, CpMem> contacts, int numContacts,
102 Kokkos::View<const float*, CpMem> invMass,
103 Kokkos::View<const float* [3], CpMem> invInertia,
104 Kokkos::View<const float* [3], CpMem> velPred,
105 Kokkos::View<const float* [3], CpMem> angVelPred,
106 FrManifoldCounts planeFriction) {
107 using detail::computeW;
108 CpExec space;
109 Kokkos::deep_copy(space, planeFriction, 0.0f);
110 Kokkos::parallel_for(
111 "peclet::dem::fric_plane_load", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
112 KOKKOS_LAMBDA(int idx) {
113 ContactC c = contacts(idx);
114 if (c.bodyB >= 0)
115 return;
116 const int idA = c.bodyA;
117 const float invMA = invMass(idA);
118 if (invMA <= 0.0f)
119 return;
120 const F3 invIA = ldF3(invInertia, idA);
121 const F3 rA{c.rA.x, c.rA.y, c.rA.z}, n{c.normal.x, c.normal.y, c.normal.z};
122 const F3 vAc = add3(ldF3(velPred, idA), cross3v(ldF3(angVelPred, idA), rA));
123 // Relative to the wall's surface velocity (0 for a static plane), so a wall pressing into a
124 // grain along its normal (a vibrating tray) registers the normal load that bounds friction.
125 const F3 vWall{c.boundaryVel.x, c.boundaryVel.y, c.boundaryVel.z};
126 const float approach = -dot3(sub3(vAc, vWall), n);
127 if (approach <= 0.0f)
128 return;
129 const float w_n = computeW(rA, n, invMA, invIA);
130 contacts(idx).friction_lambda_n = (w_n > 1e-6f) ? approach / w_n : 0.0f;
131 Kokkos::atomic_max(&planeFriction(idA, 0), approach / invMA);
132 });
133 space.fence();
134}
135
137inline void countFrictionContactsKokkos(Kokkos::View<const ContactC*, CpMem> contacts,
138 int numContacts, Kokkos::View<const int*, CpMem> realIdx,
139 FrManifoldCounts planeFriction) {
140 CpExec space;
141 Kokkos::parallel_for(
142 "peclet::dem::fric_count", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
143 KOKKOS_LAMBDA(int idx) {
144 const ContactC c = contacts(idx);
145 if (c.friction_lambda_n <= 0.0f)
146 return;
147 Kokkos::atomic_add(&planeFriction(realIdx(c.bodyA), 1), 1.0f);
148 if (c.bodyB >= 0)
149 Kokkos::atomic_add(&planeFriction(realIdx(c.bodyB), 1), 1.0f);
150 });
151 space.fence();
152}
153
166 Kokkos::View<const ContactC*, CpMem> contacts, int numContacts,
167 Kokkos::View<const float*, CpMem> invMass, Kokkos::View<const float* [3], CpMem> invInertia,
168 Kokkos::View<const float* [4], CpMem> quat, Kokkos::View<const float* [3], CpMem> velPred,
169 Kokkos::View<const float* [3], CpMem> angVelPred, Kokkos::View<const int*, CpMem> realIdx,
170 FrManifoldCounts planeFriction, float frictionDynamic, Kokkos::View<float* [3], CpMem> deltaVel,
171 Kokkos::View<float* [3], CpMem> deltaAngVel) {
172 CpExec space;
173 Kokkos::deep_copy(space, deltaVel, 0.0f);
174 Kokkos::deep_copy(space, deltaAngVel, 0.0f);
175 Kokkos::parallel_for(
176 "peclet::dem::fric_solve", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
177 KOKKOS_LAMBDA(int idx) {
178 const ContactC c = contacts(idx);
179 const float lambda_n = c.friction_lambda_n;
180 if (lambda_n <= 0.0f)
181 return;
182 const int idA = c.bodyA, idB = c.bodyB;
183 const int realA = realIdx(idA);
184 const int realB = (idB >= 0) ? realIdx(idB) : -1;
185 const float invMA = invMass(realA);
186 const float invMB = (idB >= 0) ? invMass(realB) : 0.0f;
187 const F3 invIA = ldF3(invInertia, realA);
188 const F3 invIB = (idB >= 0) ? ldF3(invInertia, realB) : F3{0, 0, 0};
189 F3 rA{c.rA.x, c.rA.y, c.rA.z}, rB{c.rB.x, c.rB.y, c.rB.z};
190 if (idB >= 0)
191 contactArmsMid(c, rA, rB); // single application point; a wall keeps its arm
192 const F3 n{c.normal.x, c.normal.y, c.normal.z};
193
194 const F3 vAc = add3(ldF3(velPred, realA), cross3v(ldF3(angVelPred, realA), rA));
195 F3 vBc{0, 0, 0};
196 if (idB >= 0)
197 vBc = add3(ldF3(velPred, realB), cross3v(ldF3(angVelPred, realB), rB));
198 else
199 vBc =
200 F3{c.boundaryVel.x, c.boundaryVel.y, c.boundaryVel.z}; // moving wall drags the grain
201 const F3 vrel = sub3(vAc, vBc);
202 const float vn = dot3(vrel, n);
203 const F3 vt = sub3(vrel, scale3(n, vn));
204 const float vt_len = Kokkos::sqrt(dot3(vt, vt));
205 if (vt_len < 1e-7f)
206 return;
207 const F3 t = scale3(vt, 1.0f / vt_len);
208
209 const F3 rnA = cross3v(rA, t), rnB = cross3v(rB, t);
210 // Isotropic inverse inertia at an end (spheres; a wall end is 0): no principal frame.
211 const bool isoA = invIA.x == invIA.y && invIA.y == invIA.z;
212 const bool isoB = invIB.x == invIB.y && invIB.y == invIB.z;
213 F4 qA{0, 0, 0, 1}, qB{0, 0, 0, 1};
214 if (!isoA)
215 qA = F4{quat(realA, 0), quat(realA, 1), quat(realA, 2), quat(realA, 3)};
216 if (!isoB)
217 qB = F4{quat(realB, 0), quat(realB, 1), quat(realB, 2), quat(realB, 3)};
218 const float w_t = (isoA && isoB)
219 ? invMA + invMB + rnA.x * rnA.x * invIA.x + rnA.y * rnA.y * invIA.y +
220 rnA.z * rnA.z * invIA.z + rnB.x * rnB.x * invIB.x +
221 rnB.y * rnB.y * invIB.y + rnB.z * rnB.z * invIB.z
222 : invMA + invMB + detail::genInvMass(rnA, invIA, qA) +
223 detail::genInvMass(rnB, invIB, qB);
224 if (w_t < 1e-6f)
225 return;
226
227 // friction_lambda_n now carries the accumulated force-chain load for BOTH body-body and
228 // wall contacts (see accumulateNormalImpulseKokkos), so it is the Coulomb bound in both
229 // cases.
230 const float bound = lambda_n;
231 const float nA = planeFriction(realA, 1);
232 const float nB = (idB >= 0) ? planeFriction(realB, 1) : 0.0f;
233 const float inv_n = 1.0f / Kokkos::fmax(Kokkos::fmax(nA, nB), 1.0f);
234 float lt = -vt_len / w_t;
235 // Per-wall AND per-pair friction (a < 0 sentinel keeps the global material — planes and
236 // body-body contacts without a pair table).
237 const float mu = (c.boundaryFriction >= 0.0f) ? c.boundaryFriction : frictionDynamic;
238 const float maxf = mu * bound;
239 if (lt < -maxf)
240 lt = -maxf;
241 lt *= inv_n;
242
243 Kokkos::atomic_add(&deltaVel(realA, 0), t.x * lt * invMA);
244 Kokkos::atomic_add(&deltaVel(realA, 1), t.y * lt * invMA);
245 Kokkos::atomic_add(&deltaVel(realA, 2), t.z * lt * invMA);
246 if (isoA) {
247 Kokkos::atomic_add(&deltaAngVel(realA, 0), rnA.x * invIA.x * lt);
248 Kokkos::atomic_add(&deltaAngVel(realA, 1), rnA.y * invIA.y * lt);
249 Kokkos::atomic_add(&deltaAngVel(realA, 2), rnA.z * invIA.z * lt);
250 } else { // I_world^-1 (r x t) lt = R diag(invI) R^T (r x t) lt
251 const F3 Jl = invRotateVector(qA, scale3(rnA, lt));
252 const F3 dww = rotateVector(qA, F3{Jl.x * invIA.x, Jl.y * invIA.y, Jl.z * invIA.z});
253 Kokkos::atomic_add(&deltaAngVel(realA, 0), dww.x);
254 Kokkos::atomic_add(&deltaAngVel(realA, 1), dww.y);
255 Kokkos::atomic_add(&deltaAngVel(realA, 2), dww.z);
256 }
257 if (idB >= 0) {
258 Kokkos::atomic_add(&deltaVel(realB, 0), -t.x * lt * invMB);
259 Kokkos::atomic_add(&deltaVel(realB, 1), -t.y * lt * invMB);
260 Kokkos::atomic_add(&deltaVel(realB, 2), -t.z * lt * invMB);
261 if (isoB) {
262 Kokkos::atomic_add(&deltaAngVel(realB, 0), -rnB.x * invIB.x * lt);
263 Kokkos::atomic_add(&deltaAngVel(realB, 1), -rnB.y * invIB.y * lt);
264 Kokkos::atomic_add(&deltaAngVel(realB, 2), -rnB.z * invIB.z * lt);
265 } else {
266 const F3 Jl = invRotateVector(qB, scale3(rnB, -lt));
267 const F3 dww = rotateVector(qB, F3{Jl.x * invIB.x, Jl.y * invIB.y, Jl.z * invIB.z});
268 Kokkos::atomic_add(&deltaAngVel(realB, 0), dww.x);
269 Kokkos::atomic_add(&deltaAngVel(realB, 1), dww.y);
270 Kokkos::atomic_add(&deltaAngVel(realB, 2), dww.z);
271 }
272 }
273 });
274 space.fence();
275}
276
277} // namespace peclet::dem
278
279#endif // DEM_SOLVER_FRICTION_HPP
dem — portable POD types + math + analytic SDFs shared by the Kokkos kernel ports.
float genInvMass(F3 tau, F3 invIlocal, F4 q)
float computeW(F3 r, F3 dir, float invM, F3 invI)
void computePlaneLoadKokkos(Kokkos::View< ContactC *, CpMem > contacts, int numContacts, Kokkos::View< const float *, CpMem > invMass, Kokkos::View< const float *[3], CpMem > invInertia, Kokkos::View< const float *[3], CpMem > velPred, Kokkos::View< const float *[3], CpMem > angVelPred, FrManifoldCounts planeFriction)
Plane (idB<0) one-shot loads.
F3 cross3v(F3 a, F3 b)
Kokkos::View< float *[2], CpMem > FrManifoldCounts
void contactArmsMid(const ContactC &c, F3 &rA, F3 &rB)
The single application point of a body-body contact: both lever arms moved to the contact midpoint,...
F3 invRotateVector(F4 q, F3 v)
F3 ldF3(const V &v, int i)
F3 rotateVector(F4 q, F3 v)
float dot3(F3 a, F3 b)
F3 sub3(F3 a, F3 b)
CpExec::memory_space CpMem
F3 scale3(F3 a, float s)
F3 add3(F3 a, F3 b)
void solveContactFrictionKokkos(Kokkos::View< const ContactC *, CpMem > contacts, int numContacts, Kokkos::View< const float *, CpMem > invMass, Kokkos::View< const float *[3], CpMem > invInertia, Kokkos::View< const float *[4], CpMem > quat, Kokkos::View< const float *[3], CpMem > velPred, Kokkos::View< const float *[3], CpMem > angVelPred, Kokkos::View< const int *, CpMem > realIdx, FrManifoldCounts planeFriction, float frictionDynamic, Kokkos::View< float *[3], CpMem > deltaVel, Kokkos::View< float *[3], CpMem > deltaAngVel)
One count-averaged Coulomb friction sweep.
void accumulateNormalImpulseKokkos(Kokkos::View< ContactC *, CpMem > contacts, int numContacts, Kokkos::View< const float *, CpMem > invMass, Kokkos::View< const float *[3], CpMem > invInertia, Kokkos::View< const float *[3], CpMem > velPred, Kokkos::View< const float *[3], CpMem > angVelPred, Kokkos::View< const int *, CpMem > realIdx, float growthRate, bool dedupTwins=true)
Force-chain normal load, accumulated over the velocity iterations: contacts(idx).friction_lambda_n +=...
void countFrictionContactsKokkos(Kokkos::View< const ContactC *, CpMem > contacts, int numContacts, Kokkos::View< const int *, CpMem > realIdx, FrManifoldCounts planeFriction)
Per-body active-contact count into planeFriction(:,1).
Kokkos::DefaultExecutionSpace CpExec
dem — portable (Kokkos) XPBD position solve (pure overlap removal).
dem — portable (Kokkos) manifold velocity solve (normal restitution impulse).
Portable mirror of ParticleSystem.cuh ContactConstraint (the fields this reduction touches).