49 namespace fs = std::filesystem;
50 fs::path n = fs::absolute(dir).lexically_normal();
51 std::string s = n.string();
52 if (!s.empty() && s.back() !=
'/' && s.back() !=
'\\')
53 s += fs::path::preferred_separator;
57struct PackedParticle {
58 double x{}, y{}, z{}, r{}, theta{};
62 double y_lo,
double R,
double gap) {
63 std::vector<PackedParticle> particles;
64 particles.reserve(
static_cast<size_t>(ncols * nrows));
65 const double step = 2.0 * R + gap;
66 const double x0 = x_lo + gap + R;
67 const double y0 = y_lo + gap + R;
68 for (
int j = 0; j < nrows; ++j) {
69 for (
int i = 0; i < ncols; ++i) {
71 p.x = x0 +
static_cast<double>(i) * step;
72 p.y = y0 +
static_cast<double>(j) * step;
76 particles.push_back(p);
84 const std::filesystem::path &mesh_cir,
85 const std::filesystem::path &mesh_fixed,
86 const std::filesystem::path &mesh_moving,
double final_time,
87 size_t num_steps,
bool file_mesh,
bool write_meshes,
88 size_t search_interval,
int ncols,
int nrows,
89 const std::string &mpi_strategy =
"auto") {
91 if (ncols < 1 || nrows < 1)
92 throw std::runtime_error(
"jha2021_comp: ncols and nrows must be >= 1");
93 const int ngrains = ncols * nrows;
95 const std::vector<double> center = {0.0, 0.0, 0.0};
96 const double R = 0.001;
97 const double mesh_size = R / 5.0;
98 const double horizon = 3.0 * mesh_size;
101 const double h_est = 0.7 * mesh_size;
102 const double Rc_est = 0.95 * h_est;
103 const double particle_padding = 1.15 * Rc_est;
104 const double wpd = particle_padding;
105 const double rwp = horizon + wpd;
106 constexpr double wall_vy = -0.06;
108 const double Lin = 2.0 * particle_padding + 2.0 * R +
109 static_cast<double>(ncols - 1) * (2.0 * R + particle_padding);
110 const double Win = 2.0 * particle_padding + 2.0 * R +
111 static_cast<double>(nrows - 1) * (2.0 * R + particle_padding);
113 const double wall_t = rwp - wpd;
114 const std::vector<double> mw_rect = {center[0] - wpd, Win, center[2],
115 Lin + wpd, Win + wall_t, center[2]};
116 const std::vector<double> cup_channel = {center[0] - rwp, center[1] - rwp,
117 Lin + rwp, Win + wall_t, wall_t,
120 std::vector<geom::GeomData> pGeomVec(3);
121 pGeomVec[0].d_geomName =
"circle";
122 pGeomVec[0].d_geomParams = {R, center[0], center[1], center[2]};
123 pGeomVec[1].d_geomName =
"open_rect_channel_2d";
124 pGeomVec[1].d_geomParams = cup_channel;
125 pGeomVec[2].d_geomName =
"rectangle";
126 pGeomVec[2].d_geomParams = mw_rect;
128 std::vector<PackedParticle> packed =
130 if (
static_cast<int>(packed.size()) != ngrains)
131 throw std::runtime_error(
"jha2021_comp: grid count mismatch");
133 const double poisson = 0.25;
134 const double rho = 1200.0;
135 const double K = 2.16e+7;
138 const double Gc = 50.0;
141 const double dt = final_time /
static_cast<double>(num_steps);
142 const double c_wave = std::sqrt(E / rho);
143 const double dt_cfl = h_est / c_wave;
145 "jha2021_comp: N={} ({}x{}), gap={:.6e} (1.15 Rc_est), wpd={:.6e}, "
146 "dt={:.6e} s, h/c={:.6e} s, dt/(h/c)={:.3f}\n",
147 ngrains, ncols, nrows, particle_padding, wpd, dt, dt_cfl, dt / dt_cfl));
149 const size_t num_outputs = 4;
150 const size_t dt_out_n = std::max<size_t>(1, num_steps / num_outputs);
151 const size_t test_dt_out_n = std::max<size_t>(1, dt_out_n / 10);
153 for (
auto &g : pGeomVec)
156 const util::Point cfix = pGeomVec[1].d_geom_p->center();
157 const util::Point cmov = pGeomVec[2].d_geom_p->center();
158 const size_t n_pack = packed.size();
159 const size_t n_wall_fixed = n_pack;
160 const size_t n_wall_moving = n_pack + 1;
161 const size_t n_total = n_pack + 2;
164 json{{
"Dimension", 2},
165 {
"Final_Time", final_time},
166 {
"Time_Steps", num_steps},
167 {
"Discretization_Type",
json{{
"Spatial",
"finite_difference"}, {
"Time",
"central_difference"}}},
168 {
"Populate_ElementNodeConnectivity",
true},
169 {
"Quad_Approximation_Order", 2},
170 {
"Particle_Sim_Type",
"Multi_Particle"},
172 modelDeckJson[
"MPI_Strategy"] = mpi_strategy;
174 std::vector<std::string> out_tags = {
"Displacement",
"Velocity",
"Force",
"Damage_Z",
175 "Damage",
"Particle_ID",
"Contact_Nodes"};
177 json{{
"File_Format",
"vtu"},
178 {
"Path", output_path_for_deck},
180 {
"Output_Interval", dt_out_n},
182 {
"Perform_FE_Out",
true},
183 {
"Compress_Type",
"zlib"},
184 {
"Perform_Out",
true},
185 {
"Test_Output_Interval", test_dt_out_n},
187 {
"PVD_Collection",
true}});
191 json{{
"Displacement_BC_Sets", 2},
194 json{{
"Type",
"Displacement_BC"},
195 {
"Particle_List", {n_wall_fixed}},
196 {
"Direction", {1, 2}},
197 {
"Zero_Displacement",
true}});
199 set2[
"Particle_List"] = std::vector<size_t>{n_wall_moving};
200 set2[
"Direction"] = std::vector<size_t>{2};
201 set2[
"Time_Function"] =
json{{
"Type",
"linear"}, {
"Parameters", std::vector<double>{wall_vy}}};
202 set2[
"Spatial_Function"] =
json{{
"Type",
"constant"}};
203 bcDeckJson[
"Displacement_BC"][
"Set_2"] = set2;
205 json pDeckJson = json::object();
208 auto meshEntry = [&](
const std::filesystem::path &f) ->
json {
210 return json{{
"File", f.string()}};
211 return json{{
"File", f.string()},
214 {
"Info",
"gmsh_builtin_mesh"},
215 {
"Mesh_Size", mesh_size},
216 {
"Write_Mesh_File", write_meshes}}}};
218 pDeckJson[
"Mesh"] =
json{{
"Sets", 3},
219 {
"Set_1", meshEntry(mesh_cir)},
220 {
"Set_2", meshEntry(mesh_fixed)},
221 {
"Set_3", meshEntry(mesh_moving)}};
226 json{{
"Type",
"PDState"},
227 {
"Is_Plane_Strain",
false},
228 {
"Horizon", horizon},
233 {
"Compute_From_Classical",
true},
234 {
"Influence_Function",
json{{
"Type", 0}}}});
237 json{{
"Type",
"PDState"},
238 {
"Is_Plane_Strain",
false},
239 {
"Horizon", horizon},
244 {
"Compute_From_Classical",
true},
245 {
"Influence_Function",
json{{
"Type", 0}}}});
246 pDeckJson[
"Material"] = matRoot;
249 json{{
"Contact_Radius_Factor", 0.95},
251 {
"Damping_On",
true},
253 {
"Friction_On",
false},
254 {
"Friction_Coeff", 0.0},
256 {
"Beta_n_Factor", 100.0},
259 contactRoot[
"Set_1_1"] = contact_base;
260 contactRoot[
"Set_1_2"] = contact_base;
261 contactRoot[
"Set_2_2"] = contact_base;
262 pDeckJson[
"Contact"] = contactRoot;
263 pDeckJson[
"Neighbor"] =
265 json{{
"Update_Criteria",
"simple_all"},
266 {
"Search_Factor", 5.0},
267 {
"Search_Interval", search_interval},
268 {
"Near_Bd_Nodes_Tol", 0.5}});
271 pGenJson[
"Random_Rotation"] =
false;
272 pGenJson[
"Data"][
"N"] = n_total;
273 for (
size_t pi = 0; pi < n_pack; ++pi) {
274 const auto &p = packed[pi];
275 pGenJson[
"Data"][std::to_string(pi)] =
json{{
"x", p.x},
280 {
"geom_id", size_t(0)},
281 {
"mat_id", size_t(0)},
282 {
"contact_id", size_t(0)}};
284 pGenJson[
"Data"][std::to_string(n_pack)] =
json{{
"x", cfix.
d_x},
289 {
"geom_id", size_t(1)},
290 {
"mat_id", size_t(1)},
291 {
"contact_id", size_t(1)},
293 pGenJson[
"Data"][std::to_string(n_pack + 1)] =
json{{
"x", cmov.
d_x},
298 {
"geom_id", size_t(2)},
299 {
"mat_id", size_t(1)},
300 {
"contact_id", size_t(1)},
302 pDeckJson[
"Particle_Generation"] = pGenJson;
305 "jha2021_comp: Lin={:.6f}, Win={:.6f}, horizon={:.6f}, plate v_y={}, Wall_Id={}\n",
306 Lin, Win, horizon, wall_vy, n_wall_moving));
308 return json{{
"Comment",
"jha2021_comp_contact"},
309 {
"Model", modelDeckJson},
310 {
"Output", outputDeckJson},
311 {
"Force_BC", bcDeckJson[
"Force_BC"]},
312 {
"Displacement_BC", bcDeckJson[
"Displacement_BC"]},
313 {
"Particle", pDeckJson[
"Particle"]},
314 {
"Mesh", pDeckJson[
"Mesh"]},
315 {
"Material", pDeckJson[
"Material"]},
316 {
"Contact", pDeckJson[
"Contact"]},
317 {
"Neighbor", pDeckJson[
"Neighbor"]},
318 {
"Particle_Generation", pDeckJson[
"Particle_Generation"]},
319 {
"Test",
json{{
"Test_Name",
"compressive_test"},
321 json{{
"Wall_Id", n_wall_moving},
322 {
"Wall_Force_Direction", 2}}}}}};
326 const std::vector<PackedParticle> &packed) {
327 std::ofstream os(csv);
328 os <<
"i, x, y, z, r, o\n";
329 for (
const auto &p : packed)
330 os << std::format(
"0, {:.6f}, {:.6f}, {:.6f}, {:.6f}, 0.000000\n", p.x, p.y, p.z, p.r);
334 std::ifstream is(csv);
338 std::getline(is, line);
340 while (std::getline(is, line)) {
343 const auto c2 = line.rfind(
',');
344 if (c2 == std::string::npos)
346 max_abs = std::max(max_abs, std::abs(std::stod(line.substr(c2 + 1))));
354 : d_outDir(std::move(out_dir)), d_interval(interval) {
356 std::filesystem::create_directories(d_outDir /
"nodal");
357 d_metricOs.open(d_outDir /
"mpi_metric_ts.csv");
358 d_metricOs <<
"step,t,max_u,com0x,com0y\n";
365 if (
data.d_particlesListTypeAll.size() < 3)
369 Rc =
data.d_particleDeck_p->d_contactDeck.getContact(0, 0).d_contactR;
376 double min_gap = 1.0e9;
378 const auto &all =
data.d_particlesListTypeAll;
379 for (
size_t i = 0; i < all.size(); ++i) {
380 if (all[i]->getGroupId(
"contact_id") != 0)
382 for (
size_t j = i + 1; j < all.size(); ++j) {
383 if (all[j]->getGroupId(
"contact_id") != 0)
385 const double gap = all[i]->getXCenter().dist(all[j]->getXCenter()) -
386 all[i]->d_geom_p->boundingRadius() -
387 all[j]->d_geom_p->boundingRadius();
388 min_gap = std::min(min_gap, gap);
393 if (d_gap0 == 0. && min_gap < 1.0e8)
395 if (min_gap < d_minGap) {
397 d_tMin =
data.d_time;
399 if (n_in > d_maxPairs) {
402 "grain contact: {} pair(s) in Rc at t={:.6e}, min_gap={:.6e}, Rc={:.6e}\n",
403 n_in,
data.d_time, min_gap, Rc));
407 double minGap()
const {
return d_minGap; }
408 double gap0()
const {
return d_gap0; }
409 double Rc()
const {
return d_Rc; }
410 double tMin()
const {
return d_tMin; }
416 if (
data.d_pdDofMpi) {
417 if (i >=
data.d_pdNodePartition.size())
419 return static_cast<int>(
data.d_pdNodePartition[i]) == rank;
421 for (
const auto *p :
data.d_particlesListTypeAll) {
422 const size_t i0 = p->d_globStart;
423 const size_t i1 = i0 + p->getNumNodes();
424 if (i < i0 || i >= i1)
434 if (d_outDir.empty() || d_interval == 0)
436 const size_t nstep =
data.currentStep();
437 if (nstep % d_interval != 0 && nstep <
data.numTimeSteps())
440 for (
const auto &u :
data.d_u)
441 max_u = std::max(max_u, u.length());
443 MPI_Allreduce(MPI_IN_PLACE, &max_u, 1, MPI_DOUBLE, MPI_MAX,
445 double com0x = 0., com0y = 0.;
446 if (!
data.d_particlesListTypeParticle.empty()) {
447 const auto c =
data.d_particlesListTypeParticle[0]->getXCenter();
452 d_metricOs << std::format(
"{},{:.12e},{:.12e},{:.12e},{:.12e}\n", nstep,
453 data.d_time, max_u, com0x, com0y);
456 const size_t n =
data.d_u.size();
457 std::vector<double> buf(6 * n, 0.);
458 for (
size_t i = 0; i < n; ++i) {
461 buf[6 * i + 0] =
data.d_u[i].d_x;
462 buf[6 * i + 1] =
data.d_u[i].d_y;
463 buf[6 * i + 2] =
data.d_u[i].d_z;
464 buf[6 * i + 3] =
data.d_v[i].d_x;
465 buf[6 * i + 4] =
data.d_v[i].d_y;
466 buf[6 * i + 5] =
data.d_v[i].d_z;
469 MPI_Allreduce(MPI_IN_PLACE, buf.data(),
static_cast<int>(buf.size()),
474 std::ofstream xr(d_outDir /
"nodal" /
"x_ref.bin", std::ios::binary);
475 const uint32_t nn =
static_cast<uint32_t
>(n);
476 xr.write(
reinterpret_cast<const char *
>(&nn),
sizeof(nn));
477 for (
size_t i = 0; i < n; ++i) {
478 const double p[3] = {
data.d_xRef[i].d_x,
data.d_xRef[i].d_y,
480 xr.write(
reinterpret_cast<const char *
>(p),
sizeof(p));
484 std::ofstream os(d_outDir /
"nodal" / std::format(
"uv_{:06d}.bin", nstep),
486 const char magic[4] = {
'P',
'D',
'U',
'V'};
487 const uint32_t step32 =
static_cast<uint32_t
>(nstep);
488 const uint32_t nn =
static_cast<uint32_t
>(n);
490 os.write(
reinterpret_cast<const char *
>(&step32),
sizeof(step32));
491 os.write(
reinterpret_cast<const char *
>(&nn),
sizeof(nn));
492 os.write(
reinterpret_cast<const char *
>(buf.data()),
493 static_cast<std::streamsize
>(buf.size() *
sizeof(double)));
496 double d_minGap = 1.0e9;
502 size_t d_interval = 0;
504 bool d_wroteXref =
false;
509int main(
int argc,
char *argv[]) {
514 unsigned int nThreads;
518 nThreads = std::thread::hardware_concurrency();
523 double final_time = 0.004;
524 size_t num_steps = 20000;
526 final_time = std::stod(input.
getCmdOption(
"-finalTime"));
528 num_steps = std::stoul(input.
getCmdOption(
"-numSteps"));
529 size_t search_interval = 40;
531 search_interval = std::stoul(input.
getCmdOption(
"-searchInterval"));
538 std::string mpi_strategy =
"auto";
541 util::io::print(std::format(
"MPI_Strategy = {}, pack = {}x{} ({} grains)\n",
542 mpi_strategy, ncols, nrows, ncols * nrows));
544 namespace fs = std::filesystem;
545 const fs::path cwd = fs::current_path();
546 fs::path out_dir = cwd /
"out";
547 fs::path inp_dir = cwd /
"inp";
550 out_dir = p.is_absolute() ? std::move(p) : cwd / p;
554 inp_dir = p.is_absolute() ? std::move(p) : cwd / p;
556 inp_dir = out_dir.parent_path() /
"inp";
558 fs::create_directories(out_dir);
559 fs::create_directories(inp_dir);
561 const fs::path mesh_cir = inp_dir /
"mesh_cir.msh";
562 const fs::path mesh_fixed = inp_dir /
"mesh_fixed_container.msh";
563 const fs::path mesh_moving = inp_dir /
"mesh_moving_container.msh";
565 const bool inbuilt = !file_mesh;
566 const bool write_meshes = input.
cmdOptionExists(
"-writeMeshes") || inbuilt;
568#ifndef JHA2021_COMP_MESH_DIR
569 throw std::runtime_error(
"JHA2021_COMP_MESH_DIR is not set.");
571 const fs::path src(JHA2021_COMP_MESH_DIR);
572 for (
const char *name :
573 {
"mesh_cir.msh",
"mesh_fixed_container.msh",
"mesh_moving_container.msh"}) {
574 const fs::path from = src / name;
575 if (!fs::exists(from))
576 throw std::runtime_error(
"Missing frozen mesh " + from.string());
577 fs::copy_file(from, inp_dir / name, fs::copy_options::overwrite_existing);
579 util::io::print(std::format(
"Using frozen meshes from {}\n", src.string()));
583 const std::string output_path_for_deck = directoryPathWithTrailingSep(out_dir);
584 auto inputJson =
buildInputJson(output_path_for_deck, mesh_cir, mesh_fixed, mesh_moving,
585 final_time, num_steps, file_mesh, write_meshes,
586 search_interval, ncols, nrows, mpi_strategy);
589 std::ofstream os(inp_dir /
"input.json");
590 os << inputJson.dump(2);
602 const double R = 0.001;
603 const double mesh_size = R / 5.0;
604 const double pad = 1.15 * 0.95 * 0.7 * mesh_size;
609 auto deck = std::make_shared<inp::Input>(inputJson);
611 const size_t ts_every = std::max<size_t>(1, num_steps / 100);
612 auto probe = std::make_unique<GrainContactProbe>(out_dir, ts_every);
613 GrainContactProbe *probe_p = probe.get();
619 int max_pairs = probe_p->maxPairs();
620 double min_gap = probe_p->minGap();
621 double gap0 = probe_p->gap0();
624 double min_gap_g = 0., gap0_g = 0.;
625 MPI_Allreduce(&max_pairs, &max_pairs_g, 1, MPI_INT, MPI_MAX,
627 MPI_Allreduce(&min_gap, &min_gap_g, 1, MPI_DOUBLE, MPI_MIN,
629 MPI_Allreduce(&
gap0, &gap0_g, 1, MPI_DOUBLE, MPI_MAX,
631 max_pairs = max_pairs_g;
637 dem.
d_Z.empty() ? 0.f : *std::max_element(dem.
d_Z.begin(), dem.
d_Z.end());
639 "grain contact: gap0={}, min_gap={} (t={}), Rc={}, pairs_in_Rc={}, max Damage_Z={}\n",
640 gap0, min_gap, probe_p->tMin(), probe_p->Rc(), max_pairs, zmax));
642 const bool require_contact = !input.
cmdOptionExists(
"-noRequireContact");
643 if (require_contact) {
644 const bool grains_touched = max_pairs > 0 && min_gap <
gap0 - 1.0e-8;
645 if (!grains_touched) {
646 util::io::print(
"requireContact: no grain–grain pair entered the contact radius.\n");
650 if (!(
gap0 > probe_p->Rc())) {
651 util::io::print(
"requireContact: grains already in Rc at t=0; packing is too tight.\n");
661 const fs::path csv = out_dir /
"pp_compressive_test_0.csv";
663 util::io::print(std::format(
"assertForce: max |plate reaction| = {} from {}\n", fmax,
681 for (
const auto &u : dem.
d_u)
682 max_u = std::max(max_u, u.length());
683 double com0x = 0., com0y = 0.;
690 MPI_Allreduce(MPI_IN_PLACE, &max_u, 1, MPI_DOUBLE, MPI_MAX,
693 std::ofstream os(out_dir /
"mpi_metric.txt");
694 os << std::format(
"{:.12e} {:.12e} {:.12e}\n", max_u, com0x, com0y);
695 util::io::print(std::format(
"mpi_metric: max|u|={:.12e} grain0_com=({:.12e},{:.12e})\n",
696 max_u, com0x, com0y));
int main(int argc, char *argv[])
void setPostprocess(std::unique_ptr< postprocess::Postprocess > p)
void run(std::shared_ptr< inp::Input > &deck)
static bool ownsNode(const data::ModelData &data, size_t i)
GrainContactProbe(std::filesystem::path out_dir={}, size_t interval=0)
void sampleMetric(data::ModelData &data)
void checkStop(data::ModelData &data) override
std::filesystem::path d_outDir
A class to store model data.
std::vector< float > d_Z
Damage at nodes.
std::vector< util::Point > d_u
Displacement of the nodes.
std::vector< particle::BaseParticle * > d_particlesListTypeParticle
List of particles.
Extra postprocessing and stop criteria (VTU writing is in rw::).
virtual void checkStop(data::ModelData &data)
nlohmann::ordered_json json
std::string directoryPathWithTrailingSep(const std::filesystem::path &dir)
json buildInputJson(const std::string &output_path_for_deck, const std::filesystem::path &mesh_cir, const std::filesystem::path &mesh_fixed, const std::filesystem::path &mesh_moving, double final_time, size_t num_steps, bool file_mesh, bool write_meshes, size_t search_interval, int ncols, int nrows, const std::string &mpi_strategy="auto")
std::vector< PackedParticle > generateCircularGrid(int ncols, int nrows, double x_lo, double y_lo, double R, double gap)
double maxAbsForceFromCsv(const std::filesystem::path &csv)
bool ownsNode(const data::ModelData &data, size_t i)
void writeLocations(const std::filesystem::path &csv, const std::vector< PackedParticle > &packed)
void createGeomObject(const std::string &geom_type, const std::vector< double > ¶ms, const std::vector< std::string > &vec_type, const std::vector< std::string > &vec_flag, std::shared_ptr< geom::GeomObject > &obj, bool perform_check)
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.
bool isLocallyOwned(const BaseParticle &p)
True if this rank updates / assembles forces for the particle. Walls are replicated on every rank....
void print(const T &msg, int nt=print_default_tab, int printMpiRank=print_default_mpi_rank)
Prints formatted information.
unsigned int getNThreads()
Get number of threads to be used by taskflow.
void initNThreads(unsigned int nThreads=std::thread::hardware_concurrency())
Initializes MpiStatus struct.
void initMpi(int argc=0, char *argv[]=nullptr)
Initializes MPI and also creates MpiStatus struct.
int mpiSize()
Get size (number) of processors.
int mpiRank()
get rank (id) of this processor
void finalizeMpi()
Call MPI_Finalize if this process initialized MPI.
MPI_Comm mpiComm()
Get MPI comm.
double normalContactStiffness(const double &K1, const double &K2, const double &horizon, int horizonPower=5)
Silling normal contact stiffness from two bulk moduli and the horizon.
static json getExampleJson(const json &given=json::object(), const geom::GeomData *region=nullptr)
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 example JSON object for ModelDeck configuration.
static json getExampleJson(const json &given=json::object())
Returns the block with the given fields set.
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.
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.
A structure to represent 3d vectors.
double d_y
the y coordinate
std::vector< double > toVec() const
Returns coordinate in stl form.
double d_x
the x coordinate