PeriDEM 0.3.0
PeriDEM -- Peridynamics-based high-fidelity model for granular media
Loading...
Searching...
No Matches
main.cpp File Reference
#include "inp/deckIncludes.h"
#include "util/io.h"
#include "util/function.h"
#include "util/parallelUtil.h"
#include "geom/geomObjectsUtil.h"
#include "material/materialUtil.h"
#include "periDEMModel.h"
#include "particle/baseParticle.h"
#include "particle/particleMpi.h"
#include "postprocess/postprocess.h"
#include <mpi.h>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <format>
#include <fstream>
#include <memory>
#include <stdexcept>
#include <thread>
#include <utility>
#include <vector>
Include dependency graph for main.cpp:

Go to the source code of this file.

Data Structures

struct  Jha2021Table2
 
class  RestitutionProbe
 
class  LateralProbe
 
class  MpiMetricTsProbe
 

Namespaces

namespace  anonymous_namespace{main.cpp}
 

Functions

Jha2021Table2 jha2021Table2 (int test_id)
 
std::string anonymous_namespace{main.cpp}::directoryPathWithTrailingSep (const std::filesystem::path &dir)
 
json anonymous_namespace{main.cpp}::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")
 
int main (int argc, char *argv[])
 

Function Documentation

◆ jha2021Table2()

Jha2021Table2 jha2021Table2 ( int  test_id)

Definition at line 75 of file main.cpp.

75 {
76 static const Jha2021Table2 k[] = {
77 {1.0, 0.996}, {0.95, 0.925}, {0.9, 0.861}, {0.85, 0.801}, {0.8, 0.744}};
78 if (test_id < 1 || test_id > 5)
79 throw std::runtime_error("jha2021Table2Test must be 1..5");
80 return k[test_id - 1];
81}

Referenced by main().

Here is the caller graph for this function:

◆ main()

int main ( int  argc,
char *  argv[] 
)

Definition at line 549 of file main.cpp.

549 {
550
551 util::parallel::initMpi(argc, argv);
553 util::io::print(std::format("Initialized MPI. MPI size = {}, MPI rank = {}\n", mpiSize, mpiRank));
555
556 util::io::InputParser input(argc, argv);
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 }
566 util::io::print(std::format("Number of threads = {}\n", util::parallel::getNThreads()));
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) {
600 const auto c = jha2021Table2(jha_test);
601 // Paper §4.1 / v0.1.0 circ_damp: T=0.04 s, Δt=0.2 μs, horizon 0.6 mm.
602 // Mesh is the frozen v0.1.0 .msh (hmin = 0.1423 mm), not in-process Gmsh.
603 final_time = 0.04;
604 num_steps = 200000;
605 horizon_in = 0.0006;
606 // v0.1.0 circ_damp: not drop-from-rest. Gap is shortened to the horizon
607 // and the leftover 0.4 mm of the 1 mm drop is an impact velocity.
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; // skip CR gate from table2 defaults for this lateral check
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;
658 util::io::print(std::format(
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 // Place inp next to out: .../run/out -> .../run/inp
680 inp_dir = out_dir.parent_path() / "inp";
681 }
682
683 fs::create_directories(out_dir);
684 fs::create_directories(inp_dir);
685
686 const std::string output_path_for_deck = directoryPathWithTrailingSep(out_dir);
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 // -deckOnly stops after the deck is written. The Python comparison reads
723 // the deck and does not need this driver to run the simulation.
724 if (input.cmdOptionExists("-deckOnly")) {
725 util::io::print("deck written; -deckOnly, not running\n");
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; // y_top - R_bottom_center - R_bot - R_top
734 util::io::print(std::format(
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
741 PeriDEMModel dem(deck);
742 RestitutionProbe *probe = nullptr;
743 LateralProbe *lat_probe = nullptr;
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();
779 util::io::print(std::format(
780 "assertCR: contacted={}, min_gap={} (t={}), H1={} (t={}), CR={}, ref={}, tol={}\n",
781 probe->contacted(), probe->minGap(), probe->tMin(), probe->H1(),
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) {
797 const double vmin = lat_probe->minAbsVxAfterContact();
798 const double vmax = lat_probe->maxAbsVx();
799 const double ic = lat_probe->icAbsVx();
800 util::io::print(std::format(
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));
804 if (!lat_probe->contacted()) {
805 util::io::print("assertLateralVx: particles never contacted.\n");
806 return EXIT_FAILURE;
807 }
808 // lat_min is the maximum allowed ratio min_vx/ic_vx after contact (e.g. 0.95).
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 // Center node is unique; take from owning rank via MAX of abs then
831 // broadcast of values — simpler: Allreduce SUM after zeroing non-owners
832 // is wrong for COM. After DOF sync every rank has the same center x.
833 }
834 if (util::parallel::mpiRank() == 0) {
835 std::ofstream os(out_dir / "mpi_metric.txt");
836 os << std::format("{:.12e} {:.12e} {:.12e}\n", max_u, com1x, com1y);
837 util::io::print(std::format(
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 icAbsVx() const
Definition main.cpp:432
double minAbsVxAfterContact() const
Definition main.cpp:429
bool contacted() const
Definition main.cpp:427
double maxAbsVx() const
Definition main.cpp:428
bool contacted() const
Definition main.cpp:379
double H1() const
Definition main.cpp:380
double CR() const
Definition main.cpp:387
double tH1() const
Definition main.cpp:383
double tMin() const
Definition main.cpp:382
double minGap() const
Definition main.cpp:381
Input command line argument parser.
Definition inputParser.h:28
std::string directoryPathWithTrailingSep(const std::filesystem::path &dir)
Definition main.cpp:34
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")
Definition main.cpp:95
std::string printStr(const T &msg, int nt=print_default_tab)
Returns formatted string for output.
Definition io.h:96
void print(const T &msg, int nt=print_default_tab, int printMpiRank=print_default_mpi_rank)
Prints formatted information.
Definition io.h:171
T max(const std::vector< T > &data)
Returns the maximum from list of data.
Definition vecMethods.h:76
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)
Definition main.cpp:75

References anonymous_namespace{main.cpp}::buildInputJson(), util::io::InputParser::cmdOptionExists(), RestitutionProbe::contacted(), LateralProbe::contacted(), RestitutionProbe::CR(), data::ModelData::d_particlesListTypeParticle, data::ModelData::d_u, data::ModelData::d_Z, util::parallel::finalizeMpi(), util::io::InputParser::getCmdOption(), util::parallel::getMpiStatus(), util::parallel::getNThreads(), RestitutionProbe::H1(), LateralProbe::icAbsVx(), util::parallel::initMpi(), util::parallel::initNThreads(), util::parallel::isMpiEnabled(), jha2021Table2(), main(), LateralProbe::maxAbsVx(), LateralProbe::minAbsVxAfterContact(), RestitutionProbe::minGap(), util::parallel::mpiComm(), util::parallel::mpiRank(), util::parallel::mpiSize(), util::io::print(), PeriDEMModel::run(), RestitutionProbe::samples(), PeriDEMModel::setPostprocess(), RestitutionProbe::tH1(), and RestitutionProbe::tMin().

Here is the call graph for this function: