PeriDEM 0.3.0
PeriDEM -- Peridynamics-based high-fidelity model for granular media
Loading...
Searching...
No Matches
particle Namespace Reference

Collection of methods and data related to particle object. More...

Data Structures

class  BaseParticle
 A class to store particle geometry, nodal discretization, and methods. More...
 
class  RefParticle
 A class to store reference particle related data. Consider a case of multiple hexagon-shaped particle related to each other by affine transformation. In such a case, it is possible to consider a reference hexagon particle and store the mesh and other details of only the reference particle. To get the data relevant to specific hexagon particle, one can apply the appropriate transformation on data for the reference hexagon particle. More...
 

Functions

void createReferenceParticles (data::ModelData &data)
 Build reference meshes from the particle deck (no placement).
 
void createParticlesFromFile (data::ModelData &data)
 Place particles from generation-file data; refs must already exist.
 
void createParticleUsingParticleZoneGeomObject (data::ModelData &data)
 Place one particle per zone geometry; refs must already exist.
 
void createParticles (data::ModelData &data)
 Build refs then place particles according to the generation method.
 
bool isLocallyOwned (const BaseParticle &p)
 True if this rank updates / assembles forces for the particle. Walls are replicated on every rank. With one MPI rank, everything is local.
 
std::string resolvedMpiStrategy (const data::ModelData &data)
 Resolved MPI strategy: none|particle|dof (auto expanded).
 
void assignMpiOwners (data::ModelData &data)
 Assign grain owners by spatial 2D brick decomposition of centers. Walls get owner -1 (all ranks). Honors Model.MPI_Strategy.
 
void exchangeGhostKinematics (data::ModelData &data)
 Distance-limited ghosts + kinematics exchange. Rebuilds the ghost plan on a Verlet-skin cadence (tied to contact neigh interval when available); between rebuilds only exchanges kinematics for the cached ghost set via Alltoallv.
 

Detailed Description

Collection of methods and data related to particle object.

Function Documentation

◆ assignMpiOwners()

void particle::assignMpiOwners ( data::ModelData &  data)

Assign grain owners by spatial 2D brick decomposition of centers. Walls get owner -1 (all ranks). Honors Model.MPI_Strategy.

Definition at line 313 of file particleMpi.cpp.

313 {
314 const int size = util::parallel::mpiSize();
315 const int rank = util::parallel::mpiRank();
316 const auto &grains = data.d_particlesListTypeParticle;
317 const std::string strategy = resolvedMpiStrategy(data);
318
319 for (auto *p : data.d_particlesListTypeAll) {
320 if (p->isWall())
321 p->d_mpiOwner = -1;
322 }
323
324 // Particle-MPI only: spatial brick over grain centers.
325 // DOF-MPI assigns nodes in pd::setupDofPartition; do not also brick grains.
326 const bool use_particle_partition =
327 strategy == "particle" && size > 1 && !grains.empty();
328
329 if (!use_particle_partition) {
330 for (auto *p : grains)
331 p->d_mpiOwner = 0;
332 if (util::parallel::isMpiEnabled() && rank == 0)
333 util::io::print(std::format(
334 "MPI strategy={}: grain ownership inactive (all grains → rank 0)\n",
335 strategy));
336 } else {
337 double xmin = std::numeric_limits<double>::max();
338 double xmax = -std::numeric_limits<double>::max();
339 double ymin = std::numeric_limits<double>::max();
340 double ymax = -std::numeric_limits<double>::max();
341 for (auto *p : grains) {
342 const auto &c = p->getXCenter();
343 xmin = std::min(xmin, c.d_x);
344 xmax = std::max(xmax, c.d_x);
345 ymin = std::min(ymin, c.d_y);
346 ymax = std::max(ymax, c.d_y);
347 }
348 const double Lx = std::max(xmax - xmin, 1.0e-16);
349 const double Ly = std::max(ymax - ymin, 1.0e-16);
350 int nx = 1, ny = size;
351 factor2d(size, Lx, Ly, nx, ny);
352
353 const double eps = 1.0e-14 * std::max(Lx, Ly);
354 for (auto *p : grains) {
355 const auto &c = p->getXCenter();
356 int ix = static_cast<int>((c.d_x - xmin) / Lx * nx);
357 int iy = static_cast<int>((c.d_y - ymin) / Ly * ny);
358 if (ix < 0)
359 ix = 0;
360 if (iy < 0)
361 iy = 0;
362 if (ix >= nx)
363 ix = nx - 1;
364 if (iy >= ny)
365 iy = ny - 1;
366 // nudge points on the max edge into the last cell
367 (void)eps;
368 p->d_mpiOwner = iy * nx + ix;
369 }
370
371 if (util::parallel::isMpiEnabled() && rank == 0)
372 util::io::print(std::format(
373 "MPI spatial owners: {} ranks as {}x{} brick over [{:.3g},{:.3g}] x "
374 "[{:.3g},{:.3g}]\n",
375 size, nx, ny, xmin, xmax, ymin, ymax));
376 }
377
378 data.d_mpiIncludeInContactCloud.assign(data.d_particlesListTypeAll.size(), 1);
379 data.d_mpiGhostPlanValid = false;
380 data.d_mpiGhostStepsSinceRebuild = 0;
381 data.d_mpiGhostNeedFrom.clear();
382 data.d_mpiGhostServeTo.clear();
383
385 size_t n_local = 0;
386 for (auto *p : grains)
387 if (isLocallyOwned(*p))
388 ++n_local;
389 util::io::print(std::format(
390 "MPI particle owners: rank {}/{} owns {}/{} grains (walls replicated)\n",
391 rank, size, n_local, grains.size()));
392 }
393}
void factor2d(int nproc, double Lx, double Ly, int &nx, int &ny)
Definition contact.h:20
std::string resolvedMpiStrategy(const data::ModelData &data)
Resolved MPI strategy: none|particle|dof (auto expanded).
bool isLocallyOwned(const BaseParticle &p)
True if this rank updates / assembles forces for the particle. Walls are replicated on every rank....
void print(const T &msg, int nt=print_default_tab, int printMpiRank=print_default_mpi_rank)
Prints formatted information.
Definition io.h:171
bool isMpiEnabled()
Function to check if MPI is enabled.
int mpiSize()
Get size (number) of processors.
int mpiRank()
get rank (id) of this processor

References anonymous_namespace{particleMpi.cpp}::factor2d(), isLocallyOwned(), util::parallel::isMpiEnabled(), util::parallel::mpiRank(), util::parallel::mpiSize(), util::io::print(), and resolvedMpiStrategy().

Referenced by PeriDEMModel::init().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ createParticles()

void particle::createParticles ( data::ModelData &  data)

Build refs then place particles according to the generation method.

Definition at line 182 of file createParticles.cpp.

182 {
184
185 if (data.d_particleDeck_p->d_pGenDeck.d_genMethod == "From_File") {
187 } else if (data.d_particleDeck_p->d_pGenDeck.d_genMethod ==
188 "Use_Particle_Geometry") {
189 createParticleUsingParticleZoneGeomObject(data);
190 } else {
191 throw std::runtime_error(
192 "Error: Particle generation method = " +
193 data.d_particleDeck_p->d_pGenDeck.d_genMethod + " is invalid.");
194 }
195}
void createReferenceParticles(data::ModelData &data)
Build reference meshes from the particle deck (no placement).
void createParticlesFromFile(data::ModelData &data)
Place particles from generation-file data; refs must already exist.

References createParticlesFromFile(), createParticleUsingParticleZoneGeomObject(), and createReferenceParticles().

Referenced by PeriDEMModel::init().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ createParticlesFromFile()

void particle::createParticlesFromFile ( data::ModelData &  data)

Place particles from generation-file data; refs must already exist.

Definition at line 61 of file createParticles.cpp.

61 {
62 util::io::log(1, data.d_name + ": Creating particle from file\n");
63
64 auto &pgen_deck = data.d_particleDeck_p->d_pGenDeck;
65 auto &pgen_json = pgen_deck.d_pGenJson;
66
68 0., 1., data.d_modelDeck_p->d_seed);
69
70 size_t num_particles = pgen_json.value("N", 0);
71 if (num_particles == 0)
72 throw std::runtime_error("No particles found in particle generation data");
73
74 auto model_ptr = modelAlias(data);
75
76 for (size_t i = 0; i < num_particles; i++) {
77 auto p_data = pgen_json.at(std::to_string(i));
78
79 std::map<std::string, size_t> p_group({
80 {"geom_id", p_data.at("geom_id").get<size_t>()},
81 {"mat_id", p_data.at("mat_id").get<size_t>()},
82 {"contact_id", p_data.at("contact_id").get<size_t>()},
83 });
84
85 auto site = util::Point(p_data.at("x").get<double>(),
86 p_data.at("y").get<double>(),
87 p_data.at("z").get<double>());
88
89 double angle = 0.;
90 double scale = p_data.value("s", double(1.));
91
92 if (p_data.find("theta") != p_data.end()) {
93 angle = p_data.at("theta").get<double>();
94 } else {
95 if (pgen_deck.d_genWithRandomRotation) {
96 angle = util::transform_to_uniform_dist(0., 2. * M_PI, uniform_dist());
97 }
98 }
99
100 auto axis = util::Point(p_data.value("ax", 0.), p_data.value("ay", 0.),
101 p_data.value("az", 1.));
102 const bool has_rotationPoint =
103 p_data.find("rotx") != p_data.end() &&
104 p_data.find("roty") != p_data.end() &&
105 p_data.find("rotz") != p_data.end();
106
107 auto &ref_p = data.d_referenceParticles[p_group["geom_id"]];
108 const auto &rep_geom_p = ref_p->d_geom_p;
109
110 std::shared_ptr<geom::GeomObject> p_geom(
111 geom::createGeomDeepCopy(rep_geom_p.get()));
112 const util::Point c0 = p_geom->center();
113 const util::Point t = site - c0;
114 util::Point rotationPivot =
115 has_rotationPoint
116 ? util::Point(p_data.value("rotx", 0.), p_data.value("roty", 0.),
117 p_data.value("rotz", 0.))
118 : c0;
119 p_geom->transform(t, scale, angle, axis, &rotationPivot);
120
121 auto p_transform =
122 geom::ParticleTransform(t, axis, angle, scale, rotationPivot);
123
124 const bool is_wall = p_data.value("is_wall", false);
125
126 auto p = new particle::BaseParticle(
127 data.d_particlesListTypeAll.size(), is_wall, ref_p->getDimension(),
128 p_group, false, ref_p->getNumNodes(), 0., model_ptr, ref_p, p_geom,
129 p_transform, ref_p->getMeshP(),
130 data.d_particleDeck_p->d_pMaterialVec[p_group["mat_id"]], true);
131
132 if (is_wall)
133 data.d_particlesListTypeWall.push_back(p);
134 else
135 data.d_particlesListTypeParticle.push_back(p);
136 data.d_particlesListTypeAll.push_back(p);
137 }
138}
A class to store particle geometry, nodal discretization, and methods.
Templated probability distribution.
Definition randomDist.h:90
std::shared_ptr< data::ModelData > modelAlias(data::ModelData &data)
GeomObject * createGeomDeepCopy(GeomObject *obj)
Creates a deep copy of a geometric object.
void log(std::ostringstream &oss, bool screen_out=false, int printMpiRank=print_default_mpi_rank)
Global method to log the message.
Definition io.cpp:41
double angle(util::Point a, util::Point b)
Computes angle between two vectors.
double transform_to_uniform_dist(double min, double max, double sample)
Transform sample from U(0,1) to U(a,b)
Definition randomDist.h:80
A struct that stores transformation parameters and provides method to transform the particle....
A structure to represent 3d vectors.
Definition point.h:30

References geom::createGeomDeepCopy(), util::io::log(), anonymous_namespace{createParticles.cpp}::modelAlias(), and util::transform_to_uniform_dist().

Referenced by createParticles().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ createParticleUsingParticleZoneGeomObject()

void particle::createParticleUsingParticleZoneGeomObject ( data::ModelData &  data)

Place one particle per zone geometry; refs must already exist.

Definition at line 38 of file createParticles.cpp.

38 {
39 util::io::log(1, data.d_name + ": Creating particle using Particle Zone Geometry Object\n");
40
41 auto p_transform = geom::ParticleTransform();
42 auto model_ptr = modelAlias(data);
43
44 for (size_t z = 0; z < data.d_particleDeck_p->d_pMeshVec.size(); z++) {
45 std::map<std::string, size_t> p_group(
46 {{"geom_id", z}, {"mat_id", 0}, {"contact_id", 0}});
47
48 auto ref_p = data.d_referenceParticles[z];
49
50 auto p = new particle::BaseParticle(
51 data.d_particlesListTypeAll.size(), false, ref_p->getDimension(),
52 p_group, false, ref_p->getNumNodes(), 0., model_ptr, ref_p,
53 ref_p->getGeomP(), p_transform, ref_p->getMeshP(),
54 data.d_particleDeck_p->d_pMaterialVec[0], true);
55
56 data.d_particlesListTypeParticle.push_back(p);
57 data.d_particlesListTypeAll.push_back(p);
58 }
59}

References util::io::log(), and anonymous_namespace{createParticles.cpp}::modelAlias().

Referenced by createParticles().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ createReferenceParticles()

void particle::createReferenceParticles ( data::ModelData &  data)

Build reference meshes from the particle deck (no placement).

Definition at line 140 of file createParticles.cpp.

140 {
141 data.d_particlesListTypeParticle.resize(0);
142 data.d_particlesListTypeAll.resize(0);
143 data.d_particlesListTypeWall.resize(0);
144 data.d_referenceParticles.clear();
145
146 if (data.d_particleDeck_p->d_pGeomVec.size() == 0)
147 throw std::runtime_error(
148 "No particle geometry groups found in particle deck");
149
150 if (data.d_particleDeck_p->d_pGeomVec.size() !=
151 data.d_particleDeck_p->d_pMeshVec.size())
152 throw std::runtime_error(
153 "Number of particle geometry groups must be equal to number of "
154 "particle mesh groups");
155
156 auto model_ptr = modelAlias(data);
157
158 for (size_t z = 0; z < data.d_particleDeck_p->d_pMeshVec.size(); z++) {
159 auto &zmeshDeck = data.d_particleDeck_p->d_pMeshVec[z];
160 auto &zgeomDeck = data.d_particleDeck_p->d_pGeomVec[z];
161
162 util::io::log(0, data.d_name +
163 ": Creating mesh for reference particle in mesh group = " +
164 std::to_string(z) + "\n");
165
167 zmeshDeck, zgeomDeck, data.d_modelDeck_p.get(), data.d_name);
168
169 util::io::log(0, data.d_name +
170 ": Creating reference particle in mesh group = " +
171 std::to_string(z) + "\n");
172
173 auto &rep_geom_p = zgeomDeck.d_geom_p;
174
175 auto ref_p = std::make_shared<particle::RefParticle>(
176 data.d_referenceParticles.size(), model_ptr, rep_geom_p, mesh);
177
178 data.d_referenceParticles.emplace_back(ref_p);
179 }
180}
std::shared_ptr< mesh::Mesh > createParticleMesh(const inp::MeshDeck &zmeshDeck, const geom::GeomData &zgeomDeck, const inp::ModelDeck *modelDeck, const std::string &modelName)
Build a reference-particle mesh: file, uniform rectangle, or in-process Gmsh.
Collection of methods and data related to finite element and mesh.
Definition mesh.cpp:29

References mesh_gen::createParticleMesh(), util::io::log(), and anonymous_namespace{createParticles.cpp}::modelAlias().

Referenced by createParticles().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ exchangeGhostKinematics()

void particle::exchangeGhostKinematics ( data::ModelData &  data)

Distance-limited ghosts + kinematics exchange. Rebuilds the ghost plan on a Verlet-skin cadence (tied to contact neigh interval when available); between rebuilds only exchanges kinematics for the cached ghost set via Alltoallv.

Definition at line 395 of file particleMpi.cpp.

395 {
397 data.d_mpiIncludeInContactCloud.assign(data.d_particlesListTypeAll.size(),
398 1);
399 return;
400 }
401
402 // Particle-MPI only. DOF-MPI owns nodes, not whole grains; grain packing
403 // from the "grain owner" rank would overwrite remote-owned nodal u/v with
404 // stale zeros. Nodal sync lives in pd::exchangeGhostDisplacement.
405 if (data.d_pdDofMpi || resolvedMpiStrategy(data) == "dof") {
406 data.d_mpiIncludeInContactCloud.assign(data.d_particlesListTypeAll.size(),
407 1);
408 return;
409 }
410
411 using clock = std::chrono::steady_clock;
412 const auto t0 = clock::now();
413
414 const size_t interval = ghostRebuildInterval(data);
415 int local_need =
416 (!data.d_mpiGhostPlanValid ||
417 data.d_mpiGhostStepsSinceRebuild >= interval)
418 ? 1
419 : 0;
420 // All ranks must take the same branch: rebuild uses Allgather, exchange uses
421 // Alltoall — disagreeing on rebuild deadlocks (seen past ~50% with contact).
422 int global_need = local_need;
423 MPI_Allreduce(MPI_IN_PLACE, &global_need, 1, MPI_INT, MPI_MAX,
425 const bool need_rebuild = global_need != 0;
426
427 auto t_rebuild0 = t0;
428 auto t_rebuild1 = t0;
429 if (need_rebuild) {
430 t_rebuild0 = clock::now();
432 t_rebuild1 = clock::now();
433 }
434
435 const auto t_ex0 = clock::now();
437 const auto t1 = clock::now();
438
439 ++data.d_mpiGhostStepsSinceRebuild;
440
441 data.appendKeyData("mpi_ghost_select_time",
442 util::methods::timeDiff(t_rebuild0, t_rebuild1));
443 data.appendKeyData("mpi_halo_exchange_time",
444 util::methods::timeDiff(t_ex0, t1));
445 data.appendKeyData("mpi_exchange_time", util::methods::timeDiff(t0, t1));
446 data.setKeyData("mpi_ghost_rebuild", need_rebuild ? 1.0 : 0.0);
447}
void rebuildGhostPlan(data::ModelData &data)
size_t ghostRebuildInterval(const data::ModelData &data)
void exchangeCachedKinematics(data::ModelData &data)
float timeDiff(std::chrono::steady_clock::time_point begin, std::chrono::steady_clock::time_point end, std::string unit="microseconds")
Returns difference between two times.
Definition vecMethods.h:309
MPI_Comm mpiComm()
Get MPI comm.

References anonymous_namespace{particleMpi.cpp}::exchangeCachedKinematics(), anonymous_namespace{particleMpi.cpp}::ghostRebuildInterval(), util::parallel::isMpiEnabled(), util::parallel::mpiComm(), anonymous_namespace{particleMpi.cpp}::rebuildGhostPlan(), resolvedMpiStrategy(), and util::methods::timeDiff().

Referenced by PeriDEMModel::computeForces().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ isLocallyOwned()

bool particle::isLocallyOwned ( const BaseParticle &  p)

True if this rank updates / assembles forces for the particle. Walls are replicated on every rank. With one MPI rank, everything is local.

Definition at line 28 of file particleMpi.cpp.

28 {
30 return true;
31 if (p.isWall())
32 return true;
34}
int d_mpiOwner
MPI owner rank for this grain (-1 = wall / all ranks). Used by particle-parallel (T09); ignored when ...
bool isWall() const
Is this particle a wall?

References particle::BaseParticle::d_mpiOwner, util::parallel::isMpiEnabled(), particle::BaseParticle::isWall(), and util::parallel::mpiRank().

Referenced by rw::writer::VtkParticleWriter::appendMeshParallelPiece(), contact::Damping::apply(), assignMpiOwners(), PeriDEMModel::computeExternalForces(), PeriDEMModel::init(), anonymous_namespace{main.cpp}::GrainContactProbe::ownsNode(), anonymous_namespace{main.cpp}::ownsNode(), MpiMetricTsProbe::ownsNode(), and anonymous_namespace{particleMpi.cpp}::rebuildGhostPlan().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ resolvedMpiStrategy()

std::string particle::resolvedMpiStrategy ( const data::ModelData &  data)

Resolved MPI strategy: none|particle|dof (auto expanded).

Definition at line 36 of file particleMpi.cpp.

36 {
37 std::string s = "auto";
38 if (data.d_input_p && data.d_input_p->d_modelDeck_p)
39 s = data.d_input_p->d_modelDeck_p->d_mpiStrategy;
40 if (s.empty())
41 s = "auto";
42 if (s == "auto") {
43 if (data.d_input_p && data.d_input_p->isMultiParticle())
44 return "particle";
45 return "dof";
46 }
47 return s;
48}

Referenced by assignMpiOwners(), exchangeGhostKinematics(), and pd::setupDofPartition().

Here is the caller graph for this function: