peclet-dem
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
23namespace peclet::dem {
24
25using FrManifoldCounts = Kokkos::View<float* [2], CpMem>; // per body: .x = plane load, .y = count
26
32inline void accumulateNormalImpulseKokkos(Kokkos::View<ContactC*, CpMem> contacts, int numContacts,
33 Kokkos::View<const float*, CpMem> invMass,
34 Kokkos::View<const float* [3], CpMem> invInertia,
35 Kokkos::View<const float* [3], CpMem> velPred,
36 Kokkos::View<const float* [3], CpMem> angVelPred,
37 Kokkos::View<const int*, CpMem> realIdx,
38 float growthRate) {
39 using detail::computeW;
40 CpExec space;
41 Kokkos::parallel_for(
42 "peclet::dem::fric_accum", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
43 KOKKOS_LAMBDA(int idx) {
44 ContactC c = contacts(idx);
45 if (c.bodyB < 0) {
46 // Wall (boundary): accumulate the transmitted normal load on the owned grain (immovable
47 // wall -> A term only), relative to the wall's surface velocity. Indexed like
48 // computePlaneLoad (c.bodyA directly reads the ghost-augmented velPred slot).
49 const int idA = c.bodyA;
50 const float invMA = invMass(idA);
51 if (invMA <= 0.0f)
52 return;
53 const F3 invIA = ldF3(invInertia, idA);
54 const F3 rA{c.rA.x, c.rA.y, c.rA.z}, n{c.normal.x, c.normal.y, c.normal.z};
55 const F3 vAc = add3(ldF3(velPred, idA), cross3v(ldF3(angVelPred, idA), rA));
56 const F3 vWall{c.boundaryVel.x, c.boundaryVel.y, c.boundaryVel.z};
57 const float approach = -dot3(sub3(vAc, vWall), n);
58 if (approach <= 0.0f)
59 return;
60 const float w_n = computeW(rA, n, invMA, invIA);
61 if (w_n > 1e-6f)
62 contacts(idx).friction_lambda_n += approach / w_n;
63 return;
64 }
65 const int realA = realIdx(c.bodyA), realB = realIdx(c.bodyB);
66 if (realA > realB)
67 return;
68 const float invMA = invMass(realA), invMB = invMass(realB);
69 const F3 invIA = ldF3(invInertia, realA), invIB = ldF3(invInertia, realB);
70 const F3 rA{c.rA.x, c.rA.y, c.rA.z}, rB{c.rB.x, c.rB.y, c.rB.z};
71 const F3 n{c.normal.x, c.normal.y, c.normal.z};
72 const F3 vAc = add3(ldF3(velPred, realA), cross3v(ldF3(angVelPred, realA), rA));
73 const F3 vBc = add3(ldF3(velPred, realB), cross3v(ldF3(angVelPred, realB), rB));
74 const F3 vrel = add3(sub3(vAc, vBc), scale3(sub3(rA, rB), growthRate));
75 const float approach = -dot3(vrel, n);
76 if (approach <= 0.0f)
77 return;
78 const float w_n = computeW(rA, n, invMA, invIA) + computeW(rB, n, invMB, invIB);
79 if (w_n > 1e-6f)
80 contacts(idx).friction_lambda_n += approach / w_n;
81 });
82 space.fence();
83}
84
87inline void computePlaneLoadKokkos(Kokkos::View<ContactC*, CpMem> contacts, int numContacts,
88 Kokkos::View<const float*, CpMem> invMass,
89 Kokkos::View<const float* [3], CpMem> invInertia,
90 Kokkos::View<const float* [3], CpMem> velPred,
91 Kokkos::View<const float* [3], CpMem> angVelPred,
92 FrManifoldCounts planeFriction) {
93 using detail::computeW;
94 CpExec space;
95 Kokkos::deep_copy(space, planeFriction, 0.0f);
96 Kokkos::parallel_for(
97 "peclet::dem::fric_plane_load", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
98 KOKKOS_LAMBDA(int idx) {
99 ContactC c = contacts(idx);
100 if (c.bodyB >= 0)
101 return;
102 const int idA = c.bodyA;
103 const float invMA = invMass(idA);
104 if (invMA <= 0.0f)
105 return;
106 const F3 invIA = ldF3(invInertia, idA);
107 const F3 rA{c.rA.x, c.rA.y, c.rA.z}, n{c.normal.x, c.normal.y, c.normal.z};
108 const F3 vAc = add3(ldF3(velPred, idA), cross3v(ldF3(angVelPred, idA), rA));
109 // Relative to the wall's surface velocity (0 for a static plane), so a wall pressing into a
110 // grain along its normal (a vibrating tray) registers the normal load that bounds friction.
111 const F3 vWall{c.boundaryVel.x, c.boundaryVel.y, c.boundaryVel.z};
112 const float approach = -dot3(sub3(vAc, vWall), n);
113 if (approach <= 0.0f)
114 return;
115 const float w_n = computeW(rA, n, invMA, invIA);
116 contacts(idx).friction_lambda_n = (w_n > 1e-6f) ? approach / w_n : 0.0f;
117 Kokkos::atomic_max(&planeFriction(idA, 0), approach / invMA);
118 });
119 space.fence();
120}
121
123inline void countFrictionContactsKokkos(Kokkos::View<const ContactC*, CpMem> contacts,
124 int numContacts, Kokkos::View<const int*, CpMem> realIdx,
125 FrManifoldCounts planeFriction) {
126 CpExec space;
127 Kokkos::parallel_for(
128 "peclet::dem::fric_count", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
129 KOKKOS_LAMBDA(int idx) {
130 const ContactC c = contacts(idx);
131 if (c.friction_lambda_n <= 0.0f)
132 return;
133 Kokkos::atomic_add(&planeFriction(realIdx(c.bodyA), 1), 1.0f);
134 if (c.bodyB >= 0)
135 Kokkos::atomic_add(&planeFriction(realIdx(c.bodyB), 1), 1.0f);
136 });
137 space.fence();
138}
139
143 Kokkos::View<const ContactC*, CpMem> contacts, int numContacts,
144 Kokkos::View<const float*, CpMem> invMass, Kokkos::View<const float* [3], CpMem> invInertia,
145 Kokkos::View<const float* [3], CpMem> velPred, Kokkos::View<const float* [3], CpMem> angVelPred,
146 Kokkos::View<const int*, CpMem> realIdx, FrManifoldCounts planeFriction, float frictionDynamic,
147 Kokkos::View<float* [3], CpMem> deltaVel, Kokkos::View<float* [3], CpMem> deltaAngVel) {
148 CpExec space;
149 Kokkos::deep_copy(space, deltaVel, 0.0f);
150 Kokkos::deep_copy(space, deltaAngVel, 0.0f);
151 Kokkos::parallel_for(
152 "peclet::dem::fric_solve", Kokkos::RangePolicy<CpExec>(space, 0, numContacts),
153 KOKKOS_LAMBDA(int idx) {
154 const ContactC c = contacts(idx);
155 const float lambda_n = c.friction_lambda_n;
156 if (lambda_n <= 0.0f)
157 return;
158 const int idA = c.bodyA, idB = c.bodyB;
159 const int realA = realIdx(idA);
160 const int realB = (idB >= 0) ? realIdx(idB) : -1;
161 const float invMA = invMass(realA);
162 const float invMB = (idB >= 0) ? invMass(realB) : 0.0f;
163 const F3 invIA = ldF3(invInertia, realA);
164 const F3 invIB = (idB >= 0) ? ldF3(invInertia, realB) : F3{0, 0, 0};
165 const F3 rA{c.rA.x, c.rA.y, c.rA.z}, rB{c.rB.x, c.rB.y, c.rB.z};
166 const F3 n{c.normal.x, c.normal.y, c.normal.z};
167
168 const F3 vAc = add3(ldF3(velPred, realA), cross3v(ldF3(angVelPred, realA), rA));
169 F3 vBc{0, 0, 0};
170 if (idB >= 0)
171 vBc = add3(ldF3(velPred, realB), cross3v(ldF3(angVelPred, realB), rB));
172 else
173 vBc = F3{c.boundaryVel.x, c.boundaryVel.y, c.boundaryVel.z}; // moving wall drags the grain
174 const F3 vrel = sub3(vAc, vBc);
175 const float vn = dot3(vrel, n);
176 const F3 vt = sub3(vrel, scale3(n, vn));
177 const float vt_len = Kokkos::sqrt(dot3(vt, vt));
178 if (vt_len < 1e-7f)
179 return;
180 const F3 t = scale3(vt, 1.0f / vt_len);
181
182 const F3 rnA = cross3v(rA, t), rnB = cross3v(rB, t);
183 const float w_t = invMA + invMB + rnA.x * rnA.x * invIA.x + rnA.y * rnA.y * invIA.y +
184 rnA.z * rnA.z * invIA.z + rnB.x * rnB.x * invIB.x +
185 rnB.y * rnB.y * invIB.y + rnB.z * rnB.z * invIB.z;
186 if (w_t < 1e-6f)
187 return;
188
189 // friction_lambda_n now carries the accumulated force-chain load for BOTH body-body and wall
190 // contacts (see accumulateNormalImpulseKokkos), so it is the Coulomb bound in both cases.
191 const float bound = lambda_n;
192 const float nA = planeFriction(realA, 1);
193 const float nB = (idB >= 0) ? planeFriction(realB, 1) : 0.0f;
194 const float inv_n = 1.0f / Kokkos::fmax(Kokkos::fmax(nA, nB), 1.0f);
195 float lt = -vt_len / w_t;
196 // Per-wall friction for a boundary contact (a < 0 sentinel keeps the global material — planes
197 // and body-body); grain-grain contacts always use the global coefficient.
198 const float mu = (idB < 0 && c.boundaryFriction >= 0.0f) ? c.boundaryFriction : frictionDynamic;
199 const float maxf = mu * bound;
200 if (lt < -maxf)
201 lt = -maxf;
202 lt *= inv_n;
203
204 Kokkos::atomic_add(&deltaVel(realA, 0), t.x * lt * invMA);
205 Kokkos::atomic_add(&deltaVel(realA, 1), t.y * lt * invMA);
206 Kokkos::atomic_add(&deltaVel(realA, 2), t.z * lt * invMA);
207 Kokkos::atomic_add(&deltaAngVel(realA, 0), rnA.x * invIA.x * lt);
208 Kokkos::atomic_add(&deltaAngVel(realA, 1), rnA.y * invIA.y * lt);
209 Kokkos::atomic_add(&deltaAngVel(realA, 2), rnA.z * invIA.z * lt);
210 if (idB >= 0) {
211 Kokkos::atomic_add(&deltaVel(realB, 0), -t.x * lt * invMB);
212 Kokkos::atomic_add(&deltaVel(realB, 1), -t.y * lt * invMB);
213 Kokkos::atomic_add(&deltaVel(realB, 2), -t.z * lt * invMB);
214 Kokkos::atomic_add(&deltaAngVel(realB, 0), -rnB.x * invIB.x * lt);
215 Kokkos::atomic_add(&deltaAngVel(realB, 1), -rnB.y * invIB.y * lt);
216 Kokkos::atomic_add(&deltaAngVel(realB, 2), -rnB.z * invIB.z * lt);
217 }
218 });
219 space.fence();
220}
221
222} // namespace peclet::dem
223
224#endif // DEM_SOLVER_FRICTION_HPP
dem — portable POD types + math + analytic SDFs shared by the Kokkos kernel ports.
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.
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)
Force-chain normal load, accumulated over the velocity iterations: contacts(idx).friction_lambda_n +=...
F3 cross3v(F3 a, F3 b)
Kokkos::View< float *[2], CpMem > FrManifoldCounts
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 *[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.
F3 ldF3(const V &v, int i)
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 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).
Portable mirror of ParticleSystem.cuh ContactConstraint (the fields this reduction touches).