PeriDEM 0.3.0
PeriDEM -- Peridynamics-based high-fidelity model for granular media
Loading...
Searching...
No Matches
main.cpp
Go to the documentation of this file.
1/*
2 * -------------------------------------------
3 * Copyright (c) 2021 - 2026 Prashant K. Jha
4 * -------------------------------------------
5 * PeriDEM https://github.com/prashjha/PeriDEM
6 *
7 * Distributed under the Boost Software License, Version 1.0. (See accompanying
8 * file LICENSE)
9 *
10 * Quick Multi_Particle MPI identity driver: two circles + one fixed floor wall.
11 * Exercises DispBC on the wall, grain–grain contact, and grain–wall contact.
12 * Dumps gathered final nodal u,v for serial / Particle-MPI / DOF-MPI compare.
13 *
14 * -mpiStrategy none|particle|dof
15 * -outputDir / -inputDir
16 * -nThreads
17 */
18
19#include "geom/geomIncludes.h"
20#include "inp/deckIncludes.h"
21#include "inp/input.h"
25#include "periDEMModel.h"
27#include "util/function.h"
28#include "util/io.h"
29#include "util/parallelUtil.h"
30#include "util/point.h"
31
32#include <mpi.h>
33#include <algorithm>
34#include <cmath>
35#include <cstdint>
36#include <filesystem>
37#include <format>
38#include <fstream>
39#include <stdexcept>
40#include <string>
41#include <thread>
42#include <vector>
43
44namespace {
45
46std::string directoryPathWithTrailingSep(const std::filesystem::path &dir) {
47 namespace fs = std::filesystem;
48 fs::path n = fs::absolute(dir).lexically_normal();
49 std::string s = n.string();
50 if (!s.empty() && s.back() != '/' && s.back() != '\\')
51 s += fs::path::preferred_separator;
52 return s;
53}
54
55bool ownsNode(const data::ModelData &data, size_t i) {
56 const int rank = util::parallel::mpiRank();
57 if (data.d_pdDofMpi) {
58 if (i >= data.d_pdNodePartition.size())
59 return false;
60 return static_cast<int>(data.d_pdNodePartition[i]) == rank;
61 }
62 for (const auto *p : data.d_particlesListTypeAll) {
63 const size_t i0 = p->d_globStart;
64 const size_t i1 = i0 + p->getNumNodes();
65 if (i < i0 || i >= i1)
66 continue;
67 if (p->isWall())
68 return rank == 0;
69 return particle::isLocallyOwned(*p);
70 }
71 return rank == 0;
72}
73
75 const std::filesystem::path &out_dir) {
76 const size_t n = data.d_u.size();
77 std::vector<double> buf(6 * n, 0.);
78 for (size_t i = 0; i < n; ++i) {
79 if (!ownsNode(data, i))
80 continue;
81 buf[6 * i + 0] = data.d_u[i].d_x;
82 buf[6 * i + 1] = data.d_u[i].d_y;
83 buf[6 * i + 2] = data.d_u[i].d_z;
84 buf[6 * i + 3] = data.d_v[i].d_x;
85 buf[6 * i + 4] = data.d_v[i].d_y;
86 buf[6 * i + 5] = data.d_v[i].d_z;
87 }
89 MPI_Allreduce(MPI_IN_PLACE, buf.data(), static_cast<int>(buf.size()),
90 MPI_DOUBLE, MPI_SUM, util::parallel::mpiComm());
91 if (util::parallel::mpiRank() != 0)
92 return;
93
94 std::filesystem::create_directories(out_dir / "nodal");
95 {
96 std::ofstream xr(out_dir / "nodal" / "x_ref.bin", std::ios::binary);
97 const uint32_t nn = static_cast<uint32_t>(n);
98 xr.write(reinterpret_cast<const char *>(&nn), sizeof(nn));
99 for (size_t i = 0; i < n; ++i) {
100 const double p[3] = {data.d_xRef[i].d_x, data.d_xRef[i].d_y,
101 data.d_xRef[i].d_z};
102 xr.write(reinterpret_cast<const char *>(p), sizeof(p));
103 }
104 }
105 {
106 std::ofstream os(out_dir / "nodal" / "final_uv.bin", std::ios::binary);
107 const char magic[4] = {'P', 'D', 'U', 'V'};
108 const uint32_t step32 = static_cast<uint32_t>(data.currentStep());
109 const uint32_t nn = static_cast<uint32_t>(n);
110 os.write(magic, 4);
111 os.write(reinterpret_cast<const char *>(&step32), sizeof(step32));
112 os.write(reinterpret_cast<const char *>(&nn), sizeof(nn));
113 os.write(reinterpret_cast<const char *>(buf.data()),
114 static_cast<std::streamsize>(buf.size() * sizeof(double)));
115 }
116}
117
120public:
123 if (data.d_particlesListTypeParticle.size() < 2)
124 return;
125 const auto *g0 = data.d_particlesListTypeParticle[0];
126 const auto *g1 = data.d_particlesListTypeParticle[1];
127 double Rc = 0.;
128 try {
129 Rc = data.d_particleDeck_p->d_contactDeck.getContact(0, 0).d_contactR;
130 } catch (...) {
131 return;
132 }
133 d_Rc = Rc;
134 const double gap = g0->getXCenter().dist(g1->getXCenter()) -
135 g0->d_geom_p->boundingRadius() -
136 g1->d_geom_p->boundingRadius();
137 d_minPpGap = std::min(d_minPpGap, gap);
138 if (gap < Rc)
139 d_sawPp = true;
140
141 double min_y = 1.e300;
142 for (size_t i = 0; i < data.d_x.size(); ++i) {
143 if (data.getParticleFromAllList(data.d_ptId[i])->isWall())
144 continue;
145 min_y = std::min(min_y, data.d_x[i].d_y);
146 }
147 if (util::parallel::mpiSize() > 1)
148 MPI_Allreduce(MPI_IN_PLACE, &min_y, 1, MPI_DOUBLE, MPI_MIN,
150 d_minGrainY = std::min(d_minGrainY, min_y);
151 if (min_y < Rc)
152 d_sawWall = true;
153 }
154
155 void assertOk() const {
156 int saw_pp = d_sawPp ? 1 : 0;
157 int saw_wall = d_sawWall ? 1 : 0;
158 double min_pp = d_minPpGap;
159 double min_y = d_minGrainY;
160 double rc = d_Rc;
161 if (util::parallel::mpiSize() > 1) {
162 int saw_pp_g = 0, saw_wall_g = 0;
163 double min_pp_g = 0., min_y_g = 0., rc_g = 0.;
164 MPI_Allreduce(&saw_pp, &saw_pp_g, 1, MPI_INT, MPI_MAX,
166 MPI_Allreduce(&saw_wall, &saw_wall_g, 1, MPI_INT, MPI_MAX,
168 MPI_Allreduce(&min_pp, &min_pp_g, 1, MPI_DOUBLE, MPI_MIN,
170 MPI_Allreduce(&min_y, &min_y_g, 1, MPI_DOUBLE, MPI_MIN,
172 MPI_Allreduce(&rc, &rc_g, 1, MPI_DOUBLE, MPI_MAX,
174 saw_pp = saw_pp_g;
175 saw_wall = saw_wall_g;
176 min_pp = min_pp_g;
177 min_y = min_y_g;
178 rc = rc_g;
179 }
180 if (!saw_pp)
181 throw std::runtime_error(std::format(
182 "particle-particle contact missing: min_gap={:.6e} Rc={:.6e}", min_pp,
183 rc));
184 if (!saw_wall)
185 throw std::runtime_error(std::format(
186 "particle-wall contact missing: min_grain_y={:.6e} Rc={:.6e}", min_y,
187 rc));
188 if (util::parallel::mpiRank() == 0)
189 util::io::print(std::format(
190 "contacts OK: min_pp_gap={:.6e} min_grain_y={:.6e} Rc={:.6e}\n",
191 min_pp, min_y, rc));
192 }
193
194private:
195 bool d_sawPp = false;
196 bool d_sawWall = false;
197 double d_minPpGap = 1.e300;
198 double d_minGrainY = 1.e300;
199 double d_Rc = 0.;
200};
201
202json buildInputJson(const std::string &output_path,
203 const std::filesystem::path &mesh_cir,
204 const std::filesystem::path &mesh_wall,
205 const std::string &mpi_strategy, double final_time,
206 size_t num_steps) {
207 const double R = 0.001;
208 const double mesh_size = R / 4.0;
209 const double horizon = 3.0 * mesh_size;
210 const double h_est = 0.7 * mesh_size;
211 const double Rc_est = 0.95 * h_est;
212
213 // Start inside PP Rc and near the floor; soft Kn (below) keeps contact stable.
214 const double gap_pp = 0.5 * Rc_est;
215 const double cy = R + 0.35 * Rc_est;
216 const double cx0 = -(R + 0.5 * gap_pp);
217 const double cx1 = +(R + 0.5 * gap_pp);
218
219 const double wall_half = 3.5 * R;
220 const double wall_thick = std::max(2.0 * mesh_size, horizon);
221
222 geom::GeomData grain;
223 grain.d_geomName = "circle";
224 grain.d_geomParams = {R, 0., 0., 0.};
225 geom::GeomData floor;
226 floor.d_geomName = "rectangle";
227 floor.d_geomParams = {-wall_half, -wall_thick, 0., wall_half, 0., 0.};
228
229 const double poisson = 0.25;
230 const double rho = 1200.0;
231 const double K = 2.16e7;
232 const double E = material::toE(K, poisson);
233 const double G = material::toGE(E, poisson);
234 const double Gc = 50.0;
235 const double Kn =
236 util::normalContactStiffness(K, K, horizon);
237
239 json{{"Dimension", 2},
240 {"Final_Time", final_time},
241 {"Time_Steps", num_steps},
242 {"Discretization_Type", json{{"Spatial", "finite_difference"}, {"Time", "central_difference"}}},
243 {"Populate_ElementNodeConnectivity", true},
244 {"Quad_Approximation_Order", 2},
245 {"Particle_Sim_Type", "Multi_Particle"},
246 {"Seed", 0}});
247 model["MPI_Strategy"] = mpi_strategy;
248 model["Wall_Contact"] = "meshed";
249
251 json{{"File_Format", "vtu"},
252 {"Path", output_path},
253 {"Tags", std::vector<std::string>({"Displacement", "Velocity", "Force",
254 "Particle_ID"})},
255 {"Output_Interval", std::max<size_t>(1, num_steps)},
256 {"Debug", 1},
257 {"Perform_FE_Out", false},
258 {"Compress_Type", "zlib"},
259 {"Perform_Out", false},
260 {"Test_Output_Interval", num_steps},
261 {"Tag_PP", ""},
262 {"PVD_Collection", false}});
263
264 // Wall (particle index 2) fully fixed; downward IC on both grains.
266 json{{"Displacement_BC_Sets", 1},
267 {"IC_Sets", 1},
268 {"Gravity", util::Point(0, -10, 0).toVec()}});
269 bc["Displacement_BC"]["Set_1"] = json{
270 {"Particle_List", std::vector<size_t>{2}},
271 {"Direction", std::vector<size_t>{1, 2}},
272 {"Time_Function",
273 {{"Type", "constant"}, {"Parameters", std::vector<double>{0.}}}},
274 {"Spatial_Function", {{"Type", "constant"}}},
275 {"Zero_Displacement", true}};
276 bc["IC"]["Set_1"] = inp::BCBaseDeck::getExampleJson(
277 json{{"Type", "IC"},
278 {"Particle_List", {0, 1}},
279 {"IC_Type", "Constant_Velocity"},
280 {"IC_Vector", {0., -0.02, 0.}}});
281
283 auto mesh_set = [&](const std::filesystem::path &f) {
284 return json{{"File", f.string()},
285 {"CreateMesh",
286 {{"Flag", true},
287 {"Info", "gmsh_builtin_mesh"},
288 {"Mesh_Size", mesh_size},
289 {"Write_Mesh_File", true}}}};
290 };
291 json mesh = {{"Sets", 2},
292 {"Set_1", mesh_set(mesh_cir)},
293 {"Set_2", mesh_set(mesh_wall)}};
294
297 json{{"Type", "PDState"},
298 {"Is_Plane_Strain", false},
299 {"Horizon", horizon},
300 {"Density", rho},
301 {"K", K},
302 {"G", G},
303 {"Gc", Gc},
304 {"Compute_From_Classical", true},
305 {"Influence_Function", json{{"Type", 0}}}});
307 json{{"Type", "PDState"},
308 {"Is_Plane_Strain", false},
309 {"Horizon", horizon},
310 {"Density", rho},
311 {"K", K},
312 {"G", G},
313 {"Gc", Gc},
314 {"Compute_From_Classical", true},
315 {"Influence_Function", json{{"Type", 0}}}});
316
318 json{{"Contact_Radius_Factor", 0.95},
319 {"Kn", Kn},
320 {"Damping_On", false},
321 {"Epsilon", 0.95},
322 {"Friction_On", false},
323 {"Friction_Coeff", 0.0},
324 {"Kn_Factor", 1.0},
325 {"Beta_n_Factor", 100.0},
326 {"K", K}});
327 // Soften contact spring so short MPI identity runs stay stable and bit-reproducible.
328 contact_base["Kn_Factor"] = 1.0e-4;
330 contact["Set_1_1"] = contact_base;
331 contact["Set_1_2"] = contact_base;
332 contact["Set_2_2"] = contact_base;
333 contact["Damping_Law"] = "off";
334 contact["Friction_Law"] = "coulomb_simple";
335
336 auto pgen = inp::PGenDeck::getExampleJson(json{{"Method", "From_File"}});
337 pgen["Random_Rotation"] = false;
338 pgen["Data"]["N"] = 3;
339 pgen["Data"]["0"] = json{{"x", cx0},
340 {"y", cy},
341 {"z", 0.},
342 {"theta", 0.},
343 {"s", 1.},
344 {"geom_id", 0},
345 {"mat_id", 0},
346 {"contact_id", 0}};
347 pgen["Data"]["1"] = json{{"x", cx1},
348 {"y", cy},
349 {"z", 0.},
350 {"theta", 0.},
351 {"s", 1.},
352 {"geom_id", 0},
353 {"mat_id", 0},
354 {"contact_id", 0}};
355 pgen["Data"]["2"] = json{{"x", 0.},
356 {"y", -0.5 * wall_thick},
357 {"z", 0.},
358 {"theta", 0.},
359 {"s", 1.},
360 {"geom_id", 1},
361 {"mat_id", 1},
362 {"contact_id", 1},
363 {"is_wall", true}};
364
365 util::io::print(std::format(
366 "mpi_identity_twop_wall: R={:.4g} mesh={:.4g} Rc~{:.4g} gap_pp={:.4g} "
367 "cy={:.4g} T={:.3g} N={}\n",
368 R, mesh_size, Rc_est, gap_pp, cy, final_time, num_steps));
369
370 return json{{"Model", model},
371 {"Output", output},
372 {"Force_BC", bc["Force_BC"]},
373 {"Displacement_BC", bc["Displacement_BC"]},
374 {"IC", bc["IC"]},
375 {"Particle", particle},
376 {"Mesh", mesh},
377 {"Material", material},
378 {"Contact", contact},
379 {"Neighbor",
381 json{{"Update_Criteria", "simple_all"},
382 {"Search_Factor", 5.0},
383 {"Search_Interval", 1},
384 {"Near_Bd_Nodes_Tol", 0.5}})},
385 {"Particle_Generation", pgen}};
386}
387
388} // namespace
389
390int main(int argc, char *argv[]) {
391 util::parallel::initMpi(argc, argv);
392 util::io::InputParser input(argc, argv);
393
394 unsigned n_threads = 1;
395 if (input.cmdOptionExists("-nThreads"))
396 n_threads = static_cast<unsigned>(std::stoi(input.getCmdOption("-nThreads")));
398
399 std::string mpi_strategy = "none";
400 if (input.cmdOptionExists("-mpiStrategy"))
401 mpi_strategy = input.getCmdOption("-mpiStrategy");
402
403 namespace fs = std::filesystem;
404 fs::path out_dir = fs::current_path() / "out";
405 fs::path inp_dir = fs::current_path() / "inp";
406 if (input.cmdOptionExists("-outputDir"))
407 out_dir = input.getCmdOption("-outputDir");
408 if (input.cmdOptionExists("-inputDir"))
409 inp_dir = input.getCmdOption("-inputDir");
410 fs::create_directories(out_dir);
411 fs::create_directories(inp_dir);
412
413 // Short stable window with soft contact.
414 const double final_time = 2.0e-4;
415 const size_t num_steps = 1000;
416
417 auto input_json =
418 buildInputJson(directoryPathWithTrailingSep(out_dir),
419 inp_dir / "mesh_cir.msh", inp_dir / "mesh_wall.msh",
420 mpi_strategy, final_time, num_steps);
421 {
422 std::ofstream os(inp_dir / "input.json");
423 os << input_json.dump(2);
424 }
425
426 auto deck = std::make_shared<inp::Input>(input_json);
427 PeriDEMModel dem(deck);
428 auto witness = std::make_unique<ContactWitness>();
429 ContactWitness *witness_p = witness.get();
430 dem.setPostprocess(std::move(witness));
431 dem.init();
432 dem.integrate();
433 witness_p->assertOk();
434 dumpFinalNodal(dem, out_dir);
435 dem.close();
436
437 if (util::parallel::mpiRank() == 0)
438 util::io::print(std::format("mpi_identity_twop_wall done (strategy={})\n",
439 mpi_strategy));
441 return 0;
442}
int main(int argc, char *argv[])
Definition main.cpp:29
void setPostprocess(std::unique_ptr< postprocess::Postprocess > p)
void checkStop(data::ModelData &data) override
Definition main.cpp:121
A class to store model data.
Definition modelData.h:50
Extra postprocessing and stop criteria (VTU writing is in rw::).
Definition postprocess.h:24
virtual void checkStop(data::ModelData &data)
Input command line argument parser.
Definition inputParser.h:28
bool cmdOptionExists(const std::string &option) const
Check if argument exists.
Definition inputParser.h:60
const std::string & getCmdOption(const std::string &option) const
Get value of argument specified by key.
Definition inputParser.h:45
nlohmann::ordered_json json
std::string directoryPathWithTrailingSep(const std::filesystem::path &dir)
Definition main.cpp:34
bool ownsNode(const data::ModelData &data, size_t i)
Definition main.cpp:55
void dumpFinalNodal(const data::ModelData &data, const std::filesystem::path &out_dir)
Definition main.cpp:74
json buildInputJson(const std::string &output_path, const std::filesystem::path &mesh_cir, const std::filesystem::path &mesh_wall, const std::string &mpi_strategy, double final_time, size_t num_steps)
Definition main.cpp:202
Definition contact.h:20
double toGE(double E, double nu)
Compute shear modulus from Young's modulus E and Poisson's ratio nu.
double toE(double K, double nu)
Compute Young's modulus E from Bulk modulus K and Poisson's ratio nu.
Collection of methods and data related to finite element and mesh.
Definition mesh.cpp:29
Collection of methods and data related to particle object.
Definition modelData.h:36
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
void initNThreads(unsigned int nThreads=std::thread::hardware_concurrency())
Initializes MpiStatus struct.
void initMpi(int argc=0, char *argv[]=nullptr)
Initializes MPI and also creates MpiStatus struct.
int mpiSize()
Get size (number) of processors.
int mpiRank()
get rank (id) of this processor
void finalizeMpi()
Call MPI_Finalize if this process initialized MPI.
MPI_Comm mpiComm()
Get MPI comm.
double normalContactStiffness(const double &K1, const double &K2, const double &horizon, int horizonPower=5)
Silling normal contact stiffness from two bulk moduli and the horizon.
Definition function.cpp:139
Input data for geometrical objects.
std::vector< double > d_geomParams
Zone parameters.
std::string d_geomName
Zone type.
static json getExampleJson(const json &given=json::object(), const geom::GeomData *region=nullptr)
Returns example JSON object for ModelDeck configuration.
Definition bcBaseDeck.h:233
static json getExampleJson(const json &given=json::object())
Returns example JSON object for ModelDeck configuration.
Definition bcDeck.h:79
static json getExampleJson(const json &given=json::object())
Returns example JSON object for ModelDeck configuration.
static json getExampleJson(const json &given=json::object())
Returns example JSON object for ModelDeck configuration.
static json getExampleJson(const json &given=json::object())
Returns the block with the given fields set.
Definition modelDeck.h:217
static json getExampleJson(const std::string &)=delete
Returns example JSON object for ModelDeck configuration.
static json getExampleJson(const json &given=json::object())
Returns the block with the given fields set.
Definition pGenDeck.h:82
static json getExampleJson(const json &given=json::object())
Returns the block with the given fields set.
static json getParticleContactExampleJson(size_t nSets=0)
Returns example JSON object for ModelDeck configuration.
static json getParticleGeomExampleJson(std::vector< geom::GeomData > pGeomVec=std::vector< geom::GeomData >())
Returns example JSON object for ModelDeck configuration.
static json getParticleMaterialExampleJson(size_t nSets=0)
Returns example JSON object for ModelDeck configuration.
A structure to represent 3d vectors.
Definition point.h:30
std::vector< double > toVec() const
Returns coordinate in stl form.
Definition point.h:118