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 * Notched-plate impact driver (Silling / Trask / Bhat KW setups).
11 * Checks: prenotch bonds, tip damage growth, no blow-up.
12 * Paper-angle validation lives outside ctest.
13 * Flags: -quick, -traskDisp, -bhatKW, -dim3, -vImpact, -Gc, -meshSize,
14 * -finalTime, -outputDir, -nThreads, -selfContact, -bondBreak.
15 */
16
17#include "inp/deckIncludes.h"
19#include "periDEMModel.h"
20#include "time_int/integrator.h"
21#include "util/function.h"
22#include "util/io.h"
23#include "util/parallelUtil.h"
24#include "util/point.h"
25
26#include <cmath>
27#include <cstdlib>
28#include <algorithm>
29#include <filesystem>
30#include <format>
31#include <fstream>
32#include <memory>
33#include <stdexcept>
34#include <string>
35#include <vector>
36
37namespace {
38
39std::string directoryPathWithTrailingSep(const std::filesystem::path &dir) {
40 namespace fs = std::filesystem;
41 fs::path n = fs::absolute(dir).lexically_normal();
42 std::string s = n.string();
43 if (!s.empty() && s.back() != '/' && s.back() != '\\')
44 s += fs::path::preferred_separator;
45 return s;
46}
47
48bool segmentCrossesVertical(double x0, double y0, double x1, double y1, double x_line,
49 double y_lo, double y_hi) {
50 if ((x0 - x_line) * (x1 - x_line) >= 0.)
51 return false;
52 const double t = (x_line - x0) / (x1 - x0);
53 if (t < 0. || t > 1.)
54 return false;
55 const double y = y0 + t * (y1 - y0);
56 return y >= y_lo && y <= y_hi;
57}
58
59size_t applyNotches(PeriDEMModel &dem, double x_left, double x_right, double y_lo,
60 double y_hi) {
61 size_t n_broken = 0;
62 for (size_t i = 0; i < dem.d_neighPd.size(); ++i) {
63 if (dem.d_ptId[i] != 0)
64 continue;
65 const auto &xi = dem.d_xRef[i];
66 for (size_t k = 0; k < dem.d_neighPd[i].size(); ++k) {
67 const size_t j = dem.d_neighPd[i][k];
68 if (dem.d_ptId[j] != 0)
69 continue;
70 const auto &xj = dem.d_xRef[j];
71 if (segmentCrossesVertical(xi.d_x, xi.d_y, xj.d_x, xj.d_y, x_left, y_lo, y_hi) ||
72 segmentCrossesVertical(xi.d_x, xi.d_y, xj.d_x, xj.d_y, x_right, y_lo, y_hi)) {
73 dem.d_fracture_p->setBondState(i, k, true);
74 ++n_broken;
75 }
76 }
77 }
78 return n_broken;
79}
80
81// Break bonds that still span an open rectangular notch slot (δ > notch width).
82size_t applyNotchSlots(PeriDEMModel &dem, double notch_half, double notch_w, double y_lo,
83 double y_hi) {
84 const double hw = 0.5 * notch_w;
85 auto crossesSlot = [&](double x0, double y0, double x1, double y1, double xc) {
86 const double xa = xc - hw, xb = xc + hw;
87 auto inSlot = [&](double x, double y) {
88 return x >= xa && x <= xb && y >= y_lo && y <= y_hi;
89 };
90 if (inSlot(x0, y0) || inSlot(x1, y1))
91 return true;
92 for (int s = 0; s <= 8; ++s) {
93 const double t = s / 8.0;
94 const double x = x0 + t * (x1 - x0);
95 const double y = y0 + t * (y1 - y0);
96 if (inSlot(x, y))
97 return true;
98 }
99 return segmentCrossesVertical(x0, y0, x1, y1, xc, y_lo, y_hi);
100 };
101 size_t n_broken = 0;
102 for (size_t i = 0; i < dem.d_neighPd.size(); ++i) {
103 if (dem.d_ptId[i] != 0)
104 continue;
105 const auto &xi = dem.d_xRef[i];
106 for (size_t k = 0; k < dem.d_neighPd[i].size(); ++k) {
107 const size_t j = dem.d_neighPd[i][k];
108 if (dem.d_ptId[j] != 0)
109 continue;
110 const auto &xj = dem.d_xRef[j];
111 if (crossesSlot(xi.d_x, xi.d_y, xj.d_x, xj.d_y, -notch_half) ||
112 crossesSlot(xi.d_x, xi.d_y, xj.d_x, xj.d_y, notch_half)) {
113 dem.d_fracture_p->setBondState(i, k, true);
114 ++n_broken;
115 }
116 }
117 }
118 return n_broken;
119}
120
121// Break bonds that cross the midplane of each prenotch.
122size_t applyNotchMidplanes(PeriDEMModel &dem, double notch_half, double y_tip,
123 double y_top) {
124 auto crossesMid = [&](double x0, double y0, double x1, double y1, double xc) {
125 if ((x0 - xc) * (x1 - xc) >= 0.)
126 return false;
127 const double den = x1 - x0;
128 if (std::abs(den) < 1.0e-30)
129 return false;
130 const double t = (xc - x0) / den;
131 if (t <= 0. || t >= 1.)
132 return false;
133 const double y = y0 + t * (y1 - y0);
134 return y >= y_tip && y <= y_top;
135 };
136 size_t n_broken = 0;
137 for (size_t i = 0; i < dem.d_neighPd.size(); ++i) {
138 if (dem.d_ptId[i] != 0)
139 continue;
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 if (dem.d_ptId[j] != 0)
144 continue;
145 const auto &xj = dem.d_xRef[j];
146 if (crossesMid(xi.d_x, xi.d_y, xj.d_x, xj.d_y, -notch_half) ||
147 crossesMid(xi.d_x, xi.d_y, xj.d_x, xj.d_y, notch_half)) {
148 dem.d_fracture_p->setBondState(i, k, true);
149 ++n_broken;
150 }
151 }
152 }
153 return n_broken;
154}
155
156double nodePhi(const PeriDEMModel &dem, size_t i) {
157 const auto &nb = dem.d_neighPd[i];
158 if (nb.empty())
159 return 0.;
160 size_t n_br = 0;
161 for (size_t k = 0; k < nb.size(); ++k) {
162 if (dem.d_fracture_p->getBondState(i, k))
163 ++n_br;
164 }
165 return static_cast<double>(n_br) / static_cast<double>(nb.size());
166}
167
168double nodeDamageForFit(const PeriDEMModel &dem, size_t i, bool use_bond_count) {
169 if (use_bond_count) {
170 if (!dem.d_phiBond.empty() && i < dem.d_phiBond.size())
171 return static_cast<double>(dem.d_phiBond[i]);
172 return nodePhi(dem, i);
173 }
174 if (!dem.d_phi.empty() && i < dem.d_phi.size())
175 return static_cast<double>(dem.d_phi[i]);
176 return nodePhi(dem, i);
177}
178
179struct CrackFit {
180 size_t n_pts = 0;
181 double angle_to_notch_deg = 0.;
182 double mean_dx = 0.;
183 double mean_dy = 0.;
184};
185
186// Max-damage ridge in outward bins from the tip; PCA for a rough path angle.
187CrackFit fitCrackFromPhi(const PeriDEMModel &dem, double tip_x, double tip_y,
188 double outward_sign, double phi_cut, double band_x, double band_y,
189 double exclude_r, double bin_h, bool use_bond_count = false) {
190 CrackFit fit;
191 std::vector<double> xs, ys;
192
193 const int nbin = std::max(4, static_cast<int>(std::ceil((band_x - exclude_r) / bin_h)));
194 for (int b = 0; b < nbin; ++b) {
195 const double r0 = exclude_r + b * bin_h;
196 const double r1 = std::min(band_x, r0 + bin_h);
197 double best_phi = phi_cut;
198 double best_dx = 0., best_dy = 0.;
199 bool found = false;
200 for (size_t i = 0; i < dem.d_xRef.size(); ++i) {
201 if (dem.d_ptId[i] != 0)
202 continue;
203 const double phi = nodeDamageForFit(dem, i, use_bond_count);
204 if (phi < best_phi)
205 continue;
206 const double dx_ref = outward_sign * (dem.d_xRef[i].d_x - tip_x);
207 const double dy_ref = tip_y - dem.d_xRef[i].d_y;
208 if (dx_ref < r0 || dx_ref >= r1)
209 continue;
210 if (dy_ref < -2.0 * bin_h || dy_ref > band_y)
211 continue;
212 const double dx = dem.d_xRef[i].d_x - tip_x;
213 const double dy = tip_y - dem.d_xRef[i].d_y;
214 best_phi = phi;
215 best_dx = dx;
216 best_dy = dy;
217 found = true;
218 }
219 if (found) {
220 xs.push_back(best_dx);
221 ys.push_back(best_dy);
222 }
223 }
224 fit.n_pts = xs.size();
225 if (fit.n_pts < 4)
226 return fit;
227 double mx = 0., my = 0.;
228 for (size_t i = 0; i < fit.n_pts; ++i) {
229 mx += xs[i];
230 my += ys[i];
231 }
232 mx /= static_cast<double>(fit.n_pts);
233 my /= static_cast<double>(fit.n_pts);
234 fit.mean_dx = mx;
235 fit.mean_dy = my;
236 double cxx = 0., cxy = 0., cyy = 0.;
237 for (size_t i = 0; i < fit.n_pts; ++i) {
238 const double x = xs[i] - mx;
239 const double y = ys[i] - my;
240 cxx += x * x;
241 cxy += x * y;
242 cyy += y * y;
243 }
244 const double trace = cxx + cyy;
245 const double det = cxx * cyy - cxy * cxy;
246 const double tmp = std::sqrt(std::max(0., 0.25 * trace * trace - det));
247 const double l1 = 0.5 * trace + tmp;
248 double vx = cxy;
249 double vy = l1 - cxx;
250 if (vx * vx + vy * vy < 1.e-30) {
251 vx = l1 - cyy;
252 vy = cxy;
253 }
254 const double angle_from_horiz =
255 std::atan2(std::abs(vy), std::abs(vx)) * 180. / M_PI;
256 fit.angle_to_notch_deg = 90. - angle_from_horiz;
257 return fit;
258}
259
260json meshSetJson(const std::filesystem::path &f, double mesh_size) {
261 return json{{"File", f.string()},
262 {"CreateMesh",
263 {{"Flag", true},
264 {"Info", "gmsh_builtin_mesh"},
265 {"Mesh_Size", mesh_size},
266 {"Write_Mesh_File", true}}}};
267}
268
269// Silling 2003: "a rectangular, equally spaced structured grid with dimensions
270// 200 x 100 x 9". `voids` carves the two notch slots out of that grid so they
271// are real gaps in the material, matching Fig. 2.
272json uniformMeshSetJson(const std::filesystem::path &f, double mesh_size,
273 const std::vector<std::vector<double>> &voids = {}) {
274 json cm = {{"Flag", true},
275 {"Info", "uniform"},
276 {"Mesh_Size", mesh_size},
277 {"Write_Mesh_File", true}};
278 if (!voids.empty())
279 cm["Void_Regions"] = voids;
280 return json{{"File", f.string()}, {"CreateMesh", cm}};
281}
282
283// The two 1.5 mm notch slots as boxes, open at the top edge.
284std::vector<std::vector<double>> notchVoidBoxes(double H, double notch_half,
285 double notch_w, double notch_depth,
286 double z_lo, double z_hi) {
287 const double hw = 0.5 * notch_w;
288 const double y_tip = 0.5 * H - notch_depth;
289 const double y_hi = 0.5 * H + 1.0e-9;
290 return {{-notch_half - hw, y_tip, z_lo, -notch_half + hw, y_hi, z_hi},
291 {notch_half - hw, y_tip, z_lo, notch_half + hw, y_hi, z_hi}};
292}
293
294// Silling Fig. 2: plate W×H with two open top notches (width notch_w, depth notch_depth),
295// centers at x=±notch_half (tip-to-tip = 2*notch_half).
296geom::GeomData sillingNotchedPlateGeom(double W, double H, double notch_half,
297 double notch_w, double notch_depth) {
298 const double x0 = -0.5 * W, y0 = -0.5 * H, x1 = 0.5 * W, y1 = 0.5 * H;
299 const double hw = 0.5 * notch_w;
300 const double y_tip = y1 - notch_depth;
301 geom::GeomData plate;
302 plate.d_geomName = "complex";
303 plate.d_geomComplexInfo = {
304 std::vector<std::string>{"rectangle", "rectangle", "rectangle"},
305 std::vector<std::string>{"plus", "minus", "minus"}};
306 plate.d_geomParams = {
307 // outer plate
308 x0, y0, 0., x1, y1, 0.,
309 // left notch cutout (opens at top)
310 -notch_half - hw, y_tip, 0., -notch_half + hw, y1 + 1.0e-6, 0.,
311 // right notch cutout
312 notch_half - hw, y_tip, 0., notch_half + hw, y1 + 1.0e-6, 0.};
313 return plate;
314}
315
316// Bhat Fig. 4(a): V-notches — 1.5 mm opening at the top edge, tapering to a
317// sharp tip at 50 mm depth. Triangle cutouts (9 params = three vertices each).
318geom::GeomData bhatVNotchedPlateGeom(double W, double H, double notch_half,
319 double notch_w, double notch_depth) {
320 const double x0 = -0.5 * W, y0 = -0.5 * H, x1 = 0.5 * W, y1 = 0.5 * H;
321 const double hw = 0.5 * notch_w;
322 const double y_tip = y1 - notch_depth;
323 const double y_top = y1 + 1.0e-6;
324 geom::GeomData plate;
325 plate.d_geomName = "complex";
326 plate.d_geomComplexInfo = {
327 std::vector<std::string>{"rectangle", "triangle", "triangle"},
328 std::vector<std::string>{"plus", "minus", "minus"}};
329 plate.d_geomParams = {
330 // outer plate
331 x0, y0, 0., x1, y1, 0.,
332 // left V: top-left, top-right, tip
333 -notch_half - hw, y_top, 0., -notch_half + hw, y_top, 0., -notch_half, y_tip, 0.,
334 // right V
335 notch_half - hw, y_top, 0., notch_half + hw, y_top, 0., notch_half, y_tip, 0.};
336 return plate;
337}
338
339json fixedTopOuterBC(double W, double H, double notch_half) {
340 const double fix_h = 0.08 * H;
341 return json{
342 {"Set_1",
343 {{"Particle_List", std::vector<size_t>{0}},
344 {"Region",
345 {{"Geometry",
346 {{"Type", "rectangle"},
347 {"Parameters",
348 std::vector<double>{-0.55 * W, 0.5 * H - fix_h, 0., -notch_half - 0.02 * W,
349 0.55 * H, 0.}}}}}},
350 {"Direction", std::vector<size_t>{1, 2}},
351 {"Time_Function", {{"Type", "constant"}, {"Parameters", std::vector<double>{0.}}}},
352 {"Spatial_Function", {{"Type", "constant"}}},
353 {"Zero_Displacement", true}}},
354 {"Set_2",
355 {{"Particle_List", std::vector<size_t>{0}},
356 {"Region",
357 {{"Geometry",
358 {{"Type", "rectangle"},
359 {"Parameters",
360 std::vector<double>{notch_half + 0.02 * W, 0.5 * H - fix_h, 0., 0.55 * W, 0.55 * H,
361 0.}}}}}},
362 {"Direction", std::vector<size_t>{1, 2}},
363 {"Time_Function", {{"Type", "constant"}, {"Parameters", std::vector<double>{0.}}}},
364 {"Spatial_Function", {{"Type", "constant"}}},
365 {"Zero_Displacement", true}}}};
366}
367
368// Trask: Single_Particle plate; between notches u=<0,-v t> (two BC sets — one scalar du each).
369json buildTraskInputJson(const std::string &output_path,
370 const std::filesystem::path &mesh_plate, double W, double H,
371 double notch_half, double notch_w, double notch_depth, double mesh_size,
372 double horizon, double rho, double E, double K, double G, double Gc,
373 double v_impact, double final_time, size_t num_steps) {
374 auto model = inp::ModelDeck::getExampleJson(2, final_time, num_steps, "finite_difference",
375 "central_difference", true, 2, "Single_Particle",
376 0);
377 // Trask §5: broken bond → weight 0 (no force). No self-contact.
378 model["Self_Contact"] = "none";
379 model["Bond_Break"] = "tension"; // literature PMB: break in tension only
380
382 "vtu", output_path,
383 std::vector<std::string>({"Displacement", "Velocity", "Force", "Damage", "Damage_Z",
384 "Particle_ID", "Fixity"}),
385 std::max<size_t>(1, num_steps / 10), 1, false, "zlib", true, num_steps, "", false);
386
387 // Trask §6.2: top left/right of notches u=<0,0>; drive between notches
388 // u=<0,-v t>; sides and bottom free (collar bond-break not yet implemented).
389 const double drive_h = std::max(2.0 * mesh_size, 0.002);
390 const std::vector<double> drive_strip{-notch_half, 0.5 * H - drive_h, 0., notch_half,
391 0.55 * H, 0.};
392 json outer = fixedTopOuterBC(W, H, notch_half);
393 json bc_disp = {
394 {"Sets", 4},
395 {"Set_1", outer["Set_1"]},
396 {"Set_2", outer["Set_2"]},
397 {"Set_3",
398 {{"Particle_List", std::vector<size_t>{0}},
399 {"Region", {{"Geometry", {{"Type", "rectangle"}, {"Parameters", drive_strip}}}}},
400 {"Direction", std::vector<size_t>{1}},
401 {"Time_Function", {{"Type", "constant"}, {"Parameters", std::vector<double>{0.}}}},
402 {"Spatial_Function", {{"Type", "constant"}}},
403 {"Zero_Displacement", true}}},
404 {"Set_4",
405 {{"Particle_List", std::vector<size_t>{0}},
406 {"Region", {{"Geometry", {{"Type", "rectangle"}, {"Parameters", drive_strip}}}}},
407 {"Direction", std::vector<size_t>{2}},
408 {"Time_Function",
409 {{"Type", "linear"}, {"Parameters", std::vector<double>{-v_impact}}}},
410 {"Spatial_Function", {{"Type", "constant"}}}}}};
411
412 geom::GeomData plate =
413 sillingNotchedPlateGeom(W, H, notch_half, notch_w, notch_depth);
415 json mesh = {{"Sets", 1}, {"Set_1", meshSetJson(mesh_plate, mesh_size)}};
418 "PMBBond", false, horizon, 0, rho, K, G, Gc, true, 0, E);
419 // Trask/Silling ω≡1 (ConstInfluence default a0=dim+1 would mis-scale c)
420 material["Set_1"]["Influence_Function"] = {
421 {"Type", 0}, {"Parameters", std::vector<double>{1.0}}};
422
423 return json{{"Model", model},
424 {"Output", output},
425 {"Displacement_BC", bc_disp},
426 {"Particle", particle},
427 {"Mesh", mesh},
428 {"Material", material}};
429}
430
431json buildImpactInputJson(const std::string &output_path,
432 const std::filesystem::path &mesh_plate,
433 const std::filesystem::path &mesh_impactor, double W, double H,
434 double notch_half, double notch_w, double notch_depth, double Iw,
435 double Ih, double gap, double mesh_size, double horizon,
436 double Rc_factor, double Kn, double rho, double E, double K, double G,
437 double Gc, double v_impact, double final_time, size_t num_steps,
438 double thickness = 0.) {
439 // Silling 2003 EMU KW: explicit central difference; brittle-microelastic PMB;
440 // broken bond force = 0; plate boundaries load-free (no Displacement_BC);
441 // rigid impactor of finite mass.
442 // thickness > 0 runs his actual 3D case (200 x 100 x 9 grid, 9 mm plate).
443 const bool dim3 = thickness > 0.;
444 const size_t dim = dim3 ? 3 : 2;
445 // Element-node connectivity is only used for strain/stress output and does not
446 // support hexahedra, which is what the 3D structured grid produces.
447 auto model = inp::ModelDeck::getExampleJson(dim, final_time, num_steps, "finite_difference",
448 "central_difference", !dim3, 2, "Multi_Particle",
449 0);
450 model["Self_Contact"] = "none";
451 model["Bond_Break"] = "tension";
452 model["Wall_Contact"] = "meshed";
453 // Silling Fig. 2: rigid cylinder of 1.57 kg. In 2D the analogue is the mass
454 // per unit thickness of his 9 mm plate, M/t = 1.57/0.009 kg/m.
455 model["Rigid_Particles"] = json::array(
456 {json{{"Id", 1}, {"Mass", dim3 ? 1.57 : 1.57 / 0.009}}});
457
459 "vtu", output_path,
460 std::vector<std::string>({"Displacement", "Velocity", "Force", "Damage", "Damage_Bond",
461 "Damage_Z", "Particle_ID"}),
462 std::max<size_t>(1, num_steps / 10), 1, false, "zlib", true, num_steps, "", false);
463
464 json ic = {
465 {"Sets", 1},
466 {"Set_1",
467 {{"Particle_List", std::vector<size_t>{1}},
468 {"Constant_Velocity",
469 {{"Velocity_Vector", std::vector<double>{0., -v_impact, 0.}}}}}}};
470
471 // The impactor is rigid but of finite mass (Model.Rigid_Particles above), so
472 // it decelerates on contact instead of being driven through the plate.
473 json bc_disp = {{"Sets", 1},
474 {"Set_1",
475 {{"Particle_List", std::vector<size_t>{1}},
476 {"Direction", std::vector<size_t>{1}},
477 {"Time_Function",
478 {{"Type", "constant"}, {"Parameters", std::vector<double>{0.}}}},
479 {"Spatial_Function", {{"Type", "constant"}}},
480 {"Zero_Displacement", true}}}};
481
482 // Plate is the full box on a structured grid with the two 1.5 mm notch slots
483 // removed from the mesh, so they are real gaps (Fig. 2) rather than material
484 // whose bonds have merely been cut.
485 geom::GeomData plate;
486 geom::GeomData impactor;
487 if (dim3) {
488 // Keep both bodies centred on z = 0 so particle generation places them
489 // consistently (it positions objects by their centre).
490 plate.d_geomName = "cuboid";
491 plate.d_geomParams = {-0.5 * W, -0.5 * H, -0.5 * thickness,
492 0.5 * W, 0.5 * H, 0.5 * thickness};
493 // Silling Fig. 4: the cylinder axis is along the impact direction and its
494 // flat end face strikes the plate edge. Params: radius, centre of the
495 // beginning cross-section, and the vector to the end cross-section.
496 impactor.d_geomName = "cylinder";
497 impactor.d_geomParams = {0.5 * Iw, 0., -0.5 * Ih, 0., 0., Ih, 0.};
498 } else {
499 plate.d_geomName = "rectangle";
500 plate.d_geomParams = {-0.5 * W, -0.5 * H, 0., 0.5 * W, 0.5 * H, 0.};
501 // In-plane section of that same cylinder: Iw wide, Ih long, flat face down.
502 impactor.d_geomName = "rectangle";
503 impactor.d_geomParams = {-0.5 * Iw, -0.5 * Ih, 0., 0.5 * Iw, 0.5 * Ih, 0.};
504 }
506
507 const auto voids =
508 notchVoidBoxes(H, notch_half, notch_w, notch_depth,
509 dim3 ? -0.5 * thickness - 1.0e-9 : -1.0e-9,
510 dim3 ? 0.5 * thickness + 1.0e-9 : 1.0e-9);
511 // Plate on Silling's equally spaced structured grid. In 2D the impactor is a
512 // rectangle so it goes on the same grid; in 3D it is a cylinder, which the
513 // uniform grid cannot represent, so it is meshed by Gmsh at the same spacing.
514 json mesh = {{"Sets", 2},
515 {"Set_1", uniformMeshSetJson(mesh_plate, mesh_size, voids)},
516 {"Set_2", dim3 ? meshSetJson(mesh_impactor, mesh_size)
517 : uniformMeshSetJson(mesh_impactor, mesh_size)}};
518
519 // Impactor is steel like the plate but unbreakable, and its nodal forces are
520 // replaced by the rigid-body acceleration each step, so its bond stiffness
521 // never enters the solution.
524 "PMBBond", false, horizon, 0, rho, K, G, Gc, true, 0, E);
526 "PDElasticBond", false, horizon, 0, rho, K, G, 0., true, 0, E);
527 material["Set_1"]["Influence_Function"] = {
528 {"Type", 0}, {"Parameters", std::vector<double>{1.0}}};
529 material["Set_2"]["Influence_Function"] = {
530 {"Type", 0}, {"Parameters", std::vector<double>{1.0}}};
531
532 // Absolute Contact_Radius from the requested mesh size (not Factor×hMin).
533 const double Rc_abs = Rc_factor * mesh_size;
535 Rc_abs, /*computeContactR=*/false, /*damping*/ false, /*friction*/ false, Kn,
536 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, K);
537 contact_base["Kn"] = Kn;
539 contact["Set_1_1"] = contact_base;
540 contact["Set_1_2"] = contact_base;
541 contact["Set_2_2"] = contact_base;
542 contact["Damping_Law"] = "off";
543 contact["Friction_Law"] = "coulomb_simple";
544
545 const double cy_imp = 0.5 * H + 0.5 * Ih + gap; // flat face `gap` above the edge
546 auto pgen = inp::PGenDeck::getExampleJson("From_File");
547 pgen["Random_Rotation"] = false;
548 pgen["Data"]["N"] = 2;
549 pgen["Data"]["0"] = json{{"x", 0.},
550 {"y", 0.},
551 {"z", 0.},
552 {"theta", 0.},
553 {"s", 1.},
554 {"geom_id", 0},
555 {"mat_id", 0},
556 {"contact_id", 0}};
557 pgen["Data"]["1"] = json{{"x", 0.},
558 {"y", cy_imp},
559 {"z", 0.},
560 {"theta", 0.},
561 {"s", 1.},
562 {"geom_id", 1},
563 {"mat_id", 1},
564 {"contact_id", 1}};
565
566 // Plate has no Displacement_BC (Silling: load-free all around); the only
567 // Displacement_BC drives the rigid impactor.
568 return json{{"Model", model},
569 {"Output", output},
570 {"Displacement_BC", bc_disp},
571 {"IC", ic},
572 {"Particle", particle},
573 {"Mesh", mesh},
574 {"Material", material},
575 {"Contact", contact},
576 {"Neighbor", inp::PNeighborDeck::getExampleJson("simple_all", 5.0, 1, 0.5)},
577 {"Particle_Generation", pgen}};
578}
579
580// Bhat 2023 §6.2: V-notched plate, free impactor, reference_gap self-contact.
581json buildBhatInputJson(const std::string &output_path,
582 const std::filesystem::path &mesh_plate,
583 const std::filesystem::path &mesh_impactor, double W, double H,
584 double notch_half, double notch_w, double notch_depth, double Iw,
585 double Ih, double gap, double mesh_size, double horizon,
586 double Rc_factor, double rho, double E, double K, double G, double Gc,
587 double v_impact, double final_time, size_t num_steps) {
588 auto model = inp::ModelDeck::getExampleJson(2, final_time, num_steps, "finite_difference",
589 "velocity_verlet", true, 2, "Multi_Particle", 0);
590 model["Self_Contact"] = "reference_gap"; // Bhat 2023 §4.4
591 model["Bond_Break"] = "absolute_stretch"; // Bhat §3.1: |s| > |s0|
592 model["Wall_Contact"] = "meshed";
593
595 "vtu", output_path,
596 std::vector<std::string>({"Displacement", "Velocity", "Force", "Damage", "Damage_Bond",
597 "Damage_Z", "Particle_ID"}),
598 std::max<size_t>(1, num_steps / 10), 1, false, "zlib", true, num_steps, "", false);
599
600 json ic = {
601 {"Sets", 1},
602 {"Set_1",
603 {{"Particle_List", std::vector<size_t>{1}},
604 {"Constant_Velocity",
605 {{"Velocity_Vector", std::vector<double>{0., -v_impact, 0.}}}}}}};
606
607 // Paper: only the outer top ligaments are fixed; impactor is free.
608 json outer = fixedTopOuterBC(W, H, notch_half);
609 json bc_disp = {{"Sets", 2}, {"Set_1", outer["Set_1"]}, {"Set_2", outer["Set_2"]}};
610
611 // Bhat specimen: OCC/Gmsh nonconvex plate with V-notch cutouts (§5, Fig. 4).
612 geom::GeomData plate =
613 bhatVNotchedPlateGeom(W, H, notch_half, notch_w, notch_depth);
614 geom::GeomData impactor;
615 impactor.d_geomName = "rectangle";
616 impactor.d_geomParams = {-0.5 * Iw, -0.5 * Ih, 0., 0.5 * Iw, 0.5 * Ih, 0.};
618
619 json mesh = {{"Sets", 2},
620 {"Set_1", meshSetJson(mesh_plate, mesh_size)},
621 {"Set_2", meshSetJson(mesh_impactor, mesh_size)}};
622
623 // Bhat Table 1, constant micromodulus, ν = 1/3 (bond-based, 2D).
624 const double nu_bhat = 1.0 / 3.0;
625 const double c_bhat =
626 6.0 * E / (M_PI * std::pow(horizon, 3.0) * (1.0 - nu_bhat));
627 const double s0_bhat = std::sqrt(4.0 * M_PI * Gc / (9.0 * E * horizon));
628
630 auto mat_bhat = inp::MaterialDeck::getExampleJson("PMBBond", false, horizon, 0, rho, K, G, Gc,
631 /*computeParamsFromElastic=*/false, 0, E);
632 mat_bhat["Bond_Potential_Params"] = std::vector<double>{c_bhat, s0_bhat};
633 mat_bhat["Influence_Function"] = {{"Type", 0}, {"Parameters", std::vector<double>{1.0}}};
634 material["Set_1"] = mat_bhat;
635 // Free deformable striker (paper). Unbreakable elastic so the contact face
636 // delivers bulk impulse; plate remains PMB with paper (c, s0).
637 auto mat_strike = inp::MaterialDeck::getExampleJson("PDElasticBond", false, horizon, 0, rho, K, G,
638 0., true, 0, E);
639 mat_strike["Influence_Function"] = {{"Type", 0}, {"Parameters", std::vector<double>{1.0}}};
640 material["Set_2"] = mat_strike;
641
642 // Absolute Rc from mesh_size (not Factor×hMin).
643 const double Kn_bhat = 18.0 * K / (M_PI * std::pow(horizon, 4.0));
644 const double Rc_abs = Rc_factor * mesh_size;
646 Rc_abs, /*computeContactR=*/false, /*damping*/ false, /*friction*/ false, Kn_bhat,
647 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, K);
648 contact_base["Kn"] = Kn_bhat;
650 contact["Set_1_1"] = contact_base;
651 contact["Set_1_2"] = contact_base;
652 contact["Set_2_2"] = contact_base;
653 contact["Damping_Law"] = "off"; // Bhat §6: β_d = 0
654 contact["Friction_Law"] = "coulomb_simple"; // μ = 0 below
655 contact["Set_1_1"]["Friction_Coefficient"] = 0.;
656 contact["Set_1_2"]["Friction_Coefficient"] = 0.;
657 contact["Set_2_2"]["Friction_Coefficient"] = 0.;
658
659 const double cy_imp = 0.5 * H + 0.5 * Ih + gap;
660 auto pgen = inp::PGenDeck::getExampleJson("From_File");
661 pgen["Random_Rotation"] = false;
662 pgen["Data"]["N"] = 2;
663 pgen["Data"]["0"] = json{{"x", 0.}, {"y", 0.}, {"z", 0.},
664 {"theta", 0.}, {"s", 1.}, {"geom_id", 0},
665 {"mat_id", 0}, {"contact_id", 0}};
666 pgen["Data"]["1"] = json{{"x", 0.}, {"y", cy_imp}, {"z", 0.},
667 {"theta", 0.}, {"s", 1.}, {"geom_id", 1},
668 {"mat_id", 1}, {"contact_id", 1}};
669
670 return json{{"Model", model},
671 {"Output", output},
672 {"Displacement_BC", bc_disp},
673 {"IC", ic},
674 {"Particle", particle},
675 {"Mesh", mesh},
676 {"Material", material},
677 {"Contact", contact},
678 {"Neighbor", inp::PNeighborDeck::getExampleJson("simple_all", 5.0, 1, 0.5)},
679 {"Particle_Generation", pgen}};
680}
681
682} // namespace
683
684int main(int argc, char *argv[]) {
685 util::parallel::initMpi(argc, argv);
686 util::io::InputParser input(argc, argv);
687
688 unsigned n_threads = 1;
689 if (input.cmdOptionExists("-nThreads"))
690 n_threads = static_cast<unsigned>(std::stoi(input.getCmdOption("-nThreads")));
692
693 const bool quick = input.cmdOptionExists("-quick");
694 const bool trask = input.cmdOptionExists("-traskDisp");
695 const bool bhat = input.cmdOptionExists("-bhatKW");
696 // Silling's actual case is 3D: a 9 mm plate on a 200 x 100 x 9 grid.
697 const bool dim3 = input.cmdOptionExists("-dim3");
698
699 namespace fs = std::filesystem;
700 // Always write under the binary cwd (build/linux/.../notched_impact_inbuilt/) unless overridden.
701 std::string run_tag =
702 quick ? "quick" : (trask ? "trask" : (bhat ? "bhat" : (dim3 ? "silling3d" : "lit")));
703 // New campaign outputs go under runs_new/ (do not mix with archived runs/).
704 fs::path base = fs::current_path() / "runs_new" / run_tag;
705 if (input.cmdOptionExists("-outputDir"))
706 base = input.getCmdOption("-outputDir");
707 const fs::path out_dir = base / "out";
708 const fs::path inp_dir = base / "inp";
709 fs::create_directories(out_dir);
710 fs::create_directories(inp_dir);
711 std::cout << std::format("notched_impact: outputDir={}\n", base.string());
712
713 // Fig. 2 Silling 2003: 200×100 mm plate, two 1.5 mm notches depth 50 mm, tip-to-tip 50 mm.
714 double W = 0.200;
715 double H = 0.100;
716 double notch_depth = 0.050;
717 double notch_half = 0.025; // notch centerline ±25 mm
718 double notch_w = 0.0015; // Silling 1.5 mm open gap
719 double plate_thickness = 0.009; // Silling Fig. 2: 9 mm (only used with -dim3)
720 // Impactor: Silling Fig. 2/4 give a cylinder of 1.57 kg striking edge-on, as
721 // wide as the 50 mm ligament between the notches. At ρ = 8000 that mass fixes
722 // its length: 1.57/(8000·π·0.025²) = 0.100 m. Bhat Fig. 4(b) draws exactly
723 // this 50 × 100 mm section, which confirms both numbers.
724 double Iw = 0.050;
725 double Ih = 0.100;
726 // Silling 2003 EMU grid 200×100×9 on the mm plate → h ≈ 1 mm; Trask KW: δ = 3h.
727 double mesh_size = 0.001;
728 double horizon = 3.0 * mesh_size; // 3 mm
729 double Rc_factor = 0.95;
730 // The impactor must start outside the contact radius, which the solver sets to
731 // Rc_factor·h_min: any closer and the first step applies a saturated repulsive
732 // force that fires the struck nodes off at kilometres per second. Nothing acts
733 // on the impactor while it closes this gap, so its only effect is to delay
734 // contact by gap/v, which the crack-speed fit measures from damage arrival.
735 double gap = 1.5 * Rc_factor * mesh_size;
736
737 // Table 2 M1 ≡ Silling maraging steel (E=191 GPa, ν≈0.3).
738 // Gc from KIc≈90 MPa√m: plane-stress KIc²/E = 42408 J/m² (Trask/Silling handbook).
739 double rho = 8000.0;
740 double E = 191.0e9;
741 double Kbulk = 159.2e9;
742 double nu = 0.5 * (1.0 - E / (3.0 * Kbulk));
743 double G = E / (2.0 * (1.0 + nu));
744 double Gc = 42408.0;
745 double v_impact = 32.0;
746 double dt = 2.5e-9; // Bhat §6.1
747 // Silling's cracks run all the way to the free edges (75 mm) at ~900 m/s, so
748 // ~85 µs of propagation on top of the free flight and the initiation delay.
749 double final_time = 1.7e-4; // 170 µs
750 if (input.cmdOptionExists("-Gc"))
751 Gc = std::stod(input.getCmdOption("-Gc"));
752 if (input.cmdOptionExists("-finalTime"))
753 final_time = std::stod(input.getCmdOption("-finalTime"));
754 if (input.cmdOptionExists("-vImpact"))
755 v_impact = std::stod(input.getCmdOption("-vImpact"));
756 if (input.cmdOptionExists("-meshSize")) {
757 mesh_size = std::stod(input.getCmdOption("-meshSize"));
758 horizon = 3.0 * mesh_size;
759 }
760 if (input.cmdOptionExists("-horizon"))
761 horizon = std::stod(input.getCmdOption("-horizon"));
762 if (input.cmdOptionExists("-horizonFactor"))
763 horizon = std::stod(input.getCmdOption("-horizonFactor")) * mesh_size;
764
765 if (quick) {
766 W = 0.040;
767 H = 0.020;
768 notch_depth = 0.5 * H;
769 notch_half = 0.125 * W;
770 mesh_size = H / 16.0;
771 notch_w = std::max(mesh_size, 0.1 * notch_half);
772 Iw = 0.008;
773 Ih = 0.004;
774 gap = 1.5 * Rc_factor * mesh_size;
775 horizon = 3.0 * mesh_size;
776 rho = 1200.0;
777 E = 1.23e9;
778 Kbulk = 2.0e9;
779 nu = 0.5 * (1.0 - E / (3.0 * Kbulk));
780 G = E / (2.0 * (1.0 + nu));
781 Gc = 424.0;
782 dt = 1.0e-8;
783 final_time = 1.0e-4;
784 }
785
786 // Contact stiffness, Bhat Eq. (4.1). The solver's contact force density is
787 // Kn·V_j·overlap, so Kn carries one power of δ per spatial dimension of the
788 // node weight: 18k/(πδ⁴) with 2D weights h²·1 m, 18k/(πδ⁵) with 3D weights h³.
789 // Using the 3D form in 2D makes contact 1/δ ≈ 333× too stiff.
790 const double Kn = 18.0 * util::harmonicMean(Kbulk, Kbulk) /
791 (M_PI * std::pow(horizon, dim3 ? 5 : 4));
792 const size_t num_steps = static_cast<size_t>(std::llround(final_time / dt));
793
794 json input_json;
795 if (trask) {
796 input_json = buildTraskInputJson(directoryPathWithTrailingSep(out_dir),
797 inp_dir / "mesh_plate.msh", W, H, notch_half, notch_w,
798 notch_depth, mesh_size, horizon, rho, E, Kbulk, G, Gc,
799 v_impact, final_time, num_steps);
800 } else if (bhat) {
801 input_json = buildBhatInputJson(
802 directoryPathWithTrailingSep(out_dir), inp_dir / "mesh_plate.msh",
803 inp_dir / "mesh_impactor.msh", W, H, notch_half, notch_w, notch_depth, Iw, Ih, gap,
804 mesh_size, horizon, Rc_factor, rho, E, Kbulk, G, Gc, v_impact, final_time, num_steps);
805 } else {
806 input_json = buildImpactInputJson(
807 directoryPathWithTrailingSep(out_dir), inp_dir / "mesh_plate.msh",
808 inp_dir / "mesh_impactor.msh", W, H, notch_half, notch_w, notch_depth, Iw, Ih, gap,
809 mesh_size, horizon, Rc_factor, Kn, rho, E, Kbulk, G, Gc, v_impact, final_time,
810 num_steps, dim3 ? plate_thickness : 0.);
811 }
812 // Optional override (e.g. Bhat paper default is reference_gap; -selfContact none
813 // isolates prenotch+void setups from restorative self-contact across the notch).
814 if (input.cmdOptionExists("-selfContact")) {
815 const auto sc = input.getCmdOption("-selfContact");
816 input_json["Model"]["Self_Contact"] = sc;
817 std::cout << std::format("notched_impact: Self_Contact override -> {}\n", sc);
818 }
819 if (input.cmdOptionExists("-bondBreak")) {
820 const auto bb = input.getCmdOption("-bondBreak");
821 input_json["Model"]["Bond_Break"] = bb;
822 std::cout << std::format("notched_impact: Bond_Break override -> {}\n", bb);
823 }
824 if (input.cmdOptionExists("-knScale")) {
825 const double s = std::stod(input.getCmdOption("-knScale"));
826 for (auto &kv : input_json["Contact"].items()) {
827 if (!kv.value().is_object() || !kv.value().contains("Kn"))
828 continue;
829 kv.value()["Kn"] = kv.value()["Kn"].get<double>() * s;
830 }
831 std::cout << std::format("notched_impact: Contact Kn scaled by {}\n", s);
832 }
833 {
834 std::ofstream os(inp_dir / "input.json");
835 os << input_json.dump(2);
836 }
837
838 auto deck = std::make_shared<inp::Input>(input_json);
839 PeriDEMModel dem(deck);
840 dem.init();
841
842 const double y_top = 0.5 * H;
843 const double y_tip = y_top - notch_depth;
844 // Prenotch bonds: Bhat §5 removes bonds whose segment leaves the nonconvex
845 // V-domain (midplane cuts through each V tip); Silling/Trask: slot spans.
846 const size_t n_pre =
847 bhat ? applyNotchMidplanes(dem, notch_half, y_tip, y_top + 0.01 * H)
848 : applyNotchSlots(dem, notch_half, notch_w, y_tip, y_top + 0.01 * H);
849 if (n_pre < 10) {
850 std::cerr << "notched_impact: expected prenotch bonds, got " << n_pre << "\n";
851 return 1;
852 }
853
854 const char *mode_str =
855 quick ? "quick"
856 : (trask ? "trask_disp"
857 : (bhat ? "bhat_kw" : (dim3 ? "silling_3d" : "silling_impact")));
858 std::cout << std::format(
859 "notched_impact: mode={} nodes={} prenotch={} h={:.4e} ε={:.4e} notch_w={:.4e} "
860 "Gc={:.0f} E={:.3e} v={:.1f} Nt={} T={:.3e}\n",
861 mode_str, dem.d_x.size(), n_pre, mesh_size, horizon, notch_w, Gc, E, v_impact, num_steps,
862 final_time);
863
864 // Drive the loop ourselves (same sequence as time_int::Integrator::integrate)
865 // so we can record when each node first becomes damaged. That arrival-time
866 // field gives the crack speed, which Silling reports as ~900 m/s.
867 std::vector<float> arrival(dem.d_x.size(), -1.f);
868 const size_t sample_every = std::max<size_t>(1, num_steps / 400);
869 auto sampleArrivals = [&]() {
870 for (size_t i = 0; i < dem.d_xRef.size(); ++i) {
871 if (arrival[i] >= 0.f || dem.d_ptId[i] != 0)
872 continue;
873 if (nodePhi(dem, i) >= 0.30)
874 arrival[i] = static_cast<float>(dem.d_time);
875 }
876 };
877
879 if (dem.performOutput())
880 dem.output();
881 dem.setCurrentDt(dem.timeStep());
883 dem.computeForces();
885 while (dem.currentStep() < dem.numTimeSteps()) {
886 dem.integrateStep();
887 if (dem.shouldOutput())
888 dem.output();
889 if (dem.currentStep() % sample_every == 0)
890 sampleArrivals();
891 dem.checkStop();
892 }
893 sampleArrivals();
894
895 double vmax = 0., phi_max = 0.;
896 size_t n_phi30 = 0;
897 for (size_t i = 0; i < dem.d_v.size(); ++i) {
898 vmax = std::max(vmax, dem.d_v[i].length());
899 if (dem.d_ptId[i] != 0)
900 continue;
901 const double phi = nodePhi(dem, i);
902 phi_max = std::max(phi_max, phi);
903 if (phi >= 0.30)
904 ++n_phi30;
905 }
906 const float zmax =
907 dem.d_Z.empty() ? 0.f : *std::max_element(dem.d_Z.begin(), dem.d_Z.end());
908 std::cout << std::format(
909 "notched_impact diag: max|v|={:.3e} maxφ={:.3f} maxZ={:.3f} n(φ≥0.3)={}\n", vmax,
910 phi_max, zmax, n_phi30);
911
912 if (!(vmax < 8.0e3) || !std::isfinite(vmax)) {
913 std::cerr << "notched_impact: blow-up, max |v| = " << vmax << "\n";
914 return 1;
915 }
916
917 size_t n_extra = 0;
918 for (size_t i = 0; i < dem.d_neighPd.size(); ++i) {
919 if (dem.d_ptId[i] != 0)
920 continue;
921 for (size_t k = 0; k < dem.d_neighPd[i].size(); ++k) {
922 if (!dem.d_fracture_p->getBondState(i, k))
923 continue;
924 const size_t j = dem.d_neighPd[i][k];
925 if (dem.d_ptId[j] != 0)
926 continue;
927 const auto &xi = dem.d_xRef[i];
928 const auto &xj = dem.d_xRef[j];
929 const bool on_notch =
930 segmentCrossesVertical(xi.d_x, xi.d_y, xj.d_x, xj.d_y, -notch_half, y_tip,
931 y_top + 0.01 * H) ||
932 segmentCrossesVertical(xi.d_x, xi.d_y, xj.d_x, xj.d_y, notch_half, y_tip,
933 y_top + 0.01 * H);
934 if (!on_notch)
935 ++n_extra;
936 }
937 }
938 if (n_extra < 20) {
939 std::cerr << "notched_impact: expected new broken bonds, got " << n_extra
940 << " (prenotch=" << n_pre << ")\n";
941 return 1;
942 }
943
944 // Dump the damage field so the crack-path metric can be re-fitted offline
945 // without repeating the simulation.
946 {
947 std::ofstream csv(base / "damage.csv");
948 csv << "x_ref,y_ref,z_ref,x_cur,y_cur,z_cur,phi,phi_bond,arrival\n";
949 for (size_t i = 0; i < dem.d_xRef.size(); ++i) {
950 if (dem.d_ptId[i] != 0)
951 continue;
952 const double phi = nodeDamageForFit(dem, i, /*use_bond_count=*/false);
953 const double phi_b = nodeDamageForFit(dem, i, /*use_bond_count=*/true);
954 if (std::max(phi, phi_b) < 0.05)
955 continue;
956 csv << std::format("{:.6e},{:.6e},{:.6e},{:.6e},{:.6e},{:.6e},{:.4f},{:.4f},{:.6e}\n",
957 dem.d_xRef[i].d_x, dem.d_xRef[i].d_y, dem.d_xRef[i].d_z,
958 dem.d_x[i].d_x, dem.d_x[i].d_y, dem.d_x[i].d_z, phi, phi_b,
959 double(arrival[i]));
960 }
961 }
962
963 const double band_x = 0.45 * W;
964 const double band_y = 0.55 * H;
965 const double phi_cut = quick ? 0.20 : 0.35;
966 const double exclude_r = 2.0 * mesh_size;
967 const bool use_bond_dmg = bhat;
968 auto left = fitCrackFromPhi(dem, -notch_half, y_tip, -1.0, phi_cut, band_x, band_y, exclude_r,
969 mesh_size, use_bond_dmg);
970 auto right = fitCrackFromPhi(dem, notch_half, y_tip, +1.0, phi_cut, band_x, band_y, exclude_r,
971 mesh_size, use_bond_dmg);
972 if (left.n_pts < 4 || right.n_pts < 4) {
973 std::cerr << "notched_impact: insufficient φ-ridge for path fit (L=" << left.n_pts
974 << " R=" << right.n_pts << ")\n";
975 return 1;
976 }
977 if (!(left.mean_dx < 0. && left.mean_dy > 0. && right.mean_dx > 0. && right.mean_dy > 0.)) {
978 std::cerr << std::format(
979 "notched_impact: tip damage not outward/down "
980 "(L dx={:.3e} dy={:.3e} R dx={:.3e} dy={:.3e})\n",
981 left.mean_dx, left.mean_dy, right.mean_dx, right.mean_dy);
982 return 1;
983 }
984
985 const double ang = 0.5 * (left.angle_to_notch_deg + right.angle_to_notch_deg);
986 std::cout << std::format(
987 "notched_impact PASS: prenotch={} new_broken={} ang_to_notch={:.1f}° "
988 "(L={:.1f}/{} R={:.1f}/{}) max|v|={:.3e}\n",
989 n_pre, n_extra, ang, left.angle_to_notch_deg, left.n_pts, right.angle_to_notch_deg,
990 right.n_pts, vmax);
991
992 dem.close();
993 return 0;
994}
void applyInitialCondition()
void applyDisplacementBC()
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< float > d_Z
Damage at nodes.
Definition modelData.h:818
size_t currentStep() const
Definition modelData.h:164
std::unique_ptr< geometry::Fracture > d_fracture_p
Fracture state of bonds.
Definition modelData.h:736
double d_time
Current time.
Definition modelData.h:595
std::vector< size_t > d_ptId
Global node to particle id (walls are assigned id after last particle id)
Definition modelData.h:764
std::vector< float > d_phi
Damage function at the nodes (volume-weighted, Silling 2000)
Definition modelData.h:834
std::vector< std::vector< size_t > > d_neighPd
Neighbor data for peridynamic forces.
Definition modelData.h:770
size_t numTimeSteps() const
Definition modelData.h:166
void setCurrentDt(double dt)
Definition modelData.h:145
double timeStep() const
Definition modelData.h:141
bool shouldOutput() const
Definition modelData.h:174
std::vector< util::Point > d_v
Velocity of the nodes.
Definition modelData.h:751
std::vector< float > d_phiBond
Damage as broken-bond count fraction (Bhattacharya & Lipton 2023)
Definition modelData.h:837
bool performOutput() const
Definition modelData.h:172
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
json buildBhatInputJson(const std::string &output_path, const std::filesystem::path &mesh_plate, const std::filesystem::path &mesh_impactor, double W, double H, double notch_half, double notch_w, double notch_depth, double Iw, double Ih, double gap, double mesh_size, double horizon, double Rc_factor, double rho, double E, double K, double G, double Gc, double v_impact, double final_time, size_t num_steps)
Definition main.cpp:581
std::string directoryPathWithTrailingSep(const std::filesystem::path &dir)
Definition main.cpp:34
size_t applyNotchMidplanes(PeriDEMModel &dem, double notch_half, double y_tip, double y_top)
Definition main.cpp:122
json buildTraskInputJson(const std::string &output_path, const std::filesystem::path &mesh_plate, double W, double H, double notch_half, double notch_w, double notch_depth, double mesh_size, double horizon, double rho, double E, double K, double G, double Gc, double v_impact, double final_time, size_t num_steps)
Definition main.cpp:369
bool segmentCrossesVertical(double x0, double y0, double x1, double y1, double x_line, double y_lo, double y_hi)
Definition main.cpp:48
size_t applyNotchSlots(PeriDEMModel &dem, double notch_half, double notch_w, double y_lo, double y_hi)
Definition main.cpp:82
json buildImpactInputJson(const std::string &output_path, const std::filesystem::path &mesh_plate, const std::filesystem::path &mesh_impactor, double W, double H, double notch_half, double notch_w, double notch_depth, double Iw, double Ih, double gap, double mesh_size, double horizon, double Rc_factor, double Kn, double rho, double E, double K, double G, double Gc, double v_impact, double final_time, size_t num_steps, double thickness=0.)
Definition main.cpp:431
double nodePhi(const PeriDEMModel &dem, size_t i)
Definition main.cpp:156
json meshSetJson(const std::filesystem::path &f, double mesh_size)
Definition main.cpp:260
std::vector< std::vector< double > > notchVoidBoxes(double H, double notch_half, double notch_w, double notch_depth, double z_lo, double z_hi)
Definition main.cpp:284
json uniformMeshSetJson(const std::filesystem::path &f, double mesh_size, const std::vector< std::vector< double > > &voids={})
Definition main.cpp:272
CrackFit fitCrackFromPhi(const PeriDEMModel &dem, double tip_x, double tip_y, double outward_sign, double phi_cut, double band_x, double band_y, double exclude_r, double bin_h, bool use_bond_count=false)
Definition main.cpp:187
size_t applyNotches(PeriDEMModel &dem, double x_left, double x_right, double y_lo, double y_hi)
Definition main.cpp:59
double nodeDamageForFit(const PeriDEMModel &dem, size_t i, bool use_bond_count)
Definition main.cpp:168
geom::GeomData bhatVNotchedPlateGeom(double W, double H, double notch_half, double notch_w, double notch_depth)
Definition main.cpp:318
geom::GeomData sillingNotchedPlateGeom(double W, double H, double notch_half, double notch_w, double notch_depth)
Definition main.cpp:296
json fixedTopOuterBC(double W, double H, double notch_half)
Definition main.cpp:339
Collection of methods and data related to finite element and mesh.
Definition mesh.cpp:28
Collection of methods and data related to particle object.
Definition modelData.h:36
void applyRigidBodyConstraint(data::ModelData &data)
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.
double harmonicMean(const double &m1, const double &m2)
Definition function.cpp:131
Input data for geometrical objects.
std::vector< double > d_geomParams
Zone parameters.
std::string d_geomName
Zone type.
std::pair< std::vector< std::string >, std::vector< std::string > > d_geomComplexInfo
Zone geometry info if it is a complex type.
static json getExampleJson(double contactR=0., bool computeContactR=true, bool dampingOn=true, bool frictionOn=true, double Kn=0., double eps=1., double mu=0., double KnFactor=1., double betanFactor=1., double deltaMax=1., double vMax=0., double K=0.)
Returns example JSON object for ModelDeck configuration.
static json getExampleJson(std::string materialType="PDState", bool isPlainStrain=false, double horizon=-1., double horizonMeshRatio=-1., double density=1., double K=0., double G=0., double Gc=0., bool computeParamsFromElastic=true, size_t influenceFnType=0, double E=-1.)
Returns example JSON object for ModelDeck configuration.
static json getExampleJson(size_t dim=2, double tFinal=1.0, size_t Nt=10, std::string spatialDiscretization="finite_difference", std::string timeDiscretization="central_difference", bool populateElementNodeConnectivity=true, size_t quadOrder=2, std::string particleSimType="Multi_Particle", int seed=0)
Returns example JSON object for ModelDeck configuration.
Definition modelDeck.h:164
static json getExampleJson(std::string outFormat="vtu", std::string path="./", std::vector< std::string > outTags={"Displacement"}, size_t outputInterval=1, size_t debug=2, bool performFEOut=true, std::string compressType="zlib", bool performOut=true, size_t dtTestOut=1, std::string tagPPFile="", bool pvdCollection=false)
Returns example JSON object for ModelDeck configuration.
Definition outputDeck.h:144
static json getExampleJson(std::string genMethod="From_File")
Returns example JSON object for ModelDeck configuration.
Definition pGenDeck.h:65
static json getExampleJson(std::string updateCriteria="simple_all", double sFactor=1., size_t neighUpdateInterval=1, double nearBdNodesTol=0.5)
Returns example JSON object for ModelDeck configuration.
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.
int main()
Definition main.cpp:31