684 {
687
688 unsigned n_threads = 1;
689 if (input.cmdOptionExists("-nThreads"))
690 n_threads = static_cast<unsigned>(std::stoi(input.getCmdOption("-nThreads")));
692
693 const bool quick = input.cmdOptionExists("-quick");
694 const bool trask = input.cmdOptionExists("-traskDisp");
695 const bool bhat = input.cmdOptionExists("-bhatKW");
696
697 const bool dim3 = input.cmdOptionExists("-dim3");
698
699 namespace fs = std::filesystem;
700
701 std::string run_tag =
702 quick ? "quick" : (trask ? "trask" : (bhat ? "bhat" : (dim3 ? "silling3d" : "lit")));
703
704 fs::path base = fs::current_path() / "runs_new" / run_tag;
705 if (input.cmdOptionExists("-outputDir"))
706 base = input.getCmdOption("-outputDir");
707 const fs::path out_dir = base / "out";
708 const fs::path inp_dir = base / "inp";
709 fs::create_directories(out_dir);
710 fs::create_directories(inp_dir);
711 std::cout << std::format("notched_impact: outputDir={}\n", base.string());
712
713
714 double W = 0.200;
715 double H = 0.100;
716 double notch_depth = 0.050;
717 double notch_half = 0.025;
718 double notch_w = 0.0015;
719 double plate_thickness = 0.009;
720
721
722
723
724 double Iw = 0.050;
725 double Ih = 0.100;
726
727 double mesh_size = 0.001;
728 double horizon = 3.0 * mesh_size;
729 double Rc_factor = 0.95;
730
731
732
733
734
735 double gap = 1.5 * Rc_factor * mesh_size;
736
737
738
739 double rho = 8000.0;
740 double E = 191.0e9;
741 double Kbulk = 159.2e9;
742 double nu = 0.5 * (1.0 - E / (3.0 * Kbulk));
743 double G = E / (2.0 * (1.0 + nu));
744 double Gc = 42408.0;
745 double v_impact = 32.0;
746 double dt = 2.5e-9;
747
748
749 double final_time = 1.7e-4;
750 if (input.cmdOptionExists("-Gc"))
751 Gc = std::stod(input.getCmdOption("-Gc"));
752 if (input.cmdOptionExists("-finalTime"))
753 final_time = std::stod(input.getCmdOption("-finalTime"));
754 if (input.cmdOptionExists("-vImpact"))
755 v_impact = std::stod(input.getCmdOption("-vImpact"));
756 if (input.cmdOptionExists("-meshSize")) {
757 mesh_size = std::stod(input.getCmdOption("-meshSize"));
758 horizon = 3.0 * mesh_size;
759 }
760 if (input.cmdOptionExists("-horizon"))
761 horizon = std::stod(input.getCmdOption("-horizon"));
762 if (input.cmdOptionExists("-horizonFactor"))
763 horizon = std::stod(input.getCmdOption("-horizonFactor")) * mesh_size;
764
765 if (quick) {
766 W = 0.040;
767 H = 0.020;
768 notch_depth = 0.5 * H;
769 notch_half = 0.125 * W;
770 mesh_size = H / 16.0;
771 notch_w = std::max(mesh_size, 0.1 * notch_half);
772 Iw = 0.008;
773 Ih = 0.004;
774 gap = 1.5 * Rc_factor * mesh_size;
775 horizon = 3.0 * mesh_size;
776 rho = 1200.0;
777 E = 1.23e9;
778 Kbulk = 2.0e9;
779 nu = 0.5 * (1.0 - E / (3.0 * Kbulk));
780 G = E / (2.0 * (1.0 + nu));
781 Gc = 424.0;
782 dt = 1.0e-8;
783 final_time = 1.0e-4;
784 }
785
786
787
788
789
791 (M_PI * std::pow(horizon, dim3 ? 5 : 4));
792 const size_t num_steps = static_cast<size_t>(std::llround(final_time / dt));
793
795 if (trask) {
797 inp_dir / "mesh_plate.msh", W, H, notch_half, notch_w,
798 notch_depth, mesh_size, horizon, rho, E, Kbulk, G, Gc,
799 v_impact, final_time, num_steps);
800 } else if (bhat) {
803 inp_dir / "mesh_impactor.msh", W, H, notch_half, notch_w, notch_depth, Iw, Ih, gap,
804 mesh_size, horizon, Rc_factor, rho, E, Kbulk, G, Gc, v_impact, final_time, num_steps);
805 } else {
808 inp_dir / "mesh_impactor.msh", W, H, notch_half, notch_w, notch_depth, Iw, Ih, gap,
809 mesh_size, horizon, Rc_factor, Kn, rho, E, Kbulk, G, Gc, v_impact, final_time,
810 num_steps, dim3 ? plate_thickness : 0.);
811 }
812
813
814 if (input.cmdOptionExists("-selfContact")) {
815 const auto sc = input.getCmdOption("-selfContact");
816 input_json["Model"]["Self_Contact"] = sc;
817 std::cout << std::format("notched_impact: Self_Contact override -> {}\n", sc);
818 }
819 if (input.cmdOptionExists("-bondBreak")) {
820 const auto bb = input.getCmdOption("-bondBreak");
821 input_json["Model"]["Bond_Break"] = bb;
822 std::cout << std::format("notched_impact: Bond_Break override -> {}\n", bb);
823 }
824 if (input.cmdOptionExists("-knScale")) {
825 const double s = std::stod(input.getCmdOption("-knScale"));
826 for (auto &kv : input_json["Contact"].items()) {
827 if (!kv.value().is_object() || !kv.value().contains("Kn"))
828 continue;
829 kv.value()["Kn"] = kv.value()["Kn"].get<double>() * s;
830 }
831 std::cout << std::format("notched_impact: Contact Kn scaled by {}\n", s);
832 }
833 {
834 std::ofstream os(inp_dir / "input.json");
835 os << input_json.dump(2);
836 }
837
838 auto deck = std::make_shared<inp::Input>(input_json);
840 dem.init();
841
842 const double y_top = 0.5 * H;
843 const double y_tip = y_top - notch_depth;
844
845
846 const size_t n_pre =
849 if (n_pre < 10) {
850 std::cerr << "notched_impact: expected prenotch bonds, got " << n_pre << "\n";
851 return 1;
852 }
853
854 const char *mode_str =
855 quick ? "quick"
856 : (trask ? "trask_disp"
857 : (bhat ? "bhat_kw" : (dim3 ? "silling_3d" : "silling_impact")));
858 std::cout << std::format(
859 "notched_impact: mode={} nodes={} prenotch={} h={:.4e} ε={:.4e} notch_w={:.4e} "
860 "Gc={:.0f} E={:.3e} v={:.1f} Nt={} T={:.3e}\n",
861 mode_str, dem.d_x.size(), n_pre, mesh_size, horizon, notch_w, Gc, E, v_impact, num_steps,
862 final_time);
863
864
865
866
867 std::vector<float> arrival(dem.d_x.size(), -1.f);
868 const size_t sample_every = std::max<size_t>(1, num_steps / 400);
869 auto sampleArrivals = [&]() {
870 for (size_t i = 0; i < dem.d_xRef.size(); ++i) {
871 if (arrival[i] >= 0.f || dem.d_ptId[i] != 0)
872 continue;
874 arrival[i] = static_cast<float>(dem.d_time);
875 }
876 };
877
878 dem.applyInitialCondition();
879 if (dem.performOutput())
880 dem.output();
881 dem.setCurrentDt(dem.timeStep());
882 dem.applyDisplacementBC();
883 dem.computeForces();
885 while (dem.currentStep() < dem.numTimeSteps()) {
886 dem.integrateStep();
887 if (dem.shouldOutput())
888 dem.output();
889 if (dem.currentStep() % sample_every == 0)
890 sampleArrivals();
891 dem.checkStop();
892 }
893 sampleArrivals();
894
895 double vmax = 0., phi_max = 0.;
896 size_t n_phi30 = 0;
897 for (size_t i = 0; i < dem.d_v.size(); ++i) {
898 vmax = std::max(vmax, dem.d_v[i].length());
899 if (dem.d_ptId[i] != 0)
900 continue;
901 const double phi =
nodePhi(dem, i);
902 phi_max = std::max(phi_max, phi);
903 if (phi >= 0.30)
904 ++n_phi30;
905 }
906 const float zmax =
907 dem.d_Z.empty() ? 0.f : *std::max_element(dem.d_Z.begin(), dem.d_Z.end());
908 std::cout << std::format(
909 "notched_impact diag: max|v|={:.3e} maxφ={:.3f} maxZ={:.3f} n(φ≥0.3)={}\n", vmax,
910 phi_max, zmax, n_phi30);
911
912 if (!(vmax < 8.0e3) || !std::isfinite(vmax)) {
913 std::cerr << "notched_impact: blow-up, max |v| = " << vmax << "\n";
914 return 1;
915 }
916
917 size_t n_extra = 0;
918 for (size_t i = 0; i < dem.d_neighPd.size(); ++i) {
919 if (dem.d_ptId[i] != 0)
920 continue;
921 for (size_t k = 0; k < dem.d_neighPd[i].size(); ++k) {
922 if (!dem.d_fracture_p->getBondState(i, k))
923 continue;
924 const size_t j = dem.d_neighPd[i][k];
925 if (dem.d_ptId[j] != 0)
926 continue;
927 const auto &xi = dem.d_xRef[i];
928 const auto &xj = dem.d_xRef[j];
929 const bool on_notch =
931 y_top + 0.01 * H) ||
933 y_top + 0.01 * H);
934 if (!on_notch)
935 ++n_extra;
936 }
937 }
938 if (n_extra < 20) {
939 std::cerr << "notched_impact: expected new broken bonds, got " << n_extra
940 << " (prenotch=" << n_pre << ")\n";
941 return 1;
942 }
943
944
945
946 {
947 std::ofstream csv(base / "damage.csv");
948 csv << "x_ref,y_ref,z_ref,x_cur,y_cur,z_cur,phi,phi_bond,arrival\n";
949 for (size_t i = 0; i < dem.d_xRef.size(); ++i) {
950 if (dem.d_ptId[i] != 0)
951 continue;
954 if (std::max(phi, phi_b) < 0.05)
955 continue;
956 csv << std::format("{:.6e},{:.6e},{:.6e},{:.6e},{:.6e},{:.6e},{:.4f},{:.4f},{:.6e}\n",
957 dem.d_xRef[i].d_x, dem.d_xRef[i].d_y, dem.d_xRef[i].d_z,
958 dem.d_x[i].d_x, dem.d_x[i].d_y, dem.d_x[i].d_z, phi, phi_b,
959 double(arrival[i]));
960 }
961 }
962
963 const double band_x = 0.45 * W;
964 const double band_y = 0.55 * H;
965 const double phi_cut = quick ? 0.20 : 0.35;
966 const double exclude_r = 2.0 * mesh_size;
967 const bool use_bond_dmg = bhat;
968 auto left =
fitCrackFromPhi(dem, -notch_half, y_tip, -1.0, phi_cut, band_x, band_y, exclude_r,
969 mesh_size, use_bond_dmg);
970 auto right =
fitCrackFromPhi(dem, notch_half, y_tip, +1.0, phi_cut, band_x, band_y, exclude_r,
971 mesh_size, use_bond_dmg);
972 if (left.n_pts < 4 || right.n_pts < 4) {
973 std::cerr << "notched_impact: insufficient φ-ridge for path fit (L=" << left.n_pts
974 << " R=" << right.n_pts << ")\n";
975 return 1;
976 }
977 if (!(left.mean_dx < 0. && left.mean_dy > 0. && right.mean_dx > 0. && right.mean_dy > 0.)) {
978 std::cerr << std::format(
979 "notched_impact: tip damage not outward/down "
980 "(L dx={:.3e} dy={:.3e} R dx={:.3e} dy={:.3e})\n",
981 left.mean_dx, left.mean_dy, right.mean_dx, right.mean_dy);
982 return 1;
983 }
984
985 const double ang = 0.5 * (left.angle_to_notch_deg + right.angle_to_notch_deg);
986 std::cout << std::format(
987 "notched_impact PASS: prenotch={} new_broken={} ang_to_notch={:.1f}° "
988 "(L={:.1f}/{} R={:.1f}/{}) max|v|={:.3e}\n",
989 n_pre, n_extra, ang, left.angle_to_notch_deg, left.n_pts, right.angle_to_notch_deg,
990 right.n_pts, vmax);
991
992 dem.close();
993 return 0;
994}
nlohmann::ordered_json json
json buildBhatInputJson(const std::string &output_path, const std::filesystem::path &mesh_plate, const std::filesystem::path &mesh_impactor, double W, double H, double notch_half, double notch_w, double notch_depth, double Iw, double Ih, double gap, double mesh_size, double horizon, double Rc_factor, double rho, double E, double K, double G, double Gc, double v_impact, double final_time, size_t num_steps)
std::string directoryPathWithTrailingSep(const std::filesystem::path &dir)
size_t applyNotchMidplanes(PeriDEMModel &dem, double notch_half, double y_tip, double y_top)
json buildTraskInputJson(const std::string &output_path, const std::filesystem::path &mesh_plate, double W, double H, double notch_half, double notch_w, double notch_depth, double mesh_size, double horizon, double rho, double E, double K, double G, double Gc, double v_impact, double final_time, size_t num_steps)
bool segmentCrossesVertical(double x0, double y0, double x1, double y1, double x_line, double y_lo, double y_hi)
size_t applyNotchSlots(PeriDEMModel &dem, double notch_half, double notch_w, double y_lo, double y_hi)
json buildImpactInputJson(const std::string &output_path, const std::filesystem::path &mesh_plate, const std::filesystem::path &mesh_impactor, double W, double H, double notch_half, double notch_w, double notch_depth, double Iw, double Ih, double gap, double mesh_size, double horizon, double Rc_factor, double Kn, double rho, double E, double K, double G, double Gc, double v_impact, double final_time, size_t num_steps, double thickness=0.)
double nodePhi(const PeriDEMModel &dem, size_t i)
CrackFit fitCrackFromPhi(const PeriDEMModel &dem, double tip_x, double tip_y, double outward_sign, double phi_cut, double band_x, double band_y, double exclude_r, double bin_h, bool use_bond_count=false)
double nodeDamageForFit(const PeriDEMModel &dem, size_t i, bool use_bond_count)
void applyRigidBodyConstraint(data::ModelData &data)
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.
double harmonicMean(const double &m1, const double &m2)