49 namespace fs = std::filesystem;
50 fs::path n = fs::absolute(dir).lexically_normal();
51 std::string s = n.string();
52 if (!s.empty() && s.back() !=
'/' && s.back() !=
'\\')
53 s += fs::path::preferred_separator;
57struct PackedParticle {
58 double x{}, y{}, z{}, r{}, theta{};
62 double y_lo,
double R,
double gap) {
63 std::vector<PackedParticle> particles;
64 particles.reserve(
static_cast<size_t>(ncols * nrows));
65 const double step = 2.0 * R + gap;
66 const double x0 = x_lo + gap + R;
67 const double y0 = y_lo + gap + R;
68 for (
int j = 0; j < nrows; ++j) {
69 for (
int i = 0; i < ncols; ++i) {
71 p.x = x0 +
static_cast<double>(i) * step;
72 p.y = y0 +
static_cast<double>(j) * step;
76 particles.push_back(p);
84 const std::filesystem::path &mesh_cir,
85 const std::filesystem::path &mesh_fixed,
86 const std::filesystem::path &mesh_moving,
double final_time,
87 size_t num_steps,
bool file_mesh,
bool write_meshes,
88 size_t search_interval,
int ncols,
int nrows,
89 const std::string &mpi_strategy =
"auto") {
91 if (ncols < 1 || nrows < 1)
92 throw std::runtime_error(
"jha2021_comp: ncols and nrows must be >= 1");
93 const int ngrains = ncols * nrows;
95 const std::vector<double> center = {0.0, 0.0, 0.0};
96 const double R = 0.001;
97 const double mesh_size = R / 5.0;
98 const double horizon = 3.0 * mesh_size;
101 const double h_est = 0.7 * mesh_size;
102 const double Rc_est = 0.95 * h_est;
103 const double particle_padding = 1.15 * Rc_est;
104 const double wpd = particle_padding;
105 const double rwp = horizon + wpd;
106 constexpr double wall_vy = -0.06;
108 const double Lin = 2.0 * particle_padding + 2.0 * R +
109 static_cast<double>(ncols - 1) * (2.0 * R + particle_padding);
110 const double Win = 2.0 * particle_padding + 2.0 * R +
111 static_cast<double>(nrows - 1) * (2.0 * R + particle_padding);
113 const double wall_t = rwp - wpd;
114 const std::vector<double> mw_rect = {center[0] - wpd, Win, center[2],
115 Lin + wpd, Win + wall_t, center[2]};
116 const std::vector<double> cup_channel = {center[0] - rwp, center[1] - rwp,
117 Lin + rwp, Win + wall_t, wall_t,
120 std::vector<geom::GeomData> pGeomVec(3);
121 pGeomVec[0].d_geomName =
"circle";
122 pGeomVec[0].d_geomParams = {R, center[0], center[1], center[2]};
123 pGeomVec[1].d_geomName =
"open_rect_channel_2d";
124 pGeomVec[1].d_geomParams = cup_channel;
125 pGeomVec[2].d_geomName =
"rectangle";
126 pGeomVec[2].d_geomParams = mw_rect;
128 std::vector<PackedParticle> packed =
130 if (
static_cast<int>(packed.size()) != ngrains)
131 throw std::runtime_error(
"jha2021_comp: grid count mismatch");
133 const double poisson = 0.25;
134 const double rho = 1200.0;
135 const double K = 2.16e+7;
138 const double Gc = 50.0;
141 const double dt = final_time /
static_cast<double>(num_steps);
142 const double c_wave = std::sqrt(E / rho);
143 const double dt_cfl = h_est / c_wave;
145 "jha2021_comp: N={} ({}x{}), gap={:.6e} (1.15 Rc_est), wpd={:.6e}, "
146 "dt={:.6e} s, h/c={:.6e} s, dt/(h/c)={:.3f}\n",
147 ngrains, ncols, nrows, particle_padding, wpd, dt, dt_cfl, dt / dt_cfl));
149 const size_t num_outputs = 4;
150 const size_t dt_out_n = std::max<size_t>(1, num_steps / num_outputs);
151 const size_t test_dt_out_n = std::max<size_t>(1, dt_out_n / 10);
153 for (
auto &g : pGeomVec)
156 const util::Point cfix = pGeomVec[1].d_geom_p->center();
157 const util::Point cmov = pGeomVec[2].d_geom_p->center();
158 const size_t n_pack = packed.size();
159 const size_t n_wall_fixed = n_pack;
160 const size_t n_wall_moving = n_pack + 1;
161 const size_t n_total = n_pack + 2;
164 2, final_time, num_steps,
"finite_difference",
"central_difference",
true, 2,
165 "Multi_Particle", 0);
166 modelDeckJson[
"MPI_Strategy"] = mpi_strategy;
168 std::vector<std::string> out_tags = {
"Displacement",
"Velocity",
"Force",
"Damage_Z",
169 "Damage",
"Particle_ID",
"Contact_Nodes"};
171 "vtu", output_path_for_deck, out_tags, dt_out_n, 1,
true,
"zlib",
true,
172 test_dt_out_n,
"0",
true);
177 "Displacement_BC",
false,
geom::GeomData(), {n_wall_fixed}, {},
"", {},
"", {},
178 {1, 2},
true,
"", {});
180 set2[
"Particle_List"] = std::vector<size_t>{n_wall_moving};
181 set2[
"Direction"] = std::vector<size_t>{2};
182 set2[
"Time_Function"] =
json{{
"Type",
"linear"}, {
"Parameters", std::vector<double>{wall_vy}}};
183 set2[
"Spatial_Function"] =
json{{
"Type",
"constant"}};
184 bcDeckJson[
"Displacement_BC"][
"Set_2"] = set2;
186 json pDeckJson = json::object();
189 auto meshEntry = [&](
const std::filesystem::path &f) ->
json {
191 return json{{
"File", f.string()}};
192 return json{{
"File", f.string()},
195 {
"Info",
"gmsh_builtin_mesh"},
196 {
"Mesh_Size", mesh_size},
197 {
"Write_Mesh_File", write_meshes}}}};
199 pDeckJson[
"Mesh"] =
json{{
"Sets", 3},
200 {
"Set_1", meshEntry(mesh_cir)},
201 {
"Set_2", meshEntry(mesh_fixed)},
202 {
"Set_3", meshEntry(mesh_moving)}};
209 pDeckJson[
"Material"] = matRoot;
212 0.95,
true,
true,
false, Kn, 0.95, 0.0, 1.0, 100.0, 1.0, 0.0, K);
214 contactRoot[
"Set_1_1"] = contact_base;
215 contactRoot[
"Set_1_2"] = contact_base;
216 contactRoot[
"Set_2_2"] = contact_base;
217 pDeckJson[
"Contact"] = contactRoot;
218 pDeckJson[
"Neighbor"] =
222 pGenJson[
"Random_Rotation"] =
false;
223 pGenJson[
"Data"][
"N"] = n_total;
224 for (
size_t pi = 0; pi < n_pack; ++pi) {
225 const auto &p = packed[pi];
226 pGenJson[
"Data"][std::to_string(pi)] =
json{{
"x", p.x},
231 {
"geom_id", size_t(0)},
232 {
"mat_id", size_t(0)},
233 {
"contact_id", size_t(0)}};
235 pGenJson[
"Data"][std::to_string(n_pack)] =
json{{
"x", cfix.
d_x},
240 {
"geom_id", size_t(1)},
241 {
"mat_id", size_t(1)},
242 {
"contact_id", size_t(1)},
244 pGenJson[
"Data"][std::to_string(n_pack + 1)] =
json{{
"x", cmov.
d_x},
249 {
"geom_id", size_t(2)},
250 {
"mat_id", size_t(1)},
251 {
"contact_id", size_t(1)},
253 pDeckJson[
"Particle_Generation"] = pGenJson;
256 "jha2021_comp: Lin={:.6f}, Win={:.6f}, horizon={:.6f}, plate v_y={}, Wall_Id={}\n",
257 Lin, Win, horizon, wall_vy, n_wall_moving));
259 return json{{
"Comment",
"jha2021_comp_contact"},
260 {
"Model", modelDeckJson},
261 {
"Output", outputDeckJson},
262 {
"Force_BC", bcDeckJson[
"Force_BC"]},
263 {
"Displacement_BC", bcDeckJson[
"Displacement_BC"]},
264 {
"Particle", pDeckJson[
"Particle"]},
265 {
"Mesh", pDeckJson[
"Mesh"]},
266 {
"Material", pDeckJson[
"Material"]},
267 {
"Contact", pDeckJson[
"Contact"]},
268 {
"Neighbor", pDeckJson[
"Neighbor"]},
269 {
"Particle_Generation", pDeckJson[
"Particle_Generation"]},
270 {
"Test",
json{{
"Test_Name",
"compressive_test"},
272 json{{
"Wall_Id", n_wall_moving},
273 {
"Wall_Force_Direction", 2}}}}}};
277 const std::vector<PackedParticle> &packed) {
278 std::ofstream os(csv);
279 os <<
"i, x, y, z, r, o\n";
280 for (
const auto &p : packed)
281 os << std::format(
"0, {:.6f}, {:.6f}, {:.6f}, {:.6f}, 0.000000\n", p.x, p.y, p.z, p.r);
285 std::ifstream is(csv);
289 std::getline(is, line);
291 while (std::getline(is, line)) {
294 const auto c2 = line.rfind(
',');
295 if (c2 == std::string::npos)
297 max_abs = std::max(max_abs, std::abs(std::stod(line.substr(c2 + 1))));
305 : d_outDir(std::move(out_dir)), d_interval(interval) {
307 std::filesystem::create_directories(d_outDir /
"nodal");
308 d_metricOs.open(d_outDir /
"mpi_metric_ts.csv");
309 d_metricOs <<
"step,t,max_u,com0x,com0y\n";
316 if (
data.d_particlesListTypeAll.size() < 3)
320 Rc =
data.d_particleDeck_p->d_contactDeck.getContact(0, 0).d_contactR;
327 double min_gap = 1.0e9;
329 const auto &all =
data.d_particlesListTypeAll;
330 for (
size_t i = 0; i < all.size(); ++i) {
331 if (all[i]->getGroupId(
"contact_id") != 0)
333 for (
size_t j = i + 1; j < all.size(); ++j) {
334 if (all[j]->getGroupId(
"contact_id") != 0)
336 const double gap = all[i]->getXCenter().dist(all[j]->getXCenter()) -
337 all[i]->d_geom_p->boundingRadius() -
338 all[j]->d_geom_p->boundingRadius();
339 min_gap = std::min(min_gap, gap);
344 if (d_gap0 == 0. && min_gap < 1.0e8)
346 if (min_gap < d_minGap) {
348 d_tMin =
data.d_time;
350 if (n_in > d_maxPairs) {
353 "grain contact: {} pair(s) in Rc at t={:.6e}, min_gap={:.6e}, Rc={:.6e}\n",
354 n_in,
data.d_time, min_gap, Rc));
358 double minGap()
const {
return d_minGap; }
359 double gap0()
const {
return d_gap0; }
360 double Rc()
const {
return d_Rc; }
361 double tMin()
const {
return d_tMin; }
367 if (
data.d_pdDofMpi) {
368 if (i >=
data.d_pdNodePartition.size())
370 return static_cast<int>(
data.d_pdNodePartition[i]) == rank;
372 for (
const auto *p :
data.d_particlesListTypeAll) {
373 const size_t i0 = p->d_globStart;
374 const size_t i1 = i0 + p->getNumNodes();
375 if (i < i0 || i >= i1)
385 if (d_outDir.empty() || d_interval == 0)
387 const size_t nstep =
data.currentStep();
388 if (nstep % d_interval != 0 && nstep <
data.numTimeSteps())
391 for (
const auto &u :
data.d_u)
392 max_u = std::max(max_u, u.length());
394 MPI_Allreduce(MPI_IN_PLACE, &max_u, 1, MPI_DOUBLE, MPI_MAX,
396 double com0x = 0., com0y = 0.;
397 if (!
data.d_particlesListTypeParticle.empty()) {
398 const auto c =
data.d_particlesListTypeParticle[0]->getXCenter();
403 d_metricOs << std::format(
"{},{:.12e},{:.12e},{:.12e},{:.12e}\n", nstep,
404 data.d_time, max_u, com0x, com0y);
407 const size_t n =
data.d_u.size();
408 std::vector<double> buf(6 * n, 0.);
409 for (
size_t i = 0; i < n; ++i) {
412 buf[6 * i + 0] =
data.d_u[i].d_x;
413 buf[6 * i + 1] =
data.d_u[i].d_y;
414 buf[6 * i + 2] =
data.d_u[i].d_z;
415 buf[6 * i + 3] =
data.d_v[i].d_x;
416 buf[6 * i + 4] =
data.d_v[i].d_y;
417 buf[6 * i + 5] =
data.d_v[i].d_z;
420 MPI_Allreduce(MPI_IN_PLACE, buf.data(),
static_cast<int>(buf.size()),
425 std::ofstream xr(d_outDir /
"nodal" /
"x_ref.bin", std::ios::binary);
426 const uint32_t nn =
static_cast<uint32_t
>(n);
427 xr.write(
reinterpret_cast<const char *
>(&nn),
sizeof(nn));
428 for (
size_t i = 0; i < n; ++i) {
429 const double p[3] = {
data.d_xRef[i].d_x,
data.d_xRef[i].d_y,
431 xr.write(
reinterpret_cast<const char *
>(p),
sizeof(p));
435 std::ofstream os(d_outDir /
"nodal" / std::format(
"uv_{:06d}.bin", nstep),
437 const char magic[4] = {
'P',
'D',
'U',
'V'};
438 const uint32_t step32 =
static_cast<uint32_t
>(nstep);
439 const uint32_t nn =
static_cast<uint32_t
>(n);
441 os.write(
reinterpret_cast<const char *
>(&step32),
sizeof(step32));
442 os.write(
reinterpret_cast<const char *
>(&nn),
sizeof(nn));
443 os.write(
reinterpret_cast<const char *
>(buf.data()),
444 static_cast<std::streamsize
>(buf.size() *
sizeof(double)));
447 double d_minGap = 1.0e9;
453 size_t d_interval = 0;
455 bool d_wroteXref =
false;
460int main(
int argc,
char *argv[]) {
465 unsigned int nThreads;
469 nThreads = std::thread::hardware_concurrency();
474 double final_time = 0.004;
475 size_t num_steps = 20000;
477 final_time = std::stod(input.
getCmdOption(
"-finalTime"));
479 num_steps = std::stoul(input.
getCmdOption(
"-numSteps"));
480 size_t search_interval = 40;
482 search_interval = std::stoul(input.
getCmdOption(
"-searchInterval"));
489 std::string mpi_strategy =
"auto";
492 util::io::print(std::format(
"MPI_Strategy = {}, pack = {}x{} ({} grains)\n",
493 mpi_strategy, ncols, nrows, ncols * nrows));
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";
501 out_dir = p.is_absolute() ? std::move(p) : cwd / p;
505 inp_dir = p.is_absolute() ? std::move(p) : cwd / p;
507 inp_dir = out_dir.parent_path() /
"inp";
509 fs::create_directories(out_dir);
510 fs::create_directories(inp_dir);
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";
516 const bool inbuilt = !file_mesh;
517 const bool write_meshes = input.
cmdOptionExists(
"-writeMeshes") || inbuilt;
519#ifndef JHA2021_COMP_MESH_DIR
520 throw std::runtime_error(
"JHA2021_COMP_MESH_DIR is not set.");
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);
530 util::io::print(std::format(
"Using frozen meshes from {}\n", src.string()));
534 const std::string output_path_for_deck = directoryPathWithTrailingSep(out_dir);
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);
540 std::ofstream os(inp_dir /
"input.json");
541 os << inputJson.dump(2);
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;
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();
562 int max_pairs = probe_p->maxPairs();
563 double min_gap = probe_p->minGap();
564 double gap0 = probe_p->gap0();
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;
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));
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");
593 if (!(
gap0 > probe_p->Rc())) {
594 util::io::print(
"requireContact: grains already in Rc at t=0; packing is too tight.\n");
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,
624 for (
const auto &u : dem.
d_u)
625 max_u = std::max(max_u, u.length());
626 double com0x = 0., com0y = 0.;
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));
void setPostprocess(std::unique_ptr< postprocess::Postprocess > p)
void run(std::shared_ptr< inp::Input > &deck)
static bool ownsNode(const data::ModelData &data, size_t i)
GrainContactProbe(std::filesystem::path out_dir={}, size_t interval=0)
void sampleMetric(data::ModelData &data)
void checkStop(data::ModelData &data) override
std::filesystem::path d_outDir
A class to store model data.
std::vector< float > d_Z
Damage at nodes.
std::vector< util::Point > d_u
Displacement of the nodes.
std::vector< particle::BaseParticle * > d_particlesListTypeParticle
List of particles.
Extra postprocessing and stop criteria (VTU writing is in rw::).
virtual void checkStop(data::ModelData &data)
nlohmann::ordered_json json
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)
bool ownsNode(const data::ModelData &data, size_t i)
void writeLocations(const std::filesystem::path &csv, const std::vector< PackedParticle > &packed)
void createGeomObject(const std::string &geom_type, const std::vector< double > ¶ms, const std::vector< std::string > &vec_type, const std::vector< std::string > &vec_flag, std::shared_ptr< geom::GeomObject > &obj, bool perform_check)
double toGE(double E, double nu)
Compute shear modulus from Young's modulus E and Poisson's ratio nu.
double toE(double K, double nu)
Compute Young's modulus E from Bulk modulus K and Poisson's ratio nu.
bool isLocallyOwned(const BaseParticle &p)
True if this rank updates / assembles forces for the particle. Walls are replicated on every rank....
void print(const T &msg, int nt=print_default_tab, int printMpiRank=print_default_mpi_rank)
Prints formatted information.
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.
double harmonicMean(const double &m1, const double &m2)
Input data for geometrical objects.
static json getExampleJson(std::string type="Foce_BC", bool isRegionActive=false, geom::GeomData regionGeomData=geom::GeomData(), std::vector< size_t > pList=std::vector< size_t >(), std::vector< size_t > pNotList=std::vector< size_t >(), std::string timeFnType="", std::vector< double > timeFnParams=std::vector< double >(), std::string spatialFnType="", std::vector< double > spatialFnParams=std::vector< double >(), std::vector< size_t > direction=std::vector< size_t >(), bool isDisplacementZero=false, std::string icType="", std::vector< double > icVec=std::vector< double >())
Returns example JSON object for ModelDeck configuration.
static json getExampleJson(size_t nForceSets=0, size_t nDispSets=0, size_t nICSets=0, bool gravityActive=false, util::Point gravity=util::Point())
Returns example JSON object for ModelDeck configuration.
static json getExampleJson(std::string materialType="PDState", bool isPlainStrain=false, double horizon=-1., double horizonMeshRatio=-1., double density=1., double K=0., double G=0., double Gc=0., bool computeParamsFromElastic=true, size_t influenceFnType=0, double E=-1.)
Returns example JSON object for ModelDeck configuration.
static json getExampleJson(size_t dim=2, double tFinal=1.0, size_t Nt=10, std::string spatialDiscretization="finite_difference", std::string timeDiscretization="central_difference", bool populateElementNodeConnectivity=true, size_t quadOrder=2, std::string particleSimType="Multi_Particle", int seed=0)
Returns example JSON object for ModelDeck configuration.
static json getExampleJson(std::string outFormat="vtu", std::string path="./", std::vector< std::string > outTags={"Displacement"}, size_t outputInterval=1, size_t debug=2, bool performFEOut=true, std::string compressType="zlib", bool performOut=true, size_t dtTestOut=1, std::string tagPPFile="", bool pvdCollection=false)
Returns example JSON object for ModelDeck configuration.
static json getExampleJson(std::string genMethod="From_File")
Returns example JSON object for ModelDeck configuration.
static json getExampleJson(std::string updateCriteria="simple_all", double sFactor=1., size_t neighUpdateInterval=1, double nearBdNodesTol=0.5)
Returns example JSON object for ModelDeck configuration.
static json getParticleContactExampleJson(size_t nSets=0)
Returns example JSON object for ModelDeck configuration.
static json getParticleGeomExampleJson(std::vector< geom::GeomData > pGeomVec=std::vector< geom::GeomData >())
Returns example JSON object for ModelDeck configuration.
A structure to represent 3d vectors.
double d_y
the y coordinate
double d_x
the x coordinate