492 {
493
496 util::io::print(std::format(
"Initialized MPI. MPI size = {}, MPI rank = {}\n", mpiSize, mpiRank));
498
500
501 unsigned int nThreads;
502 if (input.cmdOptionExists("-nThreads"))
503 nThreads = std::stoi(input.getCmdOption("-nThreads"));
504 else {
505 nThreads = std::thread::hardware_concurrency();
506 util::io::print(std::format(
"Running test with default number of threads = {}\n", nThreads));
507 }
510
511#ifndef TWOP_CONTACT_EXAMPLE
512 double final_time = 0.002;
513 size_t num_steps = 6000;
514#else
515 double final_time = 0.012;
516 size_t num_steps = 36000;
517#endif
518 bool zero_ic = false;
519 double mesh_size_in = -1.;
520 double horizon_in = -1.;
521 bool assert_cr = false;
522 double cr_ref = 1.;
523 double cr_tol = 0.1;
524 bool damping_on = false;
525 double eps_n = 0.9;
526 double beta_n_factor = 100.0;
527 std::string damping_law = "com_and_node";
528 std::string friction_law = "coulomb_simple";
529 bool friction_on = false;
530 double friction_mu = -1.;
531 double ic_vx = 0.;
532 bool assert_lat = false;
533 double lat_min = 0.;
534 bool bottom_patch_bc = false;
535 std::string mpi_strategy = "auto";
536 bool write_mpi_metric = false;
537 int jha_test = 0;
538 if (input.cmdOptionExists("-jha2021Table2Test"))
539 jha_test = std::stoi(input.getCmdOption("-jha2021Table2Test"));
540 else if (input.cmdOptionExists("-jha2021Table2"))
541 jha_test = 1;
542 if (jha_test > 0) {
544
545
546 final_time = 0.04;
547 num_steps = 200000;
548 horizon_in = 0.0006;
549
550
551 zero_ic = false;
552 assert_cr = true;
553 cr_ref = c.cr;
554 cr_tol = 0.05;
555 eps_n = c.eps;
556 damping_on = c.eps < 1.0 - 1.0e-12;
557 }
558 if (input.cmdOptionExists("-finalTime"))
559 final_time = std::stod(input.getCmdOption("-finalTime"));
560 if (input.cmdOptionExists("-numSteps"))
561 num_steps = std::stoul(input.getCmdOption("-numSteps"));
562 if (input.cmdOptionExists("-zeroIC"))
563 zero_ic = true;
564 if (input.cmdOptionExists("-meshSize"))
565 mesh_size_in = std::stod(input.getCmdOption("-meshSize"));
566 if (input.cmdOptionExists("-horizon"))
567 horizon_in = std::stod(input.getCmdOption("-horizon"));
568 if (input.cmdOptionExists("-epsN")) {
569 eps_n = std::stod(input.getCmdOption("-epsN"));
570 damping_on = eps_n < 1.0 - 1.0e-12;
571 }
572 if (input.cmdOptionExists("-betaNFactor"))
573 beta_n_factor = std::stod(input.getCmdOption("-betaNFactor"));
574 if (input.cmdOptionExists("-dampingLaw"))
575 damping_law = input.getCmdOption("-dampingLaw");
576 if (input.cmdOptionExists("-frictionLaw"))
577 friction_law = input.getCmdOption("-frictionLaw");
578 if (input.cmdOptionExists("-enableFriction"))
579 friction_on = true;
580 if (input.cmdOptionExists("-mu"))
581 friction_mu = std::stod(input.getCmdOption("-mu"));
582 if (input.cmdOptionExists("-icVx"))
583 ic_vx = std::stod(input.getCmdOption("-icVx"));
584 if (input.cmdOptionExists("-assertLateralVx")) {
585 assert_lat = true;
586 lat_min = std::stod(input.getCmdOption("-assertLateralVx"));
587 assert_cr = false;
588 }
589 if (input.cmdOptionExists("-assertCR")) {
590 assert_cr = true;
591 cr_ref = std::stod(input.getCmdOption("-assertCR"));
592 }
593 if (input.cmdOptionExists("-crTol"))
594 cr_tol = std::stod(input.getCmdOption("-crTol"));
595 if (input.cmdOptionExists("-bottomPatchBC"))
596 bottom_patch_bc = true;
597 if (input.cmdOptionExists("-mpiStrategy"))
598 mpi_strategy = input.getCmdOption("-mpiStrategy");
599 if (input.cmdOptionExists("-writeMpiMetric"))
600 write_mpi_metric = true;
602 "final_time = {}, num_steps = {}, zero_ic = {}, eps_n = {}, C_bar = {}, "
603 "damping = {}, Damping_Law = {}, Friction_Law = {}, bottomPatchBC = {}, "
604 "MPI_Strategy = {}\n",
605 final_time, num_steps, zero_ic, eps_n, beta_n_factor, damping_on,
606 damping_law, friction_law, bottom_patch_bc, mpi_strategy));
607
608 namespace fs = std::filesystem;
609 const fs::path cwd = fs::current_path();
610
611 fs::path out_dir = cwd / "out";
612 fs::path inp_dir = cwd / "inp";
613
614 if (input.cmdOptionExists("-outputDir")) {
615 fs::path p = input.getCmdOption("-outputDir");
616 out_dir = p.is_absolute() ? std::move(p) : cwd / p;
617 }
618 if (input.cmdOptionExists("-inputDir")) {
619 fs::path p = input.getCmdOption("-inputDir");
620 inp_dir = p.is_absolute() ? std::move(p) : cwd / p;
621 } else if (input.cmdOptionExists("-outputDir")) {
622
623 inp_dir = out_dir.parent_path() / "inp";
624 }
625
626 fs::create_directories(out_dir);
627 fs::create_directories(inp_dir);
628
630 const fs::path mesh1 = inp_dir / "mesh_cir_1.msh";
631 const fs::path mesh2 = inp_dir / "mesh_cir_2.msh";
632 const bool inbuilt_mesh = input.cmdOptionExists("-inbuiltMesh");
633 const bool file_mesh = jha_test > 0 && !inbuilt_mesh;
634 if (file_mesh) {
635#ifndef JHA2021_MESH_DIR
636 throw std::runtime_error("JHA2021_MESH_DIR is not set (v0.1.0 circ_damp meshes).");
637#else
638 const fs::path src(JHA2021_MESH_DIR);
639 fs::copy_file(src / "mesh_cir_1.msh", mesh1, fs::copy_options::overwrite_existing);
640 fs::copy_file(src / "mesh_cir_2.msh", mesh2, fs::copy_options::overwrite_existing);
641 util::io::print(std::format(
"Using v0.1.0 circ_damp meshes from {}\n", src.string()));
642#endif
643 } else if (jha_test > 0)
644 util::io::print(
"Using in-process Gmsh (gmsh_builtin_mesh) instead of frozen v0.1.0 .msh\n");
645
646 util::io::print(std::format(
"Output directory (VTU, log.txt): {}\n", fs::absolute(out_dir).
string()));
647 util::io::print(std::format(
"Input directory (input.json, meshes): {}\n", fs::absolute(inp_dir).
string()));
648
649 auto inputJson =
buildInputJson(output_path_for_deck, mesh1, mesh2, final_time,
650 num_steps, zero_ic, mesh_size_in, horizon_in,
651 damping_on, eps_n, jha_test > 0, file_mesh,
652 beta_n_factor, damping_law,
653 friction_law, friction_on, friction_mu,
654 ic_vx, bottom_patch_bc, mpi_strategy);
655
656 const fs::path input_json_path = inp_dir / "input.json";
657 {
658 std::ofstream os(input_json_path);
659 if (!os)
660 throw std::runtime_error("Failed to open " + input_json_path.string() + " for writing.");
661 os << inputJson.dump(2);
662 }
663 util::io::print(std::format(
"Wrote deck to {}\n", fs::absolute(input_json_path).
string()));
664 if (jha_test > 0) {
665 const auto &p1 = inputJson["Particle_Generation"]["Data"]["1"];
666 const auto &v = inputJson["IC"]["Set_1"]["Constant_Velocity"]["Velocity_Vector"];
667 const double y1 = p1.at("y").get<double>();
668 const double gap0 = y1 - 0.001 - 0.001 - 0.001;
670 "Table 2 kinematics: top y = {}, surface gap = {}, v_y = {}\n",
671 y1, gap0, v.at(1).get<double>()));
672 }
673
674 auto deck = std::make_shared<inp::Input>(inputJson);
675
679 if (assert_cr) {
680 auto p = std::make_unique<RestitutionProbe>(0.001);
681 probe = p.get();
682 dem.setPostprocess(std::move(p));
683 } else if (assert_lat) {
684 auto p = std::make_unique<LateralProbe>(ic_vx);
685 lat_probe = p.get();
686 dem.setPostprocess(std::move(p));
687 } else if (write_mpi_metric) {
688 const size_t ts_every = std::max<size_t>(1, num_steps / 100);
689 dem.setPostprocess(
690 std::make_unique<MpiMetricTsProbe>(out_dir, ts_every));
691 }
692 dem.run(deck);
693
694 if (input.cmdOptionExists("-requireContact")) {
695 const float zmax =
696 dem.d_Z.empty() ? 0.f : *std::max_element(dem.d_Z.begin(), dem.d_Z.end());
697 util::io::print(std::format(
"requireContact: max Damage_Z = {}\n", zmax));
698 if (zmax <= 0.f) {
699 util::io::print(
"requireContact: no damage; particles did not contact.\n");
700 return EXIT_FAILURE;
701 }
702 }
703
704 if (assert_cr) {
705 {
706 const fs::path gap_csv = out_dir / "gap.csv";
707 std::ofstream gs(gap_csv);
708 gs << "t,gap\n";
709 for (const auto &s : probe->samples())
710 gs << s.first << "," << s.second << "\n";
711 util::io::print(std::format(
"Wrote {}\n", fs::absolute(gap_csv).
string()));
712 }
713 const double cr = probe->
CR();
715 "assertCR: contacted={}, min_gap={} (t={}), H1={} (t={}), CR={}, ref={}, tol={}\n",
717 probe->
tH1(), cr, cr_ref, cr_tol));
718 if (cr > 1.02) {
720 "assertCR: CR > 1.02 on the 1 mm energy-equivalent drop "
721 "(surface gap = horizon, leftover height already in v_y). "
722 "Unphysical energy gain.\n");
723 return EXIT_FAILURE;
724 }
725 if (!probe->
contacted() || std::abs(cr - cr_ref) > cr_tol) {
726 util::io::print(
"assertCR: coefficient of restitution out of range.\n");
727 return EXIT_FAILURE;
728 }
729 }
730
731 if (assert_lat) {
733 const double vmax = lat_probe->
maxAbsVx();
734 const double ic = lat_probe->
icAbsVx();
736 "assertLateralVx: contacted={}, max|vx|={}, min|vx|_after_contact={}, "
737 "ic|vx|={}, slow_factor_max={}\n",
738 lat_probe->
contacted(), vmax, vmin, ic, lat_min));
741 return EXIT_FAILURE;
742 }
743
744 if (!(ic > 0.) || !(vmin < lat_min * ic)) {
746 "assertLateralVx: friction did not slow lateral speed enough "
747 "(or kinematics missed).\n");
748 return EXIT_FAILURE;
749 }
750 }
751
752 if (write_mpi_metric) {
753 double max_u = 0.;
754 for (const auto &u : dem.d_u)
755 max_u = std::
max(max_u, u.length());
756 double com1x = 0., com1y = 0.;
757 if (dem.d_particlesListTypeParticle.size() > 1) {
758 const auto c = dem.d_particlesListTypeParticle[1]->getXCenter();
759 com1x = c.d_x;
760 com1y = c.d_y;
761 }
763 MPI_Allreduce(MPI_IN_PLACE, &max_u, 1, MPI_DOUBLE, MPI_MAX,
765
766
767
768 }
770 std::ofstream os(out_dir / "mpi_metric.txt");
771 os << std::format("{:.12e} {:.12e} {:.12e}\n", max_u, com1x, com1y);
773 "mpi_metric: max|u|={:.12e} grain1_com=({:.12e},{:.12e})\n", max_u,
774 com1x, com1y));
775 }
776 }
777
779 return EXIT_SUCCESS;
780}
double minAbsVxAfterContact() const
std::string directoryPathWithTrailingSep(const std::filesystem::path &dir)
json buildInputJson(const std::string &output_path_for_deck, const std::filesystem::path &mesh_file_1, const std::filesystem::path &mesh_file_2, double final_time, size_t num_steps, bool zero_ic, double mesh_size_in, double horizon_in, bool damping_on, double eps_n, bool two_particle_test, bool file_mesh, double beta_n_factor, const std::string &damping_law="com_and_node", const std::string &friction_law="coulomb_simple", bool friction_on=false, double friction_mu=-1., double ic_vx=0., bool bottom_patch_bc=false, const std::string &mpi_strategy="auto")
std::string printStr(const T &msg, int nt=print_default_tab)
Returns formatted string for output.
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.
bool isMpiEnabled()
Function to check if MPI is enabled.
unsigned int getNThreads()
Get number of threads to be used by taskflow.
void initNThreads(unsigned int nThreads=std::thread::hardware_concurrency())
Initializes MpiStatus struct.
const MpiStatus * getMpiStatus()
Returns pointer to 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.
Jha2021Table2 jha2021Table2(int test_id)