549 {
550
553 util::io::print(std::format(
"Initialized MPI. MPI size = {}, MPI rank = {}\n", mpiSize, mpiRank));
555
557
558 unsigned int nThreads;
559 if (input.cmdOptionExists("-nThreads"))
560 nThreads = std::stoi(input.getCmdOption("-nThreads"));
561 else {
562 nThreads = std::thread::hardware_concurrency();
563 util::io::print(std::format(
"Running test with default number of threads = {}\n", nThreads));
564 }
567
568#ifndef TWOP_CONTACT_EXAMPLE
569 double final_time = 0.002;
570 size_t num_steps = 6000;
571#else
572 double final_time = 0.012;
573 size_t num_steps = 36000;
574#endif
575 bool zero_ic = false;
576 double mesh_size_in = -1.;
577 double horizon_in = -1.;
578 bool assert_cr = false;
579 double cr_ref = 1.;
580 double cr_tol = 0.1;
581 bool damping_on = false;
582 double eps_n = 0.9;
583 double beta_n_factor = 100.0;
584 std::string damping_law = "com_and_node";
585 std::string friction_law = "coulomb_simple";
586 bool friction_on = false;
587 double friction_mu = -1.;
588 double ic_vx = 0.;
589 bool assert_lat = false;
590 double lat_min = 0.;
591 bool bottom_patch_bc = false;
592 std::string mpi_strategy = "auto";
593 bool write_mpi_metric = false;
594 int jha_test = 0;
595 if (input.cmdOptionExists("-jha2021Table2Test"))
596 jha_test = std::stoi(input.getCmdOption("-jha2021Table2Test"));
597 else if (input.cmdOptionExists("-jha2021Table2"))
598 jha_test = 1;
599 if (jha_test > 0) {
601
602
603 final_time = 0.04;
604 num_steps = 200000;
605 horizon_in = 0.0006;
606
607
608 zero_ic = false;
609 assert_cr = true;
610 cr_ref = c.cr;
611 cr_tol = 0.05;
612 eps_n = c.eps;
613 damping_on = c.eps < 1.0 - 1.0e-12;
614 }
615 if (input.cmdOptionExists("-finalTime"))
616 final_time = std::stod(input.getCmdOption("-finalTime"));
617 if (input.cmdOptionExists("-numSteps"))
618 num_steps = std::stoul(input.getCmdOption("-numSteps"));
619 if (input.cmdOptionExists("-zeroIC"))
620 zero_ic = true;
621 if (input.cmdOptionExists("-meshSize"))
622 mesh_size_in = std::stod(input.getCmdOption("-meshSize"));
623 if (input.cmdOptionExists("-horizon"))
624 horizon_in = std::stod(input.getCmdOption("-horizon"));
625 if (input.cmdOptionExists("-epsN")) {
626 eps_n = std::stod(input.getCmdOption("-epsN"));
627 damping_on = eps_n < 1.0 - 1.0e-12;
628 }
629 if (input.cmdOptionExists("-betaNFactor"))
630 beta_n_factor = std::stod(input.getCmdOption("-betaNFactor"));
631 if (input.cmdOptionExists("-dampingLaw"))
632 damping_law = input.getCmdOption("-dampingLaw");
633 if (input.cmdOptionExists("-frictionLaw"))
634 friction_law = input.getCmdOption("-frictionLaw");
635 if (input.cmdOptionExists("-enableFriction"))
636 friction_on = true;
637 if (input.cmdOptionExists("-mu"))
638 friction_mu = std::stod(input.getCmdOption("-mu"));
639 if (input.cmdOptionExists("-icVx"))
640 ic_vx = std::stod(input.getCmdOption("-icVx"));
641 if (input.cmdOptionExists("-assertLateralVx")) {
642 assert_lat = true;
643 lat_min = std::stod(input.getCmdOption("-assertLateralVx"));
644 assert_cr = false;
645 }
646 if (input.cmdOptionExists("-assertCR")) {
647 assert_cr = true;
648 cr_ref = std::stod(input.getCmdOption("-assertCR"));
649 }
650 if (input.cmdOptionExists("-crTol"))
651 cr_tol = std::stod(input.getCmdOption("-crTol"));
652 if (input.cmdOptionExists("-bottomPatchBC"))
653 bottom_patch_bc = true;
654 if (input.cmdOptionExists("-mpiStrategy"))
655 mpi_strategy = input.getCmdOption("-mpiStrategy");
656 if (input.cmdOptionExists("-writeMpiMetric"))
657 write_mpi_metric = true;
659 "final_time = {}, num_steps = {}, zero_ic = {}, eps_n = {}, C_bar = {}, "
660 "damping = {}, Damping_Law = {}, Friction_Law = {}, bottomPatchBC = {}, "
661 "MPI_Strategy = {}\n",
662 final_time, num_steps, zero_ic, eps_n, beta_n_factor, damping_on,
663 damping_law, friction_law, bottom_patch_bc, mpi_strategy));
664
665 namespace fs = std::filesystem;
666 const fs::path cwd = fs::current_path();
667
668 fs::path out_dir = cwd / "out";
669 fs::path inp_dir = cwd / "inp";
670
671 if (input.cmdOptionExists("-outputDir")) {
672 fs::path p = input.getCmdOption("-outputDir");
673 out_dir = p.is_absolute() ? std::move(p) : cwd / p;
674 }
675 if (input.cmdOptionExists("-inputDir")) {
676 fs::path p = input.getCmdOption("-inputDir");
677 inp_dir = p.is_absolute() ? std::move(p) : cwd / p;
678 } else if (input.cmdOptionExists("-outputDir")) {
679
680 inp_dir = out_dir.parent_path() / "inp";
681 }
682
683 fs::create_directories(out_dir);
684 fs::create_directories(inp_dir);
685
687 const fs::path mesh1 = inp_dir / "mesh_cir_1.msh";
688 const fs::path mesh2 = inp_dir / "mesh_cir_2.msh";
689 const bool inbuilt_mesh = input.cmdOptionExists("-inbuiltMesh");
690 const bool file_mesh = jha_test > 0 && !inbuilt_mesh;
691 if (file_mesh) {
692#ifndef JHA2021_MESH_DIR
693 throw std::runtime_error("JHA2021_MESH_DIR is not set (v0.1.0 circ_damp meshes).");
694#else
695 const fs::path src(JHA2021_MESH_DIR);
696 fs::copy_file(src / "mesh_cir_1.msh", mesh1, fs::copy_options::overwrite_existing);
697 fs::copy_file(src / "mesh_cir_2.msh", mesh2, fs::copy_options::overwrite_existing);
698 util::io::print(std::format(
"Using v0.1.0 circ_damp meshes from {}\n", src.string()));
699#endif
700 } else if (jha_test > 0)
701 util::io::print(
"Using in-process Gmsh (gmsh_builtin_mesh) instead of frozen v0.1.0 .msh\n");
702
703 util::io::print(std::format(
"Output directory (VTU, log.txt): {}\n", fs::absolute(out_dir).
string()));
704 util::io::print(std::format(
"Input directory (input.json, meshes): {}\n", fs::absolute(inp_dir).
string()));
705
706 auto inputJson =
buildInputJson(output_path_for_deck, mesh1, mesh2, final_time,
707 num_steps, zero_ic, mesh_size_in, horizon_in,
708 damping_on, eps_n, jha_test > 0, file_mesh,
709 beta_n_factor, damping_law,
710 friction_law, friction_on, friction_mu,
711 ic_vx, bottom_patch_bc, mpi_strategy);
712
713 const fs::path input_json_path = inp_dir / "input.json";
714 {
715 std::ofstream os(input_json_path);
716 if (!os)
717 throw std::runtime_error("Failed to open " + input_json_path.string() + " for writing.");
718 os << inputJson.dump(2);
719 }
720 util::io::print(std::format(
"Wrote deck to {}\n", fs::absolute(input_json_path).
string()));
721
722
723
724 if (input.cmdOptionExists("-deckOnly")) {
727 return 0;
728 }
729 if (jha_test > 0) {
730 const auto &p1 = inputJson["Particle_Generation"]["Data"]["1"];
731 const auto &v = inputJson["IC"]["Set_1"]["Constant_Velocity"]["Velocity_Vector"];
732 const double y1 = p1.at("y").get<double>();
733 const double gap0 = y1 - 0.001 - 0.001 - 0.001;
735 "Table 2 kinematics: top y = {}, surface gap = {}, v_y = {}\n",
736 y1, gap0, v.at(1).get<double>()));
737 }
738
739 auto deck = std::make_shared<inp::Input>(inputJson);
740
744 if (assert_cr) {
745 auto p = std::make_unique<RestitutionProbe>(0.001);
746 probe = p.get();
747 dem.setPostprocess(std::move(p));
748 } else if (assert_lat) {
749 auto p = std::make_unique<LateralProbe>(ic_vx);
750 lat_probe = p.get();
751 dem.setPostprocess(std::move(p));
752 } else if (write_mpi_metric) {
753 const size_t ts_every = std::max<size_t>(1, num_steps / 100);
754 dem.setPostprocess(
755 std::make_unique<MpiMetricTsProbe>(out_dir, ts_every));
756 }
757 dem.run(deck);
758
759 if (input.cmdOptionExists("-requireContact")) {
760 const float zmax =
761 dem.d_Z.empty() ? 0.f : *std::max_element(dem.d_Z.begin(), dem.d_Z.end());
762 util::io::print(std::format(
"requireContact: max Damage_Z = {}\n", zmax));
763 if (zmax <= 0.f) {
764 util::io::print(
"requireContact: no damage; particles did not contact.\n");
765 return EXIT_FAILURE;
766 }
767 }
768
769 if (assert_cr) {
770 {
771 const fs::path gap_csv = out_dir / "gap.csv";
772 std::ofstream gs(gap_csv);
773 gs << "t,gap\n";
774 for (const auto &s : probe->samples())
775 gs << s.first << "," << s.second << "\n";
776 util::io::print(std::format(
"Wrote {}\n", fs::absolute(gap_csv).
string()));
777 }
778 const double cr = probe->
CR();
780 "assertCR: contacted={}, min_gap={} (t={}), H1={} (t={}), CR={}, ref={}, tol={}\n",
782 probe->
tH1(), cr, cr_ref, cr_tol));
783 if (cr > 1.02) {
785 "assertCR: CR > 1.02 on the 1 mm energy-equivalent drop "
786 "(surface gap = horizon, leftover height already in v_y). "
787 "Unphysical energy gain.\n");
788 return EXIT_FAILURE;
789 }
790 if (!probe->
contacted() || std::abs(cr - cr_ref) > cr_tol) {
791 util::io::print(
"assertCR: coefficient of restitution out of range.\n");
792 return EXIT_FAILURE;
793 }
794 }
795
796 if (assert_lat) {
798 const double vmax = lat_probe->
maxAbsVx();
799 const double ic = lat_probe->
icAbsVx();
801 "assertLateralVx: contacted={}, max|vx|={}, min|vx|_after_contact={}, "
802 "ic|vx|={}, slow_factor_max={}\n",
803 lat_probe->
contacted(), vmax, vmin, ic, lat_min));
806 return EXIT_FAILURE;
807 }
808
809 if (!(ic > 0.) || !(vmin < lat_min * ic)) {
811 "assertLateralVx: friction did not slow lateral speed enough "
812 "(or kinematics missed).\n");
813 return EXIT_FAILURE;
814 }
815 }
816
817 if (write_mpi_metric) {
818 double max_u = 0.;
819 for (const auto &u : dem.d_u)
820 max_u = std::
max(max_u, u.length());
821 double com1x = 0., com1y = 0.;
822 if (dem.d_particlesListTypeParticle.size() > 1) {
823 const auto c = dem.d_particlesListTypeParticle[1]->getXCenter();
824 com1x = c.d_x;
825 com1y = c.d_y;
826 }
828 MPI_Allreduce(MPI_IN_PLACE, &max_u, 1, MPI_DOUBLE, MPI_MAX,
830
831
832
833 }
835 std::ofstream os(out_dir / "mpi_metric.txt");
836 os << std::format("{:.12e} {:.12e} {:.12e}\n", max_u, com1x, com1y);
838 "mpi_metric: max|u|={:.12e} grain1_com=({:.12e},{:.12e})\n", max_u,
839 com1x, com1y));
840 }
841 }
842
844 return EXIT_SUCCESS;
845}
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)