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 * One grain vs a rectangle floor with Model.Wall_Contact = analytical_plane.
11 * Asserts force density vs Kn*(gap-Rc)*voli*(-n) and that contact keeps
12 * signed gap from deep wall penetration.
13 */
14
15#include "contact/contact.h"
16#include "geom/geomIncludes.h"
17#include "inp/deckIncludes.h"
19#include "periDEMModel.h"
20#include "util/io.h"
21#include "util/parallelUtil.h"
22#include "util/point.h"
23
24#include <cmath>
25#include <cstdlib>
26#include <filesystem>
27#include <format>
28#include <fstream>
29#include <memory>
30#include <stdexcept>
31#include <string>
32
33namespace {
34
35std::string directoryPathWithTrailingSep(const std::filesystem::path &dir) {
36 namespace fs = std::filesystem;
37 fs::path n = fs::absolute(dir).lexically_normal();
38 std::string s = n.string();
39 if (!s.empty() && s.back() != '/' && s.back() != '\\')
40 s += fs::path::preferred_separator;
41 return s;
42}
43
44json buildInputJson(const std::string &output_path,
45 const std::filesystem::path &mesh_cir,
46 const std::filesystem::path &mesh_wall, double R,
47 double mesh_size, double horizon, double Rc_factor,
48 double Kn, double final_time, size_t num_steps,
49 bool policy_combo = false) {
51 json{{"Dimension", 2},
52 {"Final_Time", final_time},
53 {"Time_Steps", num_steps},
54 {"Discretization_Type", json{{"Spatial", "finite_difference"}, {"Time", "central_difference"}}},
55 {"Populate_ElementNodeConnectivity", true},
56 {"Quad_Approximation_Order", 2},
57 {"Particle_Sim_Type", "Multi_Particle"},
58 {"Seed", 0}});
59 model["Wall_Contact"] = "analytical_plane";
60 if (policy_combo) {
61 model["Self_Contact"] = "reference_gap";
62 model["Bond_Break"] = "absolute_stretch";
63 }
64
66 json{{"File_Format", "vtu"},
67 {"Path", output_path},
68 {"Tags", std::vector<std::string>({"Displacement", "Force", "Particle_ID"})},
69 {"Output_Interval", std::max<size_t>(1, num_steps / 5)},
70 {"Debug", 1},
71 {"Perform_FE_Out", false},
72 {"Compress_Type", "zlib"},
73 {"Perform_Out", false},
74 {"Test_Output_Interval", num_steps},
75 {"Tag_PP", ""},
76 {"PVD_Collection", false}});
77
79 json{{"Displacement_BC_Sets", 1}});
80 bc["Displacement_BC"]["Set_1"] = json{
81 {"Particle_List", std::vector<size_t>{1}},
82 {"Direction", std::vector<size_t>{1, 2}},
83 {"Time_Function", {{"Type", "constant"}, {"Parameters", std::vector<double>{0.}}}},
84 {"Spatial_Function", {{"Type", "constant"}}},
85 {"Zero_Displacement", true}};
86
87 const double wall_half = 3.0 * R;
88 const double wall_thick = mesh_size;
89 geom::GeomData grain;
90 grain.d_geomName = "circle";
91 grain.d_geomParams = {R, 0., 0., 0.};
92 geom::GeomData floor;
93 floor.d_geomName = "rectangle";
94 floor.d_geomParams = {-wall_half, -wall_thick, 0., wall_half, 0., 0.};
95
97
98 auto mesh_set = [&](const std::filesystem::path &f) {
99 return json{{"File", f.string()},
100 {"CreateMesh",
101 {{"Flag", true},
102 {"Info", "gmsh_builtin_mesh"},
103 {"Mesh_Size", mesh_size},
104 {"Write_Mesh_File", true}}}};
105 };
106 json mesh = {{"Sets", 2},
107 {"Set_1", mesh_set(mesh_cir)},
108 {"Set_2", mesh_set(mesh_wall)}};
109
110 const double rho = 1200.0;
111 const double K = 2.16e7;
112 const double G = 1.296e7;
113 const double Gc = 50.0;
115 material["Set_1"] =
117 json{{"Type", "PDState"},
118 {"Is_Plane_Strain", false},
119 {"Horizon", horizon},
120 {"Density", rho},
121 {"K", K},
122 {"G", G},
123 {"Gc", Gc},
124 {"Compute_From_Classical", true},
125 {"Influence_Function", json{{"Type", 0}}}});
126 material["Set_2"] =
128 json{{"Type", "PDState"},
129 {"Is_Plane_Strain", false},
130 {"Horizon", horizon},
131 {"Density", rho},
132 {"K", K},
133 {"G", G},
134 {"Gc", Gc},
135 {"Compute_From_Classical", true},
136 {"Influence_Function", json{{"Type", 0}}}});
137
139 json{{"Contact_Radius_Factor", Rc_factor},
140 {"Kn", Kn},
141 {"Damping_On", false},
142 {"Epsilon", 1.0},
143 {"Friction_On", false},
144 {"Friction_Coeff", 0.0},
145 {"Kn_Factor", 1.0},
146 {"Beta_n_Factor", 100.0},
147 {"K", K}});
148 // Soften spring for a short stable settle (formula check uses this Kn too).
149 contact_base["Kn"] = Kn;
150 contact_base["Kn_Factor"] = 1.0;
152 contact["Set_1_1"] = contact_base;
153 contact["Set_1_2"] = contact_base;
154 contact["Set_2_2"] = contact_base;
155 contact["Damping_Law"] = policy_combo ? "node" : "off";
156 contact["Friction_Law"] = policy_combo ? "stick_slip" : "coulomb_simple";
157
158 // Bottom of grain near the floor (y=0): slight overlap into Rc after setup.
159 const double cy = R + 0.25 * mesh_size;
160 auto pgen = inp::PGenDeck::getExampleJson(json{{"Method", "From_File"}});
161 pgen["Random_Rotation"] = false;
162 pgen["Data"]["N"] = 2;
163 pgen["Data"]["0"] = json{{"x", 0.},
164 {"y", cy},
165 {"z", 0.},
166 {"theta", 0.},
167 {"s", 1.},
168 {"geom_id", 0},
169 {"mat_id", 0},
170 {"contact_id", 0}};
171 pgen["Data"]["1"] = json{{"x", 0.},
172 {"y", -0.5 * wall_thick},
173 {"z", 0.},
174 {"theta", 0.},
175 {"s", 1.},
176 {"geom_id", 1},
177 {"mat_id", 1},
178 {"contact_id", 1},
179 {"is_wall", true}};
180
181 return json{{"Model", model},
182 {"Output", output},
183 {"Displacement_BC", bc["Displacement_BC"]},
184 {"Particle", particle},
185 {"Mesh", mesh},
186 {"Material", material},
187 {"Contact", contact},
188 {"Neighbor",
190 json{{"Update_Criteria", "simple_all"},
191 {"Search_Factor", 5.0},
192 {"Search_Interval", 1},
193 {"Near_Bd_Nodes_Tol", 0.5}})},
194 {"Particle_Generation", pgen}};
195}
196
197struct Probe {
198 size_t n_contact_nodes = 0;
199 double min_gap = 1.e300;
200 double max_force_err = 0.;
201 double max_force_ref = 0.;
202};
203
205 Probe p;
206 if (!dem.d_contact_p || !dem.d_contact_p->d_wallContact ||
207 !dem.d_contact_p->d_pairForce)
208 throw std::runtime_error("analytical wall: contact path not set up");
209
210 const auto &contact = dem.d_particleDeck_p->d_contactDeck.getContact(0, 1);
211 const double Kn = contact.d_Kn;
212 const double Rc = contact.d_contactR;
213
214 for (auto &f : dem.d_f)
215 f = util::Point();
216 dem.d_contact_p->d_wallContact->apply(dem, dem.d_contact_p->d_pairForce.get(),
217 /*use_node_damping*/ false);
218
219 for (size_t i = 0; i < dem.d_x.size(); ++i) {
220 if (dem.d_ptId[i] != 0)
221 continue;
223 if (!wall_geom.wallContactQuery(dem.d_x[i], hit) || !hit.active)
224 continue;
225 if (!(hit.signed_gap < Rc))
226 continue;
227
228 ++p.n_contact_nodes;
229 p.min_gap = std::min(p.min_gap, hit.signed_gap);
230
231 auto scalar = Kn * (hit.signed_gap - Rc) * dem.d_vol[i];
232 if (scalar > 0.)
233 scalar = 0.;
234 const util::Point f_ref = scalar * (-1. * hit.outward_n);
235 const double err = (dem.d_f[i] - f_ref).length();
236 p.max_force_err = std::max(p.max_force_err, err);
237 p.max_force_ref = std::max(p.max_force_ref, f_ref.length());
238 }
239 if (p.n_contact_nodes == 0)
240 p.min_gap = 0.;
241 return p;
242}
243
245 double ming = 1.e300;
246 size_t n = 0;
247 for (size_t i = 0; i < dem.d_x.size(); ++i) {
248 if (dem.d_ptId[i] != 0)
249 continue;
251 if (!wall_geom.wallContactQuery(dem.d_x[i], hit) || !hit.active)
252 continue;
253 ming = std::min(ming, hit.signed_gap);
254 ++n;
255 }
256 return n ? ming : 0.;
257}
258
259} // namespace
260
261int main(int argc, char *argv[]) {
262 util::parallel::initMpi(argc, argv);
263 util::io::InputParser input(argc, argv);
264
265 unsigned n_threads = 2;
266 if (input.cmdOptionExists("-nThreads"))
267 n_threads = static_cast<unsigned>(std::stoi(input.getCmdOption("-nThreads")));
269
270 namespace fs = std::filesystem;
271 fs::path base = fs::current_path() / "wall_analytical_run";
272 if (input.cmdOptionExists("-outputDir"))
273 base = input.getCmdOption("-outputDir");
274 const fs::path out_dir = base / "out";
275 const fs::path inp_dir = base / "inp";
276 fs::create_directories(out_dir);
277 fs::create_directories(inp_dir);
278
279 const double R = 0.001;
280 const double mesh_size = R / 5.0;
281 const double horizon = 3.0 * mesh_size;
282 const double Rc_factor = 0.95;
283 const double Kn = 1.0e11;
284 const double final_time = 5.0e-4;
285 const size_t num_steps = 5000;
286
287 const bool policy_combo = input.cmdOptionExists("-policyCombo");
288 auto input_json =
289 buildInputJson(directoryPathWithTrailingSep(out_dir), inp_dir / "mesh_cir.msh",
290 inp_dir / "mesh_wall.msh", R, mesh_size, horizon, Rc_factor, Kn,
291 final_time, num_steps, policy_combo);
292 {
293 std::ofstream os(inp_dir / "input.json");
294 os << input_json.dump(2);
295 }
296
297 // -deckOnly stops after the deck is written. The deck comparison reads the
298 // deck and does not need this driver to run the simulation.
299 if (input.cmdOptionExists("-deckOnly")) {
300 util::io::print("deck written; -deckOnly, not running\n");
302 return 0;
303 }
304
305 auto deck = std::make_shared<inp::Input>(input_json);
306 PeriDEMModel dem(deck);
307 dem.init();
308
309 if (dem.d_modelDeck_p->d_wallContact != "analytical_plane")
310 throw std::runtime_error("analytical wall: Wall_Contact not analytical_plane");
311 if (policy_combo) {
312 if (dem.d_modelDeck_p->d_selfContact != "reference_gap")
313 throw std::runtime_error("alternate deck: Self_Contact is not reference_gap");
314 if (dem.d_modelDeck_p->d_bondBreak != "absolute_stretch")
315 throw std::runtime_error("alternate deck: Bond_Break is not absolute_stretch");
316 if (dem.d_particleDeck_p->d_contactDeck.d_frictionLaw != "stick_slip")
317 throw std::runtime_error("alternate deck: Friction_Law is not stick_slip");
318 if (dem.d_particleDeck_p->d_contactDeck.d_dampingLaw != "node")
319 throw std::runtime_error("alternate deck: Damping_Law is not node");
320 if (!dem.d_contact_p || !dem.d_contact_p->d_pairForce ||
321 dem.d_contact_p->d_useNodeDamping != true)
322 throw std::runtime_error("alternate deck: node damping not active on contact");
323 util::io::print("alternate deck fields applied\n");
324 }
325 if (!dem.d_contact_p || !dem.d_contact_p->d_wallContact ||
326 !dem.d_contact_p->d_wallContact->skipsMeshedGrainWall())
327 throw std::runtime_error("analytical wall: must skip meshed grain-wall");
328
329 const double Rc = dem.d_particleDeck_p->d_contactDeck.getContact(0, 1).d_contactR;
330 // Floor top face is at y=0 (matches rectangle placement in the deck).
331 geom::Plane floor_plane(util::Point(0., 1., 0.), util::Point(0., 0., 0.));
332
333 // Wall spring-only force density vs closed form.
334 {
335 const double push = 0.5 * Rc;
336 for (size_t i = 0; i < dem.d_x.size(); ++i) {
337 if (dem.d_ptId[i] != 0)
338 continue;
339 dem.d_u[i].d_y -= push;
340 dem.d_x[i] = dem.d_xRef[i] + dem.d_u[i];
341 dem.d_v[i] = util::Point();
342 }
343 const auto frozen = checkWallSpringOnly(dem, floor_plane);
344 util::io::print(std::format(
345 "analytical wall formula: n_contact={}, min_gap={}, Rc={}, max|f_ref|={}, max|f-f_ref|={}\n",
346 frozen.n_contact_nodes, frozen.min_gap, Rc, frozen.max_force_ref,
347 frozen.max_force_err));
348 if (frozen.n_contact_nodes == 0)
349 throw std::runtime_error("analytical wall: no grain nodes in contact");
350 if (!(frozen.max_force_ref > 0.))
351 throw std::runtime_error("analytical wall: expected nonzero force");
352 if (!(frozen.max_force_err <= 1.e-6 * (1. + frozen.max_force_ref)))
353 throw std::runtime_error(std::format(
354 "analytical wall: force density mismatch vs Kn*(gap-Rc)*voli*(-n): err={}",
355 frozen.max_force_err));
356 if (!(frozen.min_gap > -Rc))
357 throw std::runtime_error(std::format(
358 "analytical wall: deep penetration past Rc in contact config: min_gap={}, Rc={}",
359 frozen.min_gap, Rc));
360 }
361
362 // Full contact assembly (PD + analytical wall) must keep the same gap bound.
363 dem.computeForces();
364 const double ming = minGrainWallGap(dem, floor_plane);
365 util::io::print(std::format("analytical wall after computeForces: min_gap={}, Rc={}\n", ming, Rc));
366 if (!(ming < Rc))
367 throw std::runtime_error(
368 "analytical wall: expected grain-wall contact (min_gap < Rc)");
369 if (!(ming > -Rc))
370 throw std::runtime_error(std::format(
371 "analytical wall: deep penetration past Rc: min_gap={}, Rc={}", ming, Rc));
372
373 dem.close();
374 util::io::print("analytical wall OK\n");
375 return 0;
376}
int main(int argc, char *argv[])
Definition main.cpp:29
std::unique_ptr< contact::Contact > d_contact_p
std::vector< util::Point > d_x
Current positions of the nodes.
Definition modelData.h:745
std::vector< util::Point > d_xRef
reference positions of the nodes
Definition modelData.h:742
std::vector< util::Point > d_u
Displacement of the nodes.
Definition modelData.h:748
std::vector< util::Point > d_f
Total force on the nodes.
Definition modelData.h:757
std::shared_ptr< inp::ModelDeck > d_modelDeck_p
Pointer to deck holding problem related data.
Definition modelData.h:622
std::vector< size_t > d_ptId
Global node to particle id (walls are assigned id after last particle id)
Definition modelData.h:764
std::shared_ptr< inp::ParticleDeck > d_particleDeck_p
Pointer to deck holding particle related data.
Definition modelData.h:637
std::vector< double > d_vol
Nodal volumes.
Definition modelData.h:760
std::vector< util::Point > d_v
Velocity of the nodes.
Definition modelData.h:751
Defines abstract geometrical domain.
virtual bool wallContactQuery(const util::Point &x, WallContactHit &hit) const
Closest-point / signed-gap query for analytical wall contact. Default: unsupported (returns false,...
Infinite plane: free space is the half-space in the normal direction.
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
Probe checkWallSpringOnly(PeriDEMModel &dem, geom::GeomObject &wall_geom)
Definition main.cpp:204
double minGrainWallGap(PeriDEMModel &dem, geom::GeomObject &wall_geom)
Definition main.cpp:244
json buildInputJson(const std::string &output_path, const std::filesystem::path &mesh_cir, const std::filesystem::path &mesh_wall, double R, double mesh_size, double horizon, double Rc_factor, double Kn, double final_time, size_t num_steps, bool policy_combo=false)
Definition main.cpp:44
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
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.
void finalizeMpi()
Call MPI_Finalize if this process initialized MPI.
Input data for geometrical objects.
std::vector< double > d_geomParams
Zone parameters.
std::string d_geomName
Zone type.
Result of a wall-contact query against a geom (analytical walls).
util::Point outward_n
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
double length() const
Computes the Euclidean length of the vector.
Definition point.h:124