460 {
461
464
465 unsigned int nThreads;
466 if (input.cmdOptionExists("-nThreads"))
467 nThreads = std::stoi(input.getCmdOption("-nThreads"));
468 else
469 nThreads = std::thread::hardware_concurrency();
472
473
474 double final_time = 0.004;
475 size_t num_steps = 20000;
476 if (input.cmdOptionExists("-finalTime"))
477 final_time = std::stod(input.getCmdOption("-finalTime"));
478 if (input.cmdOptionExists("-numSteps"))
479 num_steps = std::stoul(input.getCmdOption("-numSteps"));
480 size_t search_interval = 40;
481 if (input.cmdOptionExists("-searchInterval"))
482 search_interval = std::stoul(input.getCmdOption("-searchInterval"));
483 int ncols = 4;
484 int nrows = 3;
485 if (input.cmdOptionExists("-nCols"))
486 ncols = std::stoi(input.getCmdOption("-nCols"));
487 if (input.cmdOptionExists("-nRows"))
488 nrows = std::stoi(input.getCmdOption("-nRows"));
489 std::string mpi_strategy = "auto";
490 if (input.cmdOptionExists("-mpiStrategy"))
491 mpi_strategy = input.getCmdOption("-mpiStrategy");
492 util::io::print(std::format(
"MPI_Strategy = {}, pack = {}x{} ({} grains)\n",
493 mpi_strategy, ncols, nrows, ncols * nrows));
494
495 namespace fs = std::filesystem;
496 const fs::path cwd = fs::current_path();
497 fs::path out_dir = cwd / "out";
498 fs::path inp_dir = cwd / "inp";
499 if (input.cmdOptionExists("-outputDir")) {
500 fs::path p = input.getCmdOption("-outputDir");
501 out_dir = p.is_absolute() ? std::move(p) : cwd / p;
502 }
503 if (input.cmdOptionExists("-inputDir")) {
504 fs::path p = input.getCmdOption("-inputDir");
505 inp_dir = p.is_absolute() ? std::move(p) : cwd / p;
506 } else if (input.cmdOptionExists("-outputDir"))
507 inp_dir = out_dir.parent_path() / "inp";
508
509 fs::create_directories(out_dir);
510 fs::create_directories(inp_dir);
511
512 const fs::path mesh_cir = inp_dir / "mesh_cir.msh";
513 const fs::path mesh_fixed = inp_dir / "mesh_fixed_container.msh";
514 const fs::path mesh_moving = inp_dir / "mesh_moving_container.msh";
515 const bool file_mesh = input.cmdOptionExists("-fileMesh");
516 const bool inbuilt = !file_mesh;
517 const bool write_meshes = input.cmdOptionExists("-writeMeshes") || inbuilt;
518 if (file_mesh) {
519#ifndef JHA2021_COMP_MESH_DIR
520 throw std::runtime_error("JHA2021_COMP_MESH_DIR is not set.");
521#else
522 const fs::path src(JHA2021_COMP_MESH_DIR);
523 for (const char *name :
524 {"mesh_cir.msh", "mesh_fixed_container.msh", "mesh_moving_container.msh"}) {
525 const fs::path from = src / name;
526 if (!fs::exists(from))
527 throw std::runtime_error("Missing frozen mesh " + from.string());
528 fs::copy_file(from, inp_dir / name, fs::copy_options::overwrite_existing);
529 }
530 util::io::print(std::format(
"Using frozen meshes from {}\n", src.string()));
531#endif
532 }
533
535 auto inputJson =
buildInputJson(output_path_for_deck, mesh_cir, mesh_fixed, mesh_moving,
536 final_time, num_steps, file_mesh, write_meshes,
537 search_interval, ncols, nrows, mpi_strategy);
538
539 {
540 std::ofstream os(inp_dir / "input.json");
541 os << inputJson.dump(2);
542 }
543
544 {
545 const double R = 0.001;
546 const double mesh_size = R / 5.0;
547 const double pad = 1.15 * 0.95 * 0.7 * mesh_size;
550 }
551
552 auto deck = std::make_shared<inp::Input>(inputJson);
554 const size_t ts_every = std::max<size_t>(1, num_steps / 100);
555 auto probe = std::make_unique<GrainContactProbe>(out_dir, ts_every);
556 GrainContactProbe *probe_p = probe.get();
557 dem.setPostprocess(std::move(probe));
558 dem.run(deck);
559
560
561
562 int max_pairs = probe_p->maxPairs();
563 double min_gap = probe_p->minGap();
564 double gap0 = probe_p->gap0();
566 int max_pairs_g = 0;
567 double min_gap_g = 0., gap0_g = 0.;
568 MPI_Allreduce(&max_pairs, &max_pairs_g, 1, MPI_INT, MPI_MAX,
570 MPI_Allreduce(&min_gap, &min_gap_g, 1, MPI_DOUBLE, MPI_MIN,
572 MPI_Allreduce(&
gap0, &gap0_g, 1, MPI_DOUBLE, MPI_MAX,
574 max_pairs = max_pairs_g;
575 min_gap = min_gap_g;
577 }
578
579 const float zmax =
580 dem.d_Z.empty() ? 0.f : *std::max_element(dem.d_Z.begin(), dem.d_Z.end());
582 "grain contact: gap0={}, min_gap={} (t={}), Rc={}, pairs_in_Rc={}, max Damage_Z={}\n",
583 gap0, min_gap, probe_p->tMin(), probe_p->Rc(), max_pairs, zmax));
584
585 const bool require_contact = !input.cmdOptionExists("-noRequireContact");
586 if (require_contact) {
587 const bool grains_touched = max_pairs > 0 && min_gap <
gap0 - 1.0e-8;
588 if (!grains_touched) {
589 util::io::print(
"requireContact: no grain–grain pair entered the contact radius.\n");
591 return EXIT_FAILURE;
592 }
593 if (!(
gap0 > probe_p->Rc())) {
594 util::io::print(
"requireContact: grains already in Rc at t=0; packing is too tight.\n");
596 return EXIT_FAILURE;
597 }
598 }
599
600 if (input.cmdOptionExists("-assertForce")) {
601
602 int force_ok = 1;
604 const fs::path csv = out_dir / "pp_compressive_test_0.csv";
606 util::io::print(std::format(
"assertForce: max |plate reaction| = {} from {}\n", fmax,
607 csv.string()));
608 if (fmax <= 0.) {
610 force_ok = 0;
611 }
612 }
615 if (!force_ok) {
617 return EXIT_FAILURE;
618 }
619 }
620
621
622 {
623 double max_u = 0.;
624 for (const auto &u : dem.d_u)
625 max_u = std::
max(max_u, u.length());
626 double com0x = 0., com0y = 0.;
627 if (!dem.d_particlesListTypeParticle.empty()) {
628 const auto c = dem.d_particlesListTypeParticle[0]->getXCenter();
629 com0x = c.d_x;
630 com0y = c.d_y;
631 }
633 MPI_Allreduce(MPI_IN_PLACE, &max_u, 1, MPI_DOUBLE, MPI_MAX,
636 std::ofstream os(out_dir / "mpi_metric.txt");
637 os << std::format("{:.12e} {:.12e} {:.12e}\n", max_u, com0x, com0y);
638 util::io::print(std::format(
"mpi_metric: max|u|={:.12e} grain0_com=({:.12e},{:.12e})\n",
639 max_u, com0x, com0y));
640 }
641 }
642
644 return EXIT_SUCCESS;
645}
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)
void writeLocations(const std::filesystem::path &csv, const std::vector< PackedParticle > &packed)
void print(const T &msg, int nt=print_default_tab, int printMpiRank=print_default_mpi_rank)
Prints formatted information.
T max(const std::vector< T > &data)
Returns the maximum from list of data.
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.