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 * Single-particle Mode-I open then close across a through precrack.
11 * Compares Model.Self_Contact broken_bond_kn vs reference_gap under the same
12 * prescribed kinematics (r0 ≠ Rc for bonds that crossed the crack).
13 */
14
15#include "inp/deckIncludes.h"
17#include "periDEMModel.h"
18#include "util/io.h"
19#include "util/parallelUtil.h"
20#include "util/point.h"
21
22#include <cmath>
23#include <cstdlib>
24#include <filesystem>
25#include <format>
26#include <fstream>
27#include <memory>
28#include <stdexcept>
29#include <string>
30#include <thread>
31#include <vector>
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, const std::filesystem::path &mesh_file,
45 double L, double mesh_size, double horizon, double final_time,
46 size_t num_steps, const std::string &self_contact) {
48 json{{"Dimension", 2},
49 {"Final_Time", final_time},
50 {"Time_Steps", num_steps},
51 {"Discretization_Type", json{{"Spatial", "finite_difference"}, {"Time", "central_difference"}}},
52 {"Populate_ElementNodeConnectivity", true},
53 {"Quad_Approximation_Order", 2},
54 {"Particle_Sim_Type", "Single_Particle"},
55 {"Seed", 0}});
56 model["Self_Contact"] = self_contact;
57 model["Bond_Break"] = "tension";
58
59 // Soft PD; large Gc so only the precrack is fractured.
60 const double rho = 1200.0;
61 const double K = 2.16e5;
62 const double G = 1.296e5;
63 const double Gc = 5.0e6;
64
66 json{{"File_Format", "vtu"},
67 {"Path", output_path},
68 {"Tags", std::vector<std::string>({"Displacement", "Force", "Damage_Z", "Particle_ID"})},
69 {"Output_Interval", num_steps},
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
78 const double strip = 0.2 * L;
79 json bc = json::object();
80 bc["Displacement_BC"] = {
81 {"Sets", 2},
82 {"Set_1",
83 {{"Region",
84 {{"Geometry",
85 {{"Type", "rectangle"},
86 {"Parameters", std::vector<double>{-0.05 * L, -0.05 * L, 0., strip, L + 0.05 * L, 0.}}}}}},
87 {"Direction", std::vector<size_t>{1, 2}},
88 {"Time_Function", {{"Type", "constant"}, {"Parameters", std::vector<double>{0.}}}},
89 {"Spatial_Function", {{"Type", "constant"}}},
90 {"Zero_Displacement", true}}},
91 {"Set_2",
92 {{"Region",
93 {{"Geometry",
94 {{"Type", "rectangle"},
95 {"Parameters",
96 std::vector<double>{L - strip, -0.05 * L, 0., L + 0.05 * L, L + 0.05 * L, 0.}}}}}},
97 {"Direction", std::vector<size_t>{1}},
98 {"Time_Function", {{"Type", "constant"}, {"Parameters", std::vector<double>{0.}}}},
99 {"Spatial_Function", {{"Type", "constant"}}},
100 {"Zero_Displacement", false}}}};
101
102 geom::GeomData rect;
103 rect.d_geomName = "rectangle";
104 rect.d_geomParams = {0., 0., 0., L, L, 0.};
106
107 json mesh_set = {{"File", mesh_file.string()},
108 {"CreateMesh",
109 {{"Flag", true},
110 {"Info", "gmsh_builtin_mesh"},
111 {"Mesh_Size", mesh_size},
112 {"Write_Mesh_File", true}}}};
113 json mesh = {{"Set_1", mesh_set}};
114
116 material["Set_1"] =
118 json{{"Type", "PDState"},
119 {"Is_Plane_Strain", false},
120 {"Horizon", horizon},
121 {"Density", rho},
122 {"K", K},
123 {"G", G},
124 {"Gc", Gc},
125 {"Compute_From_Classical", true},
126 {"Influence_Function", json{{"Type", 0}}}});
127
128 json root = {{"Model", model},
129 {"Output", output},
130 {"Particle", particle},
131 {"Mesh", mesh},
132 {"Material", material}};
133 root.merge_patch(bc);
134 return root;
135}
136
137size_t applyPrecrack(PeriDEMModel &dem, double x_crack) {
138 size_t n_broken = 0;
139 for (size_t i = 0; i < dem.d_neighPd.size(); ++i) {
140 const auto &xi = dem.d_xRef[i];
141 for (size_t k = 0; k < dem.d_neighPd[i].size(); ++k) {
142 const size_t j = dem.d_neighPd[i][k];
143 const auto &xj = dem.d_xRef[j];
144 if ((xi.d_x - x_crack) * (xj.d_x - x_crack) < 0.) {
145 dem.d_fracture_p->setBondState(i, k, true);
146 ++n_broken;
147 }
148 }
149 }
150 return n_broken;
151}
152
153void applyRigidOpenClose(PeriDEMModel &dem, double x_crack, double u_right) {
154 for (size_t i = 0; i < dem.d_xRef.size(); ++i) {
155 const double ux = (dem.d_xRef[i].d_x >= x_crack) ? u_right : 0.;
156 dem.d_u[i] = util::Point(ux, 0., 0.);
157 dem.d_v[i] = util::Point();
158 dem.d_x[i] = dem.d_xRef[i] + dem.d_u[i];
159 }
160}
161
163 double min_gap = 1.0e300;
164 double max_gap = 0.;
165 size_t n_pairs = 0;
166 double force_x_left = 0.;
167 double force_x_right = 0.;
168};
169
170CrackMetrics measure(PeriDEMModel &dem, double x_crack, double band) {
171 CrackMetrics m;
172 for (size_t i = 0; i < dem.d_neighPd.size(); ++i) {
173 const auto &xi = dem.d_xRef[i];
174 for (size_t k = 0; k < dem.d_neighPd[i].size(); ++k) {
175 if (!dem.d_fracture_p->getBondState(i, k))
176 continue;
177 const size_t j = dem.d_neighPd[i][k];
178 const auto &xj = dem.d_xRef[j];
179 if ((xi.d_x - x_crack) * (xj.d_x - x_crack) >= 0.)
180 continue;
181 const double gap = (dem.d_x[j] - dem.d_x[i]).length();
182 m.min_gap = std::min(m.min_gap, gap);
183 m.max_gap = std::max(m.max_gap, gap);
184 ++m.n_pairs;
185 }
186 }
187 if (m.n_pairs == 0)
188 m.min_gap = 0.;
189
190 for (size_t i = 0; i < dem.d_xRef.size(); ++i) {
191 const double x = dem.d_xRef[i].d_x;
192 const double fx = dem.d_f[i].d_x * dem.d_vol[i];
193 if (x < x_crack && x > x_crack - band)
194 m.force_x_left += fx;
195 if (x >= x_crack && x < x_crack + band)
196 m.force_x_right += fx;
197 }
198 return m;
199}
200
201struct LawResult {
202 double open_min_gap = 0.;
203 double close_min_gap = 0.;
204 double close_force_amp = 0.;
205};
206
207LawResult runLaw(const std::string &self_contact, const std::filesystem::path &run_dir, double L,
208 double mesh_size, double horizon, unsigned n_threads,
209 bool deck_only = false) {
210 namespace fs = std::filesystem;
211 const fs::path out_dir = run_dir / "out";
212 const fs::path inp_dir = run_dir / "inp";
213 fs::create_directories(out_dir);
214 fs::create_directories(inp_dir);
215
216 const fs::path mesh = inp_dir / "mesh_rect.msh";
217 // Time fields unused for prescribed kinematics; keep Output_Interval valid.
218 auto input_json =
219 buildInputJson(directoryPathWithTrailingSep(out_dir), mesh, L, mesh_size, horizon, 1.0e-6,
220 2, self_contact);
221 {
222 std::ofstream os(inp_dir / "input.json");
223 os << input_json.dump(2);
224 }
225
226 // -deckOnly stops after the deck is written. The deck comparison reads the
227 // deck and does not need this driver to run the simulation.
228 if (deck_only) {
229 util::io::print("deck written; -deckOnly, not running\n");
230 return LawResult{};
231 }
232
233 auto deck = std::make_shared<inp::Input>(input_json);
234 PeriDEMModel dem(deck);
235 dem.init();
236
237 const double x_crack = 0.5 * L;
238 const size_t n_broken = applyPrecrack(dem, x_crack);
239 if (n_broken == 0)
240 throw std::runtime_error(self_contact + ": precrack broke no bonds");
241
242 const double Rc = dem.d_maxContactR > 0. ? dem.d_maxContactR : 0.95 * mesh_size;
243 const double h = dem.d_hMax > 0. ? dem.d_hMax : mesh_size;
244 util::io::print(std::format("{}: nodes={}, broken_bonds={}, h={}, Rc={}, horizon={}\n",
245 self_contact, dem.d_x.size(), n_broken, h, Rc, horizon));
246
247 // Open: separate faces so current gap >> Rc (no self-contact yet).
248 const double u_open = 2.0 * Rc;
249 applyRigidOpenClose(dem, x_crack, u_open);
250 dem.computeForces();
251 const auto open_m = measure(dem, x_crack, 2.0 * horizon);
252 if (!(open_m.min_gap > 1.5 * Rc))
253 throw std::runtime_error(std::format("{}: open did not separate (min_gap={})", self_contact,
254 open_m.min_gap));
255
256 // Close into the window where reference_gap (natural R = r0) and
257 // broken_bond_kn (natural R = Rc) disagree: Rc < R < typical r0.
258 // Mid-horizon neighbor distances are ~O(horizon) >> Rc = 0.95 h.
259 const double u_close = -0.5 * (Rc + 0.5 * horizon);
260 applyRigidOpenClose(dem, x_crack, u_close);
261 dem.computeForces();
262 const auto close_m = measure(dem, x_crack, 2.0 * horizon);
263 const double force_amp =
264 0.5 * (std::abs(close_m.force_x_left) + std::abs(close_m.force_x_right));
265
266 util::io::print(std::format("{}: open min_gap={}, close min_gap={}, force_amp={}\n", self_contact,
267 open_m.min_gap, close_m.min_gap, force_amp));
268
269 dem.close();
270
271 LawResult r;
272 r.open_min_gap = open_m.min_gap;
273 r.close_min_gap = close_m.min_gap;
274 r.close_force_amp = force_amp;
275 return r;
276}
277
278} // namespace
279
280int main(int argc, char *argv[]) {
281 util::parallel::initMpi(argc, argv);
282 util::io::InputParser input(argc, argv);
283
284 unsigned int n_threads = 2;
285 if (input.cmdOptionExists("-nThreads"))
286 n_threads = static_cast<unsigned>(std::stoi(input.getCmdOption("-nThreads")));
288
289 namespace fs = std::filesystem;
290 fs::path base = fs::current_path() / "self_contact_mode1_run";
291 if (input.cmdOptionExists("-outputDir"))
292 base = input.getCmdOption("-outputDir");
293
294 const double L = 0.002;
295 const double mesh_size = L / 6.0;
296 const double horizon = 3.0 * mesh_size;
297
298 LawResult bb;
299 LawResult rg;
300 try {
301 const bool deck_only = input.cmdOptionExists("-deckOnly");
302 bb = runLaw("broken_bond_kn", base / "broken_bond_kn", L, mesh_size, horizon,
303 n_threads, deck_only);
304 rg = runLaw("reference_gap", base / "reference_gap", L, mesh_size, horizon,
305 n_threads, deck_only);
306 if (deck_only)
307 return EXIT_SUCCESS;
308 } catch (const std::exception &e) {
309 util::io::print(std::format("self-contact Mode-I failed: {}\n", e.what()));
310 return 1;
311 }
312
313 if (!(bb.close_force_amp > 0.) || !(rg.close_force_amp > 0.)) {
314 util::io::print(std::format(
315 "close did not activate self-contact: F_bb={}, F_rg={}\n", bb.close_force_amp,
316 rg.close_force_amp));
317 return 1;
318 }
319
320 const double fmax = std::max(bb.close_force_amp, rg.close_force_amp);
321 const double rel = std::abs(bb.close_force_amp - rg.close_force_amp) / fmax;
322 if (!(rel > 0.05)) {
323 util::io::print(std::format(
324 "self-contact laws did not differ enough under close: F_bb={}, F_rg={}, rel={}\n",
325 bb.close_force_amp, rg.close_force_amp, rel));
326 return 1;
327 }
328
329 util::io::print(std::format(
330 "self-contact Mode-I OK: open gaps bb={}/rg={}, close forces bb={}/rg={}, rel_diff={}\n", bb.open_min_gap,
331 rg.open_min_gap, bb.close_force_amp, rg.close_force_amp, rel));
332 return 0;
333}
int main(int argc, char *argv[])
Definition main.cpp:29
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::unique_ptr< geometry::Fracture > d_fracture_p
Fracture state of bonds.
Definition modelData.h:736
std::vector< std::vector< size_t > > d_neighPd
Neighbor data for peridynamic forces.
Definition modelData.h:770
double d_maxContactR
Maximum contact radius between over pairs of particles and walls.
Definition modelData.h:649
double d_hMax
Minimum mesh over all particles and walls.
Definition modelData.h:643
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
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
LawResult runLaw(const std::string &self_contact, const std::filesystem::path &run_dir, double L, double mesh_size, double horizon, unsigned n_threads, bool deck_only=false)
Definition main.cpp:207
std::string directoryPathWithTrailingSep(const std::filesystem::path &dir)
Definition main.cpp:34
json buildInputJson(const std::string &output_path, const std::filesystem::path &mesh_file, double L, double mesh_size, double horizon, double final_time, size_t num_steps, const std::string &self_contact)
Definition main.cpp:44
CrackMetrics measure(PeriDEMModel &dem, double x_crack, double band)
Definition main.cpp:170
void applyRigidOpenClose(PeriDEMModel &dem, double x_crack, double u_right)
Definition main.cpp:153
size_t applyPrecrack(PeriDEMModel &dem, double x_crack)
Definition main.cpp:137
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.
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())
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 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