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 * Two-particle regression test: one deck stack for every geometry; only geom name,
8 * model dimension (2 vs 3), and particle placement differ.
9 *
10 * Reference meshes are always defined at the origin (same canonical shape for mesh
11 * groups 0 and 1). Particle positions are set only in Particle_Generation — do not
12 * bake translation into the second particle's geomParams (that broke the general
13 * path vs twop_circ_inbuilt).
14 *
15 * Layout (run_base defaults to the current working directory):
16 * <run_base>/<geom_name>/inp/ — input.json, meshes
17 * <run_base>/<geom_name>/out/ — VTU, log.txt (Output.Path must end with a separator)
18 *
19 * -outputDir <path> optional parent for all runs (each geometry still gets
20 * its own <geom>/inp and <geom>/out under this path).
21 * -inputDir <path> optional parent for inp trees only (default: same as -outputDir base).
22 * -allGeometries run every geometry in geom::acceptable_geometries (overrides -geometry).
23 * -geometry <name> run a single geometry (must be in acceptable_geometries). If omitted,
24 * default is circle (unless -allGeometries is set).
25 */
26
28#include "inp/deckIncludes.h"
29#include "util/io.h"
30#include "util/function.h"
32#include "periDEMModel.h"
33#include <cmath>
34#include <filesystem>
35#include <format>
36#include <fstream>
37#include <stdexcept>
38#include <thread>
39#include <vector>
40
41namespace {
42
43std::string directoryPathWithTrailingSep(const std::filesystem::path &dir) {
44 namespace fs = std::filesystem;
45 fs::path n = fs::absolute(dir).lexically_normal();
46 std::string s = n.string();
47 if (!s.empty() && s.back() != '/' && s.back() != '\\')
48 s += fs::path::preferred_separator;
49 return s;
50}
51
52bool is3DGeometry(const std::string &g) {
53 return g == "sphere" || g == "ellipsoid" || g == "cube" || g == "cuboid" || g == "cylinder" ||
54 g == "sphere_minus_sphere" || g == "cuboid_minus_cuboid" || g == "open_cuboid_channel_3d";
55}
56
57bool isAcceptableGeometryName(const std::string &g) {
58 for (const std::string &n : geom::getAcceptableGeometries()) {
59 if (n == g)
60 return true;
61 }
62 return false;
63}
64
65json buildInputJson(const std::string &geomName, const std::string &output_path_for_deck,
66 const std::filesystem::path &mesh_file_1,
67 const std::filesystem::path &mesh_file_2) {
68
69 const double s = 0.001;
70 const double mesh_size = s / 5.0;
71 const double horizon = 3.0 * mesh_size;
72 const double particle_dist = 0.001;
73
74 const double poisson1 = 0.25;
75 const double rho1 = 1200.0;
76 const double K1 = 2.16e+7;
77 const double E1 = material::toE(K1, poisson1);
78 const double G1 = material::toGE(E1, poisson1);
79 const double Gc1 = 50.0;
80
81 const double poisson2 = 0.25;
82 const double rho2 = 1200.0;
83 const double K2 = 2.16e+7;
84 const double E2 = material::toE(K2, poisson2);
85 const double G2 = material::toGE(E2, poisson2);
86 const double Gc2 = 50.0;
87
88 const double R_contact_factor = 0.95;
89 const double Kn_11 = util::normalContactStiffness(K1, K1, horizon);
90 const double Kn_22 = util::normalContactStiffness(K2, K2, horizon);
91 const double Kn_12 = util::normalContactStiffness(K1, K2, horizon);
92 const double beta_n_eps = 0.9;
93 const double friction_coeff = 0.5;
94 const double beta_n_factor = 100.0;
95
96 const int model_dim = is3DGeometry(geomName) ? 3 : 2;
97
98 /* Reference shapes at origin for both mesh groups (same as twop_circ_inbuilt). */
99 const util::Point origin(0.0, 0.0, 0.0);
100 const std::vector<double> gp_ref = geom::exampleGeomParams(geomName, origin, s);
101
102 /* Same integration window and IC gap as twop_circ_inbuilt (stable dt). */
103 const double final_time = 0.0001; // 0.012;
104 const size_t num_steps = 1000; //36000;
105 const size_t dt_out_n = num_steps / 4; // 10;
106
107 auto modelDeckJson = inp::ModelDeck::getExampleJson(
108 json{{"Dimension", static_cast<size_t>(model_dim)},
109 {"Final_Time", final_time},
110 {"Time_Steps", num_steps},
111 {"Discretization_Type", json{{"Spatial", "finite_difference"}, {"Time", "central_difference"}}},
112 {"Populate_ElementNodeConnectivity", true},
113 {"Quad_Approximation_Order", 2},
114 {"Particle_Sim_Type", "Multi_Particle"},
115 {"Seed", 0}});
116
117 auto outputDeckJson = inp::OutputDeck::getExampleJson(
118 json{{"File_Format", "vtu"},
119 {"Path", output_path_for_deck},
120 {"Tags", std::vector<std::string>({"Displacement", "Velocity", "Force", "Damage_Z", "Damage", "Particle_ID"})},
121 {"Output_Interval", dt_out_n},
122 {"Debug", 2},
123 {"Perform_FE_Out", true},
124 {"Compress_Type", "zlib"},
125 {"Perform_Out", true},
126 {"Test_Output_Interval", 1},
127 {"Tag_PP", ""},
128 {"PVD_Collection", true}});
129
130 auto bcDeckJson = inp::BCDeck::getExampleJson(
131 json{{"Displacement_BC_Sets", 1},
132 {"IC_Sets", 1},
133 {"Gravity", util::Point(0, -10, 0).toVec()}});
134
135 bcDeckJson["Displacement_BC"]["Set_1"] = inp::BCBaseDeck::getExampleJson(
136 json{{"Type", "Displacement_BC"},
137 {"Particle_List", {0}},
138 {"Direction", {1, 2}},
139 {"Zero_Displacement", true}});
140
141 const double free_fall_dist = particle_dist - horizon;
142 const double free_fall_vel = -std::sqrt(2.0 * std::abs(-10.0) * std::max(1e-30, free_fall_dist));
143 bcDeckJson["IC"]["Set_1"] = inp::BCBaseDeck::getExampleJson(
144 json{{"Type", "IC"},
145 {"Particle_List", {1}},
146 {"IC_Type", "Constant_Velocity"},
147 {"IC_Vector", {0.0, free_fall_vel, 0.0}}});
148
149 auto pDeckJson = json({});
150
151 std::vector<geom::GeomData> pGeomVec(2);
152 pGeomVec[0].d_geomName = geomName;
153 pGeomVec[0].d_geomParams = gp_ref;
154 pGeomVec[1].d_geomName = geomName;
155 pGeomVec[1].d_geomParams = gp_ref;
156
157 pDeckJson["Particle"] = inp::ParticleDeck::getParticleGeomExampleJson(pGeomVec);
158
159 const std::string f1 = mesh_file_1.string();
160 const std::string f2 = mesh_file_2.string();
161 auto meshSet1 = json({{"File", f1},
162 {"CreateMesh",
163 {{"Flag", true},
164 {"Info", "gmsh_builtin_mesh"},
165 {"Mesh_Size", mesh_size},
166 {"Write_Mesh_File", true}}}});
167 auto meshSet2 = json({{"File", f2},
168 {"CreateMesh",
169 {{"Flag", true},
170 {"Info", "gmsh_builtin_mesh"},
171 {"Mesh_Size", mesh_size},
172 {"Write_Mesh_File", true}}}});
173 pDeckJson["Mesh"] = json({{"Sets", 2}, {"Set_1", meshSet1}, {"Set_2", meshSet2}});
174
176 pMatJson["Set_1"] = inp::MaterialDeck::getExampleJson(
177 json{{"Type", "PDState"},
178 {"Is_Plane_Strain", false},
179 {"Horizon", horizon},
180 {"Density", rho1},
181 {"K", K1},
182 {"G", G1},
183 {"Gc", Gc1},
184 {"Compute_From_Classical", true},
185 {"Influence_Function", json{{"Type", 0}}}});
186 pMatJson["Set_2"] = inp::MaterialDeck::getExampleJson(
187 json{{"Type", "PDState"},
188 {"Is_Plane_Strain", false},
189 {"Horizon", horizon},
190 {"Density", rho2},
191 {"K", K2},
192 {"G", G2},
193 {"Gc", Gc2},
194 {"Compute_From_Classical", true},
195 {"Influence_Function", json{{"Type", 0}}}});
196 pDeckJson["Material"] = pMatJson;
197
200 json{{"Contact_Radius_Factor", R_contact_factor},
201 {"Kn", Kn_11},
202 {"Damping_On", false},
203 {"Epsilon", beta_n_eps},
204 {"Friction_On", false},
205 {"Friction_Coeff", friction_coeff},
206 {"Kn_Factor", 1.0},
207 {"Beta_n_Factor", beta_n_factor},
208 {"K", 0.0}});
209
210 pContactJson["Set_1_1"] = contact_base;
211 pContactJson["Set_1_1"]["Kn"] = Kn_11;
212 pContactJson["Set_1_2"] = contact_base;
213 pContactJson["Set_1_2"]["Kn"] = Kn_12;
214 pContactJson["Set_2_2"] = contact_base;
215 pContactJson["Set_2_2"]["Kn"] = Kn_22;
216 pDeckJson["Contact"] = pContactJson;
217
218 pDeckJson["Neighbor"] = inp::PNeighborDeck::getExampleJson(
219 json{{"Update_Criteria", "simple_all"},
220 {"Search_Factor", 10.0},
221 {"Search_Interval", 40},
222 {"Near_Bd_Nodes_Tol", 0.5}});
223
224 auto pGenJson = inp::PGenDeck::getExampleJson(json{{"Method", "From_File"}});
225 pGenJson["Data"]["N"] = 2;
226
227 /*
228 * Placement matches twop_circ: lower particle corner at (s,s[,s]); upper at
229 * (s, 2*s + particle_dist + s, [s]) so vertical gap is particle_dist between
230 * outer extents (same as R1=R2=s in twop_circ).
231 */
232 if (model_dim == 2) {
233 pGenJson["Data"]["0"] = {
234 {"x", s}, {"y", s}, {"z", 0.0},
235 {"theta", 0.0}, {"s", 1.0},
236 {"ax", 0.0}, {"ay", 0.0}, {"az", 1.0},
237 {"geom_id", 0}, {"mat_id", 0}, {"contact_id", 0}
238 };
239 pGenJson["Data"]["1"] = {
240 {"x", s}, {"y", 2.0 * s + particle_dist + s}, {"z", 0.0},
241 {"theta", M_PI}, {"s", 1.0},
242 {"ax", 0.0}, {"ay", 0.0}, {"az", 1.0},
243 {"geom_id", 1}, {"mat_id", 1}, {"contact_id", 1}
244 };
245 } else {
246 pGenJson["Data"]["0"] = {
247 {"x", s}, {"y", s}, {"z", s},
248 {"theta", 0.0}, {"s", 1.0},
249 {"ax", 0.0}, {"ay", 0.0}, {"az", 1.0},
250 {"geom_id", 0}, {"mat_id", 0}, {"contact_id", 0}
251 };
252 pGenJson["Data"]["1"] = {
253 {"x", s}, {"y", 2.0 * s + particle_dist + s}, {"z", s},
254 {"theta", M_PI}, {"s", 1.0},
255 {"ax", 0.0}, {"ay", 0.0}, {"az", 1.0},
256 {"geom_id", 1}, {"mat_id", 1}, {"contact_id", 1}
257 };
258 }
259 pDeckJson["Particle_Generation"] = pGenJson;
260
261 return json({{"Model", modelDeckJson},
262 {"Output", outputDeckJson},
263 {"Force_BC", bcDeckJson["Force_BC"]},
264 {"Displacement_BC", bcDeckJson["Displacement_BC"]},
265 {"IC", bcDeckJson["IC"]},
266 {"Particle", pDeckJson["Particle"]},
267 {"Mesh", pDeckJson["Mesh"]},
268 {"Material", pDeckJson["Material"]},
269 {"Contact", pDeckJson["Contact"]},
270 {"Neighbor", pDeckJson["Neighbor"]},
271 {"Particle_Generation", pDeckJson["Particle_Generation"]}});
272}
273
274} // namespace
275
276int main(int argc, char *argv[]) {
277
278 util::parallel::initMpi(argc, argv);
279 int mpiSize = util::parallel::mpiSize(), mpiRank = util::parallel::mpiRank();
280 util::io::print(std::format("Initialized MPI. MPI size = {}, MPI rank = {}\n", mpiSize, mpiRank));
282
283 util::io::InputParser input(argc, argv);
284
285 unsigned int nThreads;
286 if (input.cmdOptionExists("-nThreads"))
287 nThreads = std::stoi(input.getCmdOption("-nThreads"));
288 else {
289 nThreads = std::thread::hardware_concurrency();
290 util::io::print(std::format("Running test with default number of threads = {}\n", nThreads));
291 }
293 util::io::print(std::format("Number of threads = {}\n", util::parallel::getNThreads()));
294
295 namespace fs = std::filesystem;
296 const fs::path cwd = fs::current_path();
297
298 fs::path run_base = cwd;
299 if (input.cmdOptionExists("-outputDir")) {
300 fs::path p = input.getCmdOption("-outputDir");
301 run_base = p.is_absolute() ? std::move(p) : cwd / p;
302 }
303
304 fs::path inp_base = run_base;
305 if (input.cmdOptionExists("-inputDir")) {
306 fs::path p = input.getCmdOption("-inputDir");
307 inp_base = p.is_absolute() ? std::move(p) : cwd / p;
308 }
309
310 std::vector<std::string> geometriesToRun;
311 if (input.cmdOptionExists("-allGeometries")) {
312 geometriesToRun = geom::getAcceptableGeometries();
313 util::io::print("twop_general_inbuilt: -allGeometries — running full geometry list.\n");
314 } else if (input.cmdOptionExists("-geometry")) {
315 const std::string g = input.getCmdOption("-geometry");
317 throw std::runtime_error(
318 "twop_general_inbuilt: -geometry \"" + g +
319 "\" is not in geom::acceptable_geometries.");
320 geometriesToRun = {g};
321 util::io::print(std::format("twop_general_inbuilt: single geometry = {}.\n", g));
322 } else {
323 geometriesToRun = {"circle"};
324 util::io::print("twop_general_inbuilt: default geometry = circle (use -geometry <name> or -allGeometries).\n");
325 }
326
327 for (const std::string &geomName : geometriesToRun) {
328 util::io::print(std::format("--- twop_general_inbuilt: geometry = {} ---\n", geomName));
329
330 const fs::path out_dir = run_base / geomName / "out";
331 const fs::path inp_dir = inp_base / geomName / "inp";
332 fs::create_directories(out_dir);
333 fs::create_directories(inp_dir);
334
335 const std::string output_path_for_deck = directoryPathWithTrailingSep(out_dir);
336
337 const fs::path mesh1 = inp_dir / (std::string("mesh_gen_1_") + geomName + ".msh");
338 const fs::path mesh2 = inp_dir / (std::string("mesh_gen_2_") + geomName + ".msh");
339
340 auto inputJson = buildInputJson(geomName, output_path_for_deck, mesh1, mesh2);
341
342 const fs::path input_json_path = inp_dir / (std::string("input_") + geomName + ".json");
343 {
344 std::ofstream os(input_json_path);
345 if (!os)
346 throw std::runtime_error("Failed to open " + input_json_path.string() + " for writing.");
347 os << inputJson.dump(2);
348 }
349 util::io::print(std::format("Output directory (VTU, log.txt): {}\n", fs::absolute(out_dir).string()));
350 util::io::print(std::format("Input directory (input.json, meshes): {}\n", fs::absolute(inp_dir).string()));
351 util::io::print(std::format("Wrote deck to {}\n", fs::absolute(input_json_path).string()));
352
353 // -deckOnly stops after the deck is written. The deck comparison reads the
354 // deck and does not need this driver to run the simulation. Each geometry
355 // writes its own deck, so this continues rather than returns.
356 if (input.cmdOptionExists("-deckOnly"))
357 continue;
358
359 auto deck = std::make_shared<inp::Input>(inputJson);
360
361 PeriDEMModel dem(deck);
362 dem.run(deck);
363 }
364
365 return EXIT_SUCCESS;
366}
int main(int argc, char *argv[])
Definition main.cpp:29
void run(std::shared_ptr< inp::Input > &deck)
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 is3DGeometry(const std::string &g)
Definition main.cpp:52
json buildInputJson(const std::string &geomName, const std::string &output_path_for_deck, const std::filesystem::path &mesh_file_1, const std::filesystem::path &mesh_file_2)
Definition main.cpp:65
bool isAcceptableGeometryName(const std::string &g)
Definition main.cpp:57
const std::vector< std::string > & getAcceptableGeometries()
Returns list of acceptable geometries for PeriDEM simulation.
std::vector< double > exampleGeomParams(const std::string &geom_type, const util::Point &c, double s)
Canonical parameter vector for examples, mesh generation tests, and demos.
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.
void print(const T &msg, int nt=print_default_tab, int printMpiRank=print_default_mpi_rank)
Prints formatted information.
Definition io.h:171
unsigned int getNThreads()
Get number of threads to be used by taskflow.
void initNThreads(unsigned int nThreads=std::thread::hardware_concurrency())
Initializes MpiStatus struct.
const MpiStatus * getMpiStatus()
Returns pointer to 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
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
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