18#include <Kokkos_Core.hpp>
33#include "peclet/core/common/view.hpp"
34#include "peclet/core/geom/scene_builder.hpp"
66 r.erase(std::remove(r.begin(), r.end(),
this), r.end());
86 static std::vector<Simulation*> r;
115 int addAnalyticWall(
const std::vector<int>& nodeInts,
const std::vector<float>& nodeReals,
116 int root,
bool invert,
float restitution,
float friction) {
117 namespace g = peclet::core::geom;
118 if (nodeInts.size() % g::kNodeIntStride || nodeReals.size() % g::kNodeRealStride)
119 throw std::runtime_error(
"add_analytic_wall: node arrays are not a whole number of records");
120 const int n =
static_cast<int>(nodeInts.size() / g::kNodeIntStride);
121 if (n == 0 ||
static_cast<int>(nodeReals.size() / g::kNodeRealStride) != n)
122 throw std::runtime_error(
"add_analytic_wall: node int/real counts disagree");
123 if (root < 0 || root >= n)
124 throw std::runtime_error(
"add_analytic_wall: root out of range");
125 const int base =
static_cast<int>(wallNodesHost_.size());
126 for (
int i = 0; i < n; ++i) {
127 g::ShapeNode<float> nd = g::decodeNode<float>(&nodeInts[i * g::kNodeIntStride],
128 &nodeReals[i * g::kNodeRealStride]);
129 if (nd.kind == g::kGrid)
130 throw std::runtime_error(
"add_analytic_wall: grid leaves are not supported here");
131 if (nd.kind >= g::kCsgBase) {
135 wallNodesHost_.push_back(nd);
137 P_.
wallNodes = Kokkos::View<g::ShapeNode<float>*,
CpMem>(
"wallNodes", wallNodesHost_.size());
138 auto hn = Kokkos::create_mirror_view(
P_.
wallNodes);
139 for (std::size_t i = 0; i < wallNodesHost_.size(); ++i)
140 hn(i) = wallNodesHost_[i];
145 w.nodeCount =
static_cast<int>(wallNodesHost_.size());
148 wallRootTf_[base + root] = wallNodesHost_[(std::size_t)(base + root)].transform;
149 w.sign = invert ? -1.0f : 1.0f;
150 w.restitution = restitution;
151 w.friction = friction;
152 const int idx =
static_cast<int>(wallsHost_.size());
153 wallsHost_.push_back(w);
155 for (
auto& wh : wallsHost_)
156 if (wh.shapeRoot >= 0)
158 P_.
numWalls =
static_cast<int>(wallsHost_.size());
164 void setDomain(
float lx,
float ly,
float lz,
bool px,
bool py,
bool pz) {
165 P_.
domain =
Domain{
F3{0, 0, 0},
F3{lx, ly, lz},
F3{lx, ly, lz}, px, py, pz};
172 F3{mx.
x - mn.
x, mx.y - mn.
y, mx.z - mn.
z},
202 std::tuple<float, float, float>
gravity()
const {
227 else if (mode ==
"onesided")
229 else if (mode ==
"multilevel")
231 else if (mode ==
"escalate")
233 else if (mode ==
"ordered")
236 throw std::invalid_argument(
237 "set_stabilization: expected 'off', 'onesided', 'multilevel', 'escalate' or "
241 static const char*
const names[] = {
"off",
"onesided",
"multilevel",
"escalate",
"ordered"};
252 if (model ==
"newton")
254 else if (model ==
"poisson")
257 throw std::invalid_argument(
"set_restitution_model: expected 'newton' or 'poisson'");
275 void setSleeping(
bool enabled,
float threshold_scale = 2.0f,
int consecutive = 64,
276 float wake_scale = 40.0f,
bool wake_on_lost_contact =
false,
277 float immovable_frac = 0.01f) {
279 if (threshold_scale > 0.0f)
283 if (wake_scale > 0.0f)
286 if (immovable_frac >= 0.0f)
314 else if (mode ==
"off")
316 else if (mode ==
"on")
319 throw std::invalid_argument(
"set_fused_sweeps: expected 'auto', 'on' or 'off'");
337 if (mode ==
"one_way")
339 else if (mode ==
"two_way")
342 throw std::invalid_argument(
"set_shell_detection: expected 'one_way' or 'two_way'");
350 Kokkos::parallel_reduce(
351 "peclet::dem::count_asleep", Kokkos::RangePolicy<CpExec>(0,
P_.
numReal),
352 KOKKOS_LAMBDA(
int i,
int& acc) { acc += a(i) ? 1 : 0; }, n);
358 if (mat < 0 || mat >= 8)
359 throw std::invalid_argument(
"material id out of range");
360 auto he = Kokkos::create_mirror_view(
P_.
hertzE);
361 auto hn = Kokkos::create_mirror_view(
P_.
hertzNu);
372 requireDt(
"step_hertz");
384 throw std::invalid_argument(
"set_dt: dt must be > 0 (relax() is the dynamics-free substep)");
411 for (
int i = 0; i <
P_.
numReal && i < (int)ids.size(); ++i)
412 h(i) =
static_cast<unsigned char>(ids[i]);
419 throw std::invalid_argument(
"material id out of range");
432 for (
auto xy : {std::pair<int, int>{a, b}, std::pair<int, int>{b, a}}) {
434 h((xy.first *
kMaxMaterials + xy.second) * 2 + 1) = friction;
441 throw std::invalid_argument(
"wall index out of range");
442 auto h = Kokkos::create_mirror_view(
P_.
walls);
443 Kokkos::deep_copy(h,
P_.
walls);
444 h(wid).materialId = mat;
445 Kokkos::deep_copy(
P_.
walls, h);
447 void addPlane(
float px,
float py,
float pz,
float nx,
float ny,
float nz) {
448 auto h = Kokkos::create_mirror_view(
P_.
planes);
464 int addSdfWall(
const std::vector<float>& grid,
int nx,
int ny,
int nz,
F3 origin,
F3 spacing,
465 float restitution,
float friction) {
466 if (nx < 2 || ny < 2 || nz < 2)
467 throw std::runtime_error(
"addSdfWall: grid dims must be >= 2 on each axis");
468 if (
static_cast<long>(nx) * ny * nz !=
static_cast<long>(grid.size()))
469 throw std::runtime_error(
"addSdfWall: grid.size() must equal nx*ny*nz");
475 w.grid.offset =
static_cast<int>(wallGridHost_.size());
477 w.grid.invSpacing = peclet::core::Vec3<float>{spacing.
x > 0 ? 1.0f / spacing.
x : 0.0f,
478 spacing.
y > 0 ? 1.0f / spacing.
y : 0.0f,
479 spacing.
z > 0 ? 1.0f / spacing.
z : 0.0f};
481 w.grid.extension = peclet::core::geom::GridExtension::kContainer;
482 w.restitution = restitution;
483 w.friction = friction;
484 const int idx =
static_cast<int>(wallsHost_.size());
485 wallsHost_.push_back(w);
486 wallGridHost_.insert(wallGridHost_.end(), grid.begin(), grid.end());
488 P_.
wallGrid = Kokkos::View<float*, CpMem>(
"wallGrid", wallGridHost_.size());
489 auto hg = Kokkos::create_mirror_view(
P_.
wallGrid);
490 for (
size_t i = 0; i < wallGridHost_.size(); ++i)
491 hg(i) = wallGridHost_[i];
494 P_.
numWalls =
static_cast<int>(wallsHost_.size());
506 if (wallIndex < 0 || wallIndex >=
static_cast<int>(wallsHost_.size()))
507 throw std::runtime_error(
"setWallVelocity: wall index out of range");
508 wallsHost_[wallIndex].linVel = linVel;
509 wallsHost_[wallIndex].angVel = angVel;
510 wallsHost_[wallIndex].center = center;
526 namespace g = peclet::core::geom;
527 if (wallIndex < 0 || wallIndex >=
static_cast<int>(wallsHost_.size()))
528 throw std::runtime_error(
"setWallTransform: wall index out of range");
529 const int root = wallsHost_[(std::size_t)wallIndex].shapeRoot;
531 throw std::runtime_error(
532 "setWallTransform: this wall is a sampled GRID SDF, which has no shape tree to place -- "
533 "only analytic walls (add_analytic_wall) can be moved");
534 const auto it = wallRootTf_.find(root);
535 if (it == wallRootTf_.end())
536 throw std::runtime_error(
"setWallTransform: authored root transform missing (internal)");
537 g::Transform<float> W;
538 W.translation = peclet::core::Vec3<float>{translation.
x, translation.
y, translation.
z};
539 W.rotation = peclet::core::Quat<float>{qx, qy, qz, qw};
541 wallNodesHost_[(std::size_t)root].transform =
542 g::SceneBuilder<float>::composeTransform(W, it->second);
543 auto hn = Kokkos::create_mirror_view(
P_.
wallNodes);
544 for (std::size_t i = 0; i < wallNodesHost_.size(); ++i)
545 hn(i) = wallNodesHost_[i];
552 std::vector<float>
wallSdfAt(
int wallIndex,
const std::vector<float>& pts)
const {
553 if (wallIndex < 0 || wallIndex >=
static_cast<int>(wallsHost_.size()))
554 throw std::runtime_error(
"wall_sdf_at: wall index out of range");
555 const int m =
static_cast<int>(pts.size() / 3);
556 std::vector<float> out((std::size_t)m, 0.0f);
557 const WallSdf w = wallsHost_[(std::size_t)wallIndex];
558 Kokkos::View<float*, CpMem> d(
"wallProbe", std::max(1, m));
559 Kokkos::View<float*, CpMem> q(
"wallProbePts", std::max(1, 3 * m));
560 auto hq = Kokkos::create_mirror_view(q);
561 for (
int i = 0; i < 3 * m; ++i)
562 hq(i) = pts[(std::size_t)i];
563 Kokkos::deep_copy(q, hq);
565 Kokkos::parallel_for(
566 "peclet::dem::wall_sdf_probe", Kokkos::RangePolicy<CpExec>(0, m), KOKKOS_LAMBDA(
int i) {
567 d(i) =
sampleWallSdf(
F3{q(3 * i), q(3 * i + 1), q(3 * i + 2)}, w, grid);
570 auto hd = Kokkos::create_mirror_view(d);
571 Kokkos::deep_copy(hd, d);
572 for (
int i = 0; i < m; ++i)
573 out[(std::size_t)i] = hd(i);
579 const int n =
static_cast<int>(xyz.size() / 3);
581 throw std::invalid_argument(
582 "set_positions: " + std::to_string(n) +
583 " particles exceed Simulation(capacity=" + std::to_string(
P_.
capacity) +
")");
590 mpiGidsGlobal_ =
false;
592 auto pos = Kokkos::create_mirror_view(
P_.
pos);
593 auto q = Kokkos::create_mirror_view(
P_.
quat);
594 auto im = Kokkos::create_mirror_view(
P_.
invMass);
595 auto sc = Kokkos::create_mirror_view(
P_.
scale);
597 auto sid = Kokkos::create_mirror_view(
P_.
shapeId);
598 auto vel = Kokkos::create_mirror_view(
P_.
vel);
599 auto av = Kokkos::create_mirror_view(
P_.
angVel);
600 auto gi = Kokkos::create_mirror_view(
P_.
gid);
601 for (
int i = 0; i < n; ++i) {
603 pos(i, 0) = xyz[3 * i];
604 pos(i, 1) = xyz[3 * i + 1];
605 pos(i, 2) = xyz[3 * i + 2];
616 vel(i, 0) = vel(i, 1) = vel(i, 2) = 0;
617 av(i, 0) = av(i, 1) = av(i, 2) = 0;
619 Kokkos::deep_copy(
P_.
pos, pos);
620 Kokkos::deep_copy(
P_.
quat, q);
622 Kokkos::deep_copy(
P_.
scale, sc);
625 Kokkos::deep_copy(
P_.
vel, vel);
627 Kokkos::deep_copy(
P_.
gid, gi);
631 auto sc = Kokkos::create_mirror_view(
P_.
scale);
634 Kokkos::deep_copy(
P_.
scale, sc);
641 for (
int i = 0; i <
P_.
numReal && i < (int)s.size(); ++i)
647 auto vel = Kokkos::create_mirror_view(
P_.
vel);
648 for (
int i = 0; i <
P_.
numReal && 3 * i + 2 < (int)v.size(); ++i) {
649 vel(i, 0) = v[3 * i];
650 vel(i, 1) = v[3 * i + 1];
651 vel(i, 2) = v[3 * i + 2];
653 Kokkos::deep_copy(
P_.
vel, vel);
658 auto ef = Kokkos::create_mirror_view(
P_.
extForce);
659 for (
int i = 0; i <
P_.
numReal && 3 * i + 2 < (int)f.size(); ++i) {
661 ef(i, 1) = f[3 * i + 1];
662 ef(i, 2) = f[3 * i + 2];
673 auto et = Kokkos::create_mirror_view(
P_.
extTorque);
674 for (
int i = 0; i <
P_.
numReal && 3 * i + 2 < (int)t.size(); ++i) {
676 et(i, 1) = t[3 * i + 1];
677 et(i, 2) = t[3 * i + 2];
692 auto h = Kokkos::create_mirror_view(
P_.
quat);
693 for (
int i = 0; i <
P_.
numReal && 4 * i + 3 < (int)q.size(); ++i) {
695 h(i, 1) = q[4 * i + 1];
696 h(i, 2) = q[4 * i + 2];
697 h(i, 3) = q[4 * i + 3];
699 Kokkos::deep_copy(
P_.
quat, h);
702 auto h = Kokkos::create_mirror_view(
P_.
angVel);
703 for (
int i = 0; i <
P_.
numReal && 3 * i + 2 < (int)w.size(); ++i) {
705 h(i, 1) = w[3 * i + 1];
706 h(i, 2) = w[3 * i + 2];
712 for (
int i = 0; i <
P_.
numReal && 3 * i + 2 < (int)ii.size(); ++i) {
714 h(i, 1) = ii[3 * i + 1];
715 h(i, 2) = ii[3 * i + 2];
720 auto h = Kokkos::create_mirror_view(
P_.
invMass);
721 for (
int i = 0; i <
P_.
numReal && i < (int)im.size(); ++i)
726 return peclet::core::toVector(
730 return peclet::core::toVector(
738 else if (new_factor > 0.0f)
749 auto im = Kokkos::create_mirror_view(
P_.
invMass);
753 out[i] = (im(i) > 0.0f) ? (1.0f / im(i)) : 0.0f;
758 return peclet::core::toVector(
759 Kokkos::subview(
P_.
pos, Kokkos::make_pair(0,
P_.
numReal), Kokkos::ALL));
762 return peclet::core::toVector(
763 Kokkos::subview(
P_.
vel, Kokkos::make_pair(0,
P_.
numReal), Kokkos::ALL));
766 return peclet::core::toVector(
767 Kokkos::subview(
P_.
quat, Kokkos::make_pair(0,
P_.
numReal), Kokkos::ALL));
770 return peclet::core::toVector(Kokkos::subview(
P_.
scale, Kokkos::make_pair(0,
P_.
numReal)));
777 for (
int i = 0; i < n; ++i)
787 for (
int i = 0; i < n; ++i)
799 auto sc = Kokkos::create_mirror_view(
P_.
scale);
800 Kokkos::deep_copy(sc,
P_.
scale);
809 void exportSdf(
const std::string& filename,
int rx,
int ry,
int rz) {
810 const std::vector<float> grid =
getSdfGrid(rx, ry, rz);
819 void initMpi(std::tuple<double, double, double> origin, std::tuple<double, double, double> size,
820 std::tuple<long, long, long> gsize, std::tuple<bool, bool, bool> periodic,
822 halo_->initMpi({std::get<0>(origin), std::get<1>(origin), std::get<2>(origin)},
823 {std::get<0>(size), std::get<1>(size), std::get<2>(size)},
824 {std::get<0>(gsize), std::get<1>(gsize), std::get<2>(gsize)},
825 {std::get<0>(periodic), std::get<1>(periodic), std::get<2>(periodic)}, comm);
836 void enableMpiStep(
double rcut,
int sync_every = 1,
bool forward_rotation =
true,
837 int rebalance_every = 0,
double verlet_skin = 0.0) {
839 mpiSyncEvery_ = sync_every < 1 ? 1 : sync_every;
840 mpiForwardRotation_ = forward_rotation;
841 mpiRebalanceEvery_ = rebalance_every < 0 ? 0 : rebalance_every;
844 halo_->setVerletSkin(
static_cast<float>(verlet_skin));
847 long mpiRebuilds()
const {
return halo_->numRebuilds(); }
848 long mpiGathers()
const {
return halo_->numGathers(); }
851 int rebalance() {
return halo_->rebalance(
P_); }
857 int migrateToWeights(
const std::vector<peclet::core::Real>& w,
int align = 1) {
858 if (!halo_->inited())
859 throw std::runtime_error(
"migrate_to_weights: call init_mpi first");
860 const auto& G = halo_->decomposer().globalSize();
862 MPI_Comm_size(halo_->comm(), &size);
863 if (align < 1 || (align & (align - 1)) != 0)
864 throw std::invalid_argument(
"migrate_to_weights: align must be a power of two >= 1, got " +
865 std::to_string(align));
866 double coarseCells = 1.0;
867 std::size_t cells = 1;
868 for (
int k = 0; k < 3; ++k) {
869 if (G[k] % align != 0)
870 throw std::invalid_argument(
"migrate_to_weights: align " + std::to_string(align) +
871 " does not divide the ORB grid (" + std::to_string(G[0]) +
872 ", " + std::to_string(G[1]) +
", " + std::to_string(G[2]) +
874 coarseCells *=
static_cast<double>(G[k] / align);
875 cells *=
static_cast<std::size_t
>(G[k]);
877 if (coarseCells <
static_cast<double>(size))
878 throw std::invalid_argument(
"migrate_to_weights: align " + std::to_string(align) +
879 " leaves fewer aligned boxes than ranks");
880 if (w.size() != cells)
881 throw std::invalid_argument(
"migrate_to_weights: weights has " + std::to_string(w.size()) +
882 " entries, the ORB grid has " + std::to_string(cells) +
" cells");
883 return halo_->migrateToWeights(
P_, w, align);
886 const peclet::core::decomp::BlockDecomposer<3>& decomposer()
const {
return halo_->decomposer(); }
892 void ensureGlobalGids() {
896 MPI_Exscan(&mine, &base, 1, MPI_LONG, MPI_SUM, halo_->comm());
897 if (halo_->rank() == 0)
903 mpiGidsGlobal_ =
true;
905 void stepMpi(
int nsteps) {
906 requireDt(
"step_mpi");
907 const double rcut = mpiRcut_;
912 for (
int s = 0; s < nsteps; ++s) {
913 if (mpiRebalanceEvery_ > 0 && mpiStepCount_ % mpiRebalanceEvery_ == 0)
914 halo_->rebalance(
P_);
915 demStepMpi(
P_, *halo_, rcut, mpiSyncEvery_, mpiForwardRotation_);
923 void stepHertzMpi(
int substeps,
float skin_frac) {
924 requireDt(
"step_hertz_mpi");
926 if (mpiRebalanceEvery_ > 0 && mpiHertzCalls_ % mpiRebalanceEvery_ == 0)
927 halo_->rebalance(
P_);
929 demStepHertzMpi(
P_, *halo_,
P_.
dt, substeps, skin_frac);
931 int rank()
const {
return halo_->rank(); }
932 int numGhost()
const {
return halo_->numGhost(); }
946 const DebugContactCapture& debugCapturedContacts()
const {
return P_.
debugCaptured; }
981 Kokkos::View<std::uint64_t*, CpMem> seen(
"dbg_color_seen", std::max(nb,
P_.
capacity + 1));
982 int velConf = 0, posConf = 0;
984 Kokkos::deep_copy(space, seen, std::uint64_t(0));
992 Kokkos::parallel_reduce(
993 "peclet::dem::dbg_vel_color", Kokkos::RangePolicy<CpExec>(space, 0, nm),
994 KOKKOS_LAMBDA(
int idx,
int& acc) {
995 const int c = mColor(idx);
998 const auto m = manifolds(idx);
999 const std::uint64_t bit = std::uint64_t(1) << c;
1000 if ((Kokkos::atomic_fetch_or(&seen(ov.
slotA(idx, realIdx(m.bodyA))), bit) >> c) & 1)
1003 ((Kokkos::atomic_fetch_or(&seen(ov.
slotB(idx, realIdx(m.bodyB))), bit) >> c) & 1))
1011 if (nc > 0 && nu > 0) {
1012 Kokkos::deep_copy(space, seen, std::uint64_t(0));
1020 Kokkos::parallel_reduce(
1021 "peclet::dem::dbg_pos_color", Kokkos::RangePolicy<CpExec>(space, 0, nu),
1022 KOKKOS_LAMBDA(
int idx,
int& acc) {
1023 const int c = uColor(idx);
1026 const auto ct = contacts(units.leader(idx));
1027 const std::uint64_t bit = std::uint64_t(1) << c;
1028 if ((Kokkos::atomic_fetch_or(&seen(ov.
slotA(idx, ct.bodyA)), bit) >> c) & 1)
1030 if (ct.bodyB >= 0 &&
1031 ((Kokkos::atomic_fetch_or(&seen(ov.
slotB(idx, ct.bodyB)), bit) >> c) & 1))
1037 return {velConf, posConf};
1048 int velLeft = 0, posLeft = 0;
1052 Kokkos::parallel_reduce(
1053 "peclet::dem::dbg_vel_left", Kokkos::RangePolicy<CpExec>(space, 0, nm),
1054 KOKKOS_LAMBDA(
int i,
int& acc) {
1059 Kokkos::parallel_reduce(
1060 "peclet::dem::dbg_pos_left", Kokkos::RangePolicy<CpExec>(space, 0, nu),
1061 KOKKOS_LAMBDA(
int u,
int& acc) {
1066 return {velLeft, posLeft};
1075 if (L.numLevels <= 0 || L.numManifolds <= 0)
1079 Kokkos::View<int*, CpMem> grp(
"dbg_ml_grp", nb);
1084 Kokkos::parallel_for(
1085 "peclet::dem::dbg_ml_grp0", Kokkos::RangePolicy<CpExec>(space, 0, nb),
1086 KOKKOS_LAMBDA(
int i) { grp(i) = i; });
1088 for (
int lvl = 1; lvl <= L.numLevels; ++lvl) {
1089 const int pOff = L.parentOff[lvl - 1], ng = L.numGroups[lvl - 1];
1090 const int shift = 6 * (lvl - 1);
1091 Kokkos::parallel_for(
1092 "peclet::dem::dbg_ml_compose", Kokkos::RangePolicy<CpExec>(space, 0, nb),
1093 KOKKOS_LAMBDA(
int i) { grp(i) = parent(pOff + grp(i)); });
1094 Kokkos::View<std::uint64_t*, CpMem> seen(
"dbg_ml_seen", ng + 1);
1096 Kokkos::parallel_reduce(
1097 "peclet::dem::dbg_ml_color", Kokkos::RangePolicy<CpExec>(space, 0, nm),
1098 KOKKOS_LAMBDA(
int idx,
int& acc) {
1099 const int c =
static_cast<int>((cp(idx) >> shift) & 63);
1102 const auto m = manifolds(idx);
1103 const int gA = grp(realIdx(m.bodyA));
1104 const int gB = m.bodyB >= 0 ? grp(realIdx(m.bodyB)) : -1;
1105 const std::uint64_t bit = std::uint64_t(1) << c;
1106 if ((Kokkos::atomic_fetch_or(&seen(gA), bit) >> c) & 1)
1108 if (gB >= 0 && gB != gA && ((Kokkos::atomic_fetch_or(&seen(gB), bit) >> c) & 1))
1143 auto pos = Kokkos::create_mirror_view(
P_.
pos);
1144 Kokkos::deep_copy(pos,
P_.
pos);
1145 auto sc = Kokkos::create_mirror_view(
P_.
scale);
1146 Kokkos::deep_copy(sc,
P_.
scale);
1147 auto vel = Kokkos::create_mirror_view(
P_.
vel);
1148 Kokkos::deep_copy(vel,
P_.
vel);
1151 std::ofstream out(filename);
1153 throw std::runtime_error(
"Could not open file for writing: " + filename);
1154 out <<
"<?xml version=\"1.0\"?>\n";
1155 out <<
"<VTKFile type=\"PolyData\" version=\"0.1\" byte_order=\"LittleEndian\">\n";
1156 out <<
" <PolyData>\n";
1157 out <<
" <Piece NumberOfPoints=\"" << n <<
"\" NumberOfVerts=\"0\" "
1158 <<
"NumberOfLines=\"0\" NumberOfStrips=\"0\" NumberOfPolys=\"0\">\n";
1159 out <<
" <Points>\n";
1160 out <<
" <DataArray type=\"Float32\" Name=\"Position\" NumberOfComponents=\"3\" "
1161 "format=\"ascii\">\n";
1162 for (
int i = 0; i < n; ++i)
1163 out << pos(i, 0) <<
" " << pos(i, 1) <<
" " << pos(i, 2) <<
" ";
1164 out <<
"\n </DataArray>\n";
1165 out <<
" </Points>\n";
1166 out <<
" <PointData Scalars=\"Radius\">\n";
1167 out <<
" <DataArray type=\"Float32\" Name=\"Radius\" NumberOfComponents=\"1\" "
1168 "format=\"ascii\">\n";
1169 for (
int i = 0; i < n; ++i)
1171 out <<
"\n </DataArray>\n";
1172 out <<
" <DataArray type=\"Float32\" Name=\"Velocity\" NumberOfComponents=\"3\" "
1173 "format=\"ascii\">\n";
1174 for (
int i = 0; i < n; ++i)
1175 out << vel(i, 0) <<
" " << vel(i, 1) <<
" " << vel(i, 2) <<
" ";
1176 out <<
"\n </DataArray>\n";
1177 out <<
" </PointData>\n";
1178 out <<
" </Piece>\n";
1179 out <<
" </PolyData>\n";
1180 out <<
"</VTKFile>\n";
1182 std::printf(
"Exported VTP: %s\n", filename.c_str());
1186 bool dtSet_ =
false;
1187 void requireDt(
const char* who)
const {
1189 throw std::runtime_error(std::string(who) +
1190 ": call set_dt(dt) first (there is no default time step)");
1194 void uploadWalls() {
1195 const int n = std::max<int>(1,
static_cast<int>(wallsHost_.size()));
1196 if (
static_cast<int>(
P_.
walls.extent(0)) < n)
1197 P_.
walls = Kokkos::View<WallSdf*, CpMem>(
"walls", n);
1198 auto h = Kokkos::create_mirror_view(
P_.
walls);
1199 for (
size_t i = 0; i < wallsHost_.size(); ++i)
1200 h(i) = wallsHost_[i];
1201 Kokkos::deep_copy(
P_.
walls, h);
1204 std::vector<WallSdf> wallsHost_;
1205 std::vector<float> wallGridHost_;
1206 std::vector<peclet::core::geom::ShapeNode<float>> wallNodesHost_;
1208 std::map<int, peclet::core::geom::Transform<float>> wallRootTf_;
1209#ifdef PECLET_DEM_MPI
1210 std::unique_ptr<ParticleHalo> halo_ = std::make_unique<ParticleHalo>();
1211 double mpiRcut_ = 0.0;
1212 int mpiSyncEvery_ = 1;
1213 bool mpiForwardRotation_ =
true;
1214 int mpiRebalanceEvery_ = 0;
1215 long mpiStepCount_ = 0;
1216 long mpiHertzCalls_ =
1218 bool mpiGidsGlobal_ =
false;
dem — portable (ArborX) broad-phase, the Kokkos-native replacement for the CUDA-only cuBQL broad-phas...
Host-side shape registry (Layer 1).
void setSphereShape(float radius)
void ensureContactCapacity()
Host-facing facade with std::vector setters/getters (binding-agnostic).
void setQuaternions(const std::vector< float > &q)
void setFusedSweeps(const std::string &mode)
Fused colour sweeps (CUDA): run a whole sweep — and where eligible the whole adaptive iteration loop ...
std::vector< float > getMasses() const
void setThermostat(float temperature, float tau, float kB)
void setWallTransform(int wallIndex, F3 translation, float qx, float qy, float qz, float qw)
bool incrementalColoring() const
void relax(int n=1)
n dynamics-free RELAXATION substeps (overlap removal only, no gravity / velocity update): the growth-...
std::vector< float > getInvInertia() const
std::pair< int, int > debugColoringConflicts()
bool velocityUseGS() const
int numAsleep()
Number of currently-sleeping real bodies (diagnostics / tests).
std::vector< float > getVelocities() const
void setPairMaterial(int a, int b, float restitution, float friction)
Set the symmetric pair material (restitution, friction) for material ids (a, b).
std::vector< float > getScales() const
std::pair< int, int > debugColoringLeftovers()
void enablePeriodicity(bool x, bool y, bool z)
std::tuple< double, float, int > restOrphanStats()
Orphan-account diagnostics: (sum, max, count>0) of the per-body orphaned budget.
void stepHertz(int substeps, float skin_frac)
Advance substeps explicit soft-sphere Hertz-Mindlin steps of the stored dt (device-side loop; the (e,...
peclet::dem::SplitStats debugSplitStats() const
static std::vector< Simulation * > & registry()
std::tuple< float, float, float > domainExtent() const
void setVelocityUseGS(bool useGS)
std::tuple< float, float, float > domainOrigin() const
const V3 & externalTorquesView() const
std::vector< float > getQuaternions() const
std::string stabilizationMode() const
bool sleeping() const
True while island sleeping is enabled.
void setWallVelocity(int wallIndex, F3 linVel, F3 angVel, F3 center)
std::string restitutionModel() const
void exportSdf(const std::string &filename, int rx, int ry, int rz)
std::tuple< float, float, float > gravity() const
void writeVtp(const std::string &filename) const
void setVerletSkin(float skin_frac)
Verlet-cached impulse broadphase (single-GPU, non-periodic; default OFF).
void setGlobalScale(float s)
std::tuple< bool, bool, bool > domainPeriodic() const
void addPlane(float px, float py, float pz, float nx, float ny, float nz)
void setVelocities(const std::vector< float > &v)
float growthFactor() const
std::string shellDetection() const
int addSdfWall(const std::vector< float > &grid, int nx, int ny, int nz, F3 origin, F3 spacing, float restitution, float friction)
void setPositions(const std::vector< float > &xyz)
void setIncrementalColoring(bool enabled)
Incremental (warm-started) graph colouring of the contact manifolds (default ON): reuse last substep'...
void setShellDetection(const std::string &mode)
Narrow-phase shell detection of the XPBD engine (step / step_mpi / relax / compute_overlaps; the Hert...
float globalScale() const
void setSleeping(bool enabled, float threshold_scale=2.0f, int consecutive=64, float wake_scale=40.0f, bool wake_on_lost_contact=false, float immovable_frac=0.01f)
Island sleeping / freezing (single-GPU statics, default ON; enabled=False disables).
void setInvInertia(const std::vector< float > &ii)
const Vf & invMassView() const
void setDt(float dt)
The time step every stepper uses (suite/docs/NAMING.md 1.5).
void setAngularVelocities(const std::vector< float > &w)
const V3 & externalForcesView() const
void setDomainCanonical(F3 extent, F3 origin, bool px, bool py, bool pz)
void setHertzMaterial(int mat, float youngs, float poisson)
Per-material Young's modulus + Poisson ratio for the Hertz-Mindlin engine (material ids as in setMate...
void clearExternalForces()
std::vector< float > getAngularVelocities() const
void setScalesUniform(float s)
std::vector< float > getSdfGrid(int rx, int ry, int rz)
void clearExternalTorques()
void setCudaGraphs(bool enabled)
CUDA-graph replay of the solver's iteration loops (single-rank CUDA; default ON).
void setExternalTorques(const std::vector< float > &t)
void setDomain(float lx, float ly, float lz, bool px, bool py, bool pz)
int debugMultilevelColoringConflicts()
void setSolverIterations(int pos, int vel)
const V3 & velocitiesView() const
void setExternalForces(const std::vector< float > &f)
std::vector< float > getPositions() const
std::tuple< double, float, int > restBankStats()
Poisson-restitution diagnostics: (sum, max, count>0) of the committed per-pair owed separation impuls...
int addAnalyticWall(const std::vector< int > &nodeInts, const std::vector< float > &nodeReals, int root, bool invert, float restitution, float friction)
Add an ANALYTIC wall from a core shape tree in the flat encoding (Layer 1).
void setMaterialParams(float restitution_normal, float restitution_tangent, float friction)
std::string fusedSweeps() const
void exportLammps(const std::string &filename, int step) const
float getGrowthRate() const
void setDomainMinMax(F3 mn, F3 mx)
void setMaterialIds(const std::vector< int > &ids)
Per-particle material ids (0..kMaxMaterials-1); pair (e, mu) values come from setPairMaterial.
const V3 & positionsView() const
void setInvMass(const std::vector< float > &im)
void setScales(const std::vector< float > &s)
void setGrowthParams(float rate, float new_factor)
void setGravity(float gx, float gy, float gz)
void setWallMaterialId(int wid, int mat)
Give an SDF wall a material id so particle-wall (e, mu) also resolves via the pair table.
std::vector< float > wallSdfAt(int wallIndex, const std::vector< float > &pts) const
void setStabilizationMode(const std::string &mode)
Select the stabilization pass of the staged velocity solve: "off" (pure symmetric PGS),...
void setRestitutionModel(const std::string &model)
Restitution model of the PGS velocity solve: "newton" (default; per-substep restitution with Moreau's...
void step(int n=1)
Advance n XPBD substeps of the stored dt (set_dt first; suite/docs/NAMING.md 1.5).
dem — portable (Kokkos) time integration kernels (integration.cu).
dem — portable (CUDA-free) I/O helpers for the Kokkos dem module: a LAMMPS dump writer and a scalar-S...
dem — portable (Kokkos) owner<->ghost particle halo for the distributed XPBD step.
constexpr int kMlSlotSkip
void fillGidBaseKokkos(Vi gid, int n, int base)
gid(i) = base + i over [0, n) — the per-rank global-id re-base of the distributed step (namespace sco...
Kokkos::View< float *, CpMem > Vf
float sampleWallSdf(F3 p, const WallSdf &w, GridView grid)
Trilinearly sample a static world-space wall SDF at world point p.
Kokkos::View< float *[3], CpMem > V3
std::vector< float > generateSdfKokkos(int rx, int ry, int rz, F3 dmin, F3 dmax, int numReal, PosView pos, QuatView quat, ScalarF scale, ScalarI shapeId, Kokkos::View< const ShapeDesc *, CpMem > shapes, bool px, bool py, bool pz, GridView sdfGrid=GridView{}, float globalScale=1.0f)
globalScale multiplies every per-particle scale, exactly as the narrow-phase does (narrowphase....
void writeLammpsDump(const std::string &filename, int step, const std::vector< float > &pos, const std::vector< float > &vel, const std::vector< float > &quat, const std::vector< float > &radii, const float *boxMin, const float *boxMax, bool pbcEnabled)
int readInt(Kokkos::View< int, CpMem > v)
peclet::core::Vec3< float > toCoreVec(F3 p)
F3 -> core's Real-templated point type. Layout-identical; this is a type change, not a copy.
CpExec::memory_space CpMem
void demStepHertz(Particles &P, float dt, int nsteps, float skinFrac)
nsteps of the soft-sphere Hertz-Mindlin engine — the single-GPU instantiation of the force-based driv...
void updateGrowthScalesKokkos(int n, Vf scale, Vf targetScale, float factor)
Growth mode: scale = target * factor (when active).
float computeOverlapsKokkos(Particles &P)
Max pair interpenetration on the committed state (faithful to CUDA Simulation::compute_overlaps): cop...
void demStep(Particles &P)
One full XPBD DEM substep over the particle SoA (mirrors simulation.cpp Simulation::step()).
constexpr int kColorUncolourable
std::tuple< double, float, int > restBankStatsKokkos(Kokkos::View< const float *, CpMem > bank, int n)
Poisson-restitution diagnostics: (sum, max, count>0) over the committed owed-impulse store (namespace...
constexpr int kMaxMaterials
Pair-material lookup: flat [K*K*2] table, entry ((a*K + b)*2) = restitution, +1 = friction.
Kokkos::DefaultExecutionSpace CpExec
void writeSdfVti(const std::string &filename, const std::vector< float > &grid, int rx, int ry, int rz, const float *minB, const float *maxB)
dem — portable (Kokkos) narrow-phase: SDF point-shell collision + boundary planes.
dem — portable (Kokkos) SDF-grid reconstruction: the get_sdf_grid pipeline.
dem — portable (Kokkos) particle SoA container: the storage the dem flip pivots on.
dem — portable (Kokkos) periodic ghost generation (periodicity.cu / integration.cu).
int MPI_Exscan(const void *a, void *b, int c, MPI_Datatype d, MPI_Op e, MPI_Comm f)
dem — the shape registry: the host-side authoritative list of particle shapes (analytic sphere / holl...
dem — portable (host) surface-shell point generators for the analytic shapes.
dem — island sleeping / freezing for the single-GPU PGS statics path.
dem — the shared contact-solve driver: the full modern velocity + position solve sequence (warm-start...
dem — the force-based DEM step driver: explicit soft-contact time stepping (the engine family that co...
dem — portable (Kokkos) Coulomb friction cluster (the single dissipative friction path).
Soft-sphere Hertz–Mindlin DEM (reference force model) for SPHERES.
dem — multilevel (GraphMG-style) momentum-conserving contact stabilization.
dem — portable (Kokkos) XPBD position solve (pure overlap removal).
dem — portable (Kokkos) manifold velocity solve (normal restitution impulse).
dem — the single-rank step drivers over the particle SoA: the XPBD substep (demStep),...
dem — the distributed step drivers (gated PECLET_DEM_MPI): the hook policies that turn the shared sol...
Kokkos::View< WallSdf *, CpMem > walls
Kokkos::View< float *, CpMem > hertzE
Kokkos::View< unsigned char *, CpMem > asleep
Kokkos::View< unsigned char *, CpMem > materialId
Kokkos::View< int, CpMem > manifoldCount
Kokkos::View< int *, CpMem > unitStart
Kokkos::View< ManifoldC *, CpMem > manifolds
Kokkos::View< int, CpMem > contactCount
DebugContactCapture debugCaptured
Kokkos::View< float *, CpMem > pairMaterials
Kokkos::View< ShapeDesc *, CpMem > shapes
Kokkos::View< float *, CpMem > prevRestBank
Kokkos::View< peclet::core::geom::ShapeNode< float > *, CpMem > wallNodes
Kokkos::View< float *, CpMem > hertzNu
struct peclet::dem::Particles::MlLast mlLast
Kokkos::View< float *, CpMem > bodyOrphan
Kokkos::View< int *, CpMem > mlParent
Kokkos::View< float *, CpMem > sdfGrid
bool sleepWakeLostContact
Kokkos::View< int *, CpMem > unitColor
Kokkos::View< float, CpMem > maxOverlap
Kokkos::View< long long *, CpMem > mlColorPacked
Kokkos::View< float *, CpMem > wallGrid
Kokkos::View< int, CpMem > orphanClampCount
void allocate(int cap, int maxPairs_, int maxContacts_, int nShapes, int nShell, int nPlanes)
Kokkos::View< ContactC *, CpMem > contacts
Kokkos::View< int *, CpMem > unitContacts
Kokkos::View< PlaneP *, CpMem > planes
Kokkos::View< int *, CpMem > manifoldColor
Kokkos::View< int *, CpMem > slotB
Kokkos::View< int *, CpMem > slotA
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
diagnostics.split_stats() (docs/contact_solve_framework.md §WO-4 item 4, §13.5 WO-4b): the last subst...
Static, world-space SDF container/geometry the particles collide against (a drum barrel,...
peclet::core::geom::GridDesc< float > grid