65 const std::filesystem::path root(
argv[1]);
66 const auto folder = std::filesystem::temp_directory_path() /
67 (
"junction-safety-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()));
68 const auto input = folder /
"inputFiles";
69 std::filesystem::create_directories(input);
70 std::filesystem::create_directories(folder /
"F");
71 std::filesystem::copy_file(root /
"Library/Materials/W.txt", input /
"W.txt");
72 const auto meshFile = root /
"Library/Meshes/unitCube24.msh";
73 std::filesystem::copy_file(meshFile, input /
"mesh.msh");
75 std::vector<int> seeds;
80 for(
const auto& face : region.second->faces())
82 const auto& pair = face.second->periodicFacePair;
83 if(pair.second && pair.first[
axis] > 0.9 &&
84 pair.first.normalized().cross(Eigen::Vector3d::Unit(
axis)).norm() < 1.e-8)
86 seeds.push_back(face.first);
91 require(seeds.size() == 3,
"Expected three cube periodic directions");
94 std::filesystem::copy_file(root /
"Library/DislocationDynamics/DD.txt", input /
"DD.txt");
95 const double length=200.0/std::sqrt(3.0);
96 for(
int scenario=0;scenario<5;++scenario)
99 std::ofstream
f(input/
"DefectiveCrystal.txt");
100 f<<
"physics=DislocationDynamics;\nuseFEM=0;\nNsteps=0;\nmaxResolveSteps=0;\ndtMax=1;\noutputFrequency=1;\noutputBinary=0;\nstartAtTimeStep=0;\nperiodicImageSize=0;\nEwaldLengthFactor=0;\n";
101 std::ofstream
p(input/
"polycrystal.txt");
102 p<<std::setprecision(17)<<
"materialFile=W.txt;\nmeshFile=mesh.msh;\nabsoluteTemperature=300;\nC2G1=1 0 0 0 1 0 0 0 1;\nF="<<length<<
" 0 0 0 "<<length<<
" 0 0 0 "<<length<<
";\nX0=0 0 0;\nperiodicFaceIDs="<<seeds[scenario==2 ? 0 : 1]<<
";\n";
107 using V=Eigen::Vector3d;
110 const auto grain=base.poly.grain(1);
111 const auto planeAt=[&](
const V& point,
const V& normal)
113 return base.dislocationDynamics().glidePlaneFactory.getFromKey(
model::GlidePlaneKey<3>(point,grain->reciprocalLatticeDirection(normal)));
115 const auto plane=planeAt(V::Zero(),V::UnitZ());
116 const auto insert=[&](
const V& burgers,
const auto& gp,
const auto& nodes)
118 const auto loop=network.
loops().
create(burgers,gp);
119 std::vector<std::shared_ptr<Network::LoopNodeType>> ring;
120 for(
const auto& node : nodes)
122 ring.push_back(network.
loopNodes().
create(loop,node,node->get_P(),loop->periodicGlidePlane->getPatch(V::Zero()),
128 if(scenario==0 || scenario>=3)
130 const double delta=8.0/std::sqrt(3.0);
131 const V burgers=2.0/std::sqrt(3.0)*V::UnitY();
138 const auto first=insert(burgers,plane,std::vector{
a,
b,
c});
139 const auto second=insert(-burgers,planeAt(
V(delta,0,0),
V(1,0,1)),std::vector{d,
e,
f});
141 const auto keyA=std::make_pair(
a->sID,
b->sID);
142 const auto keyB=std::make_pair(d->sID,
e->sID);
143 std::set<std::pair<size_t,size_t>> independentKeys;
149 const V shift=scenario==3 ?
V(0,0,40.0/std::sqrt(3.0)) :
V(0,40.0/std::sqrt(3.0),0);
158 insert(burgers,planeAt(shift,V::UnitZ()),std::vector{
aa,
bb,
cc});
163 insert(burgers,plane,std::vector{
aa,
bb,
cc,
c});
165 insert(-burgers,planeAt(
V(delta,0,0)+shift,
V(1,0,1)),std::vector{dd,ee,ff});
166 independentKeys.insert(std::minmax(
aa->sID,
bb->sID));
167 independentKeys.insert(std::minmax(dd->sID,ee->sID));
170 auto queue=Access::find(network);
171 Access::Queue selected(1);
172 for(
const auto& item : queue.front())
174 if((std::get<0>(item)==keyA && std::get<1>(item)==keyB) ||
175 (std::get<0>(item)==keyB && std::get<1>(item)==keyA) ||
176 (independentKeys.count(std::get<0>(item)) && independentKeys.count(std::get<1>(item))))
178 selected.front().push_back(item);
181 require(!selected.front().empty(),
"X-separated attractive segments were missed by sweep");
184 require(selected.front().size()>=2,
"Expected an event for each independent collision");
187 for(
const auto&
event : queue.front())
189 selected.front().push_back(
event);
191 require(Access::contract(network,selected)==(scenario==3 ? 2 : 1),
192 "Batch must contract independent pairs and defer their dependent events");
193 for(
const auto& entry : network.
loops())
195 require(entry.second.lock()->isLoop(),
"Batch broke shared loop closure");
200 shared=shared || entry.second.lock()->loopLinks().size()>1;
202 require(shared,
"Batch lost junction sharing");
203 require(Access::contract(network,Access::find(network))>0,
204 "Deferred junction was not available in the next iteration");
210 for(
const auto& node : {d,
e,
f})
212 node->trySet_P(node->get_P()+
V(20,0,-20));
219 require(Access::contract(network,selected)==0,
"Stale collision was contracted");
221 "Stale rejection changed topology");
228 for(
const auto& slip : grain->slipSystems())
230 if(std::abs(slip.second->n.cartesian().normalized().x())<1.e-8)
232 burgers=slip.second->s.cartesian();
236 require(burgers.norm()>0,
"Missing X-directed glide plane");
241 const auto first=insert(2*burgers,plane,std::vector{
a,
b,
c});
242 const auto second=insert(-burgers,plane,std::vector{
a,
b,d});
246 b->trySet_P(
V(0.7*length,0,0));
250 std::shared_ptr<Network::NetworkNodeType> endpoint;
253 const auto link=entry.second.lock();
254 if(link->loopLinks().size()==2 && (link->source==
a || link->sink==
a))
256 endpoint=link->source==
a ? link->sink : link->source;
259 require(
bool(endpoint) && endpoint->isBoundaryNode(),
"Missing boundary junction endpoint");
263 const auto unrelated=insert(burgers,plane,std::vector{endpoint,
e,
f});
266 const auto area=[&]()
269 for(
const auto* ll : unrelated->linkSequence())
271 result+=0.5*ll->source->get_P().cross(ll->sink->get_P());
275 const V before=
area();
276 const auto count=network.
loops().size();
277 const V probe(7,11,19);
278 const Eigen::Matrix3d stressBefore=network.
stress(probe,
nullptr,
nullptr,
nullptr);
279 Access::glissile(network);
281 const Eigen::Matrix3d stressAfter=network.
stress(probe,
nullptr,
nullptr,
nullptr);
282 require((stressAfter-stressBefore).norm()<1.e-9*std::max(1.e-12,stressBefore.norm()),
283 "Glissile seed changed the original stress field");
284 const auto shared=network.
networkLinks().
get(std::minmax(
a->sID,endpoint->sID));
285 require(shared && shared->loopLinks().size()==3 && shared->hasZeroBurgers(),
286 "Glissile seed lost the original shared segment or its Burgers cancellation");
287 require(network.
loops().size()>count,
"Expected glissile junction was not created");
288 require((
area()-before).norm()<1.e-8,
"Unrelated loop at shared endpoint was altered");
289 require(
first->isLoop() && second->isLoop() && unrelated->isLoop(),
"Junction sharing broke loop closure");
291 for(
const auto& entry : network.
loopNodes())
293 const auto ln=entry.second.lock();
294 require((ln->get_P()+ln->periodicPlanePatch()->shift-ln->networkNode->get_P()).norm()<1.e-7,
295 "Junction formation broke periodic mapping");
298 if(
argc>2 && std::string(
argv[2])==
"benchmark")
300 std::filesystem::copy_file(root/
"Library/Meshes/bicrystal_48.msh",input/
"mesh.msh",std::filesystem::copy_options::overwrite_existing);
302 std::ofstream
f(input/
"DefectiveCrystal.txt");
303 f<<
"physics=DislocationDynamics;\nuseFEM=0;\nNsteps=0;\nmaxResolveSteps=0;\ndtMax=1;\noutputFrequency=1;\noutputBinary=0;\nstartAtTimeStep=0;\nperiodicImageSize=0 0 0;\nEwaldLengthFactor=0;\n";
304 std::ofstream
p(input/
"polycrystal.txt");
305 const double scale=3671.9477120460206;
306 p<<std::setprecision(17)<<
"materialFile=W.txt;\nmeshFile=mesh.msh;\nabsoluteTemperature=300;\nC2G1=1 0 0 0 1 0 0 0 1;\nC2G2=0.8 0 0.6 0 1 0 -0.6 0 0.8;\nF="<<scale<<
" 0 0 0 "<<scale<<
" 0 0 0 "<<scale<<
";\nX0=0 0 0;\nperiodicFaceIDs=-1;\n";
312 std::ifstream stream(root/
"tests/data/bicrystalBoundary433.txt");
313 require(
bool(stream),
"Missing collision benchmark fixture");
317 auto queue=Access::find(network);
318 const auto searchStart=std::chrono::steady_clock::now();
319 for(
int i=0;
i<20;++
i)
321 queue=Access::find(network);
323 const double searchSeconds=std::chrono::duration<double>(std::chrono::steady_clock::now()-searchStart).count()/20;
324 const auto refreshStart=std::chrono::steady_clock::now();
326 for(
int i=0;
i<200;++
i)
328 refreshed+=Access::refresh(network,queue);
330 const double refreshSeconds=std::chrono::duration<double>(std::chrono::steady_clock::now()-refreshStart).count()/200;
331 require(!queue.front().empty() && refreshed>0,
"Benchmark has no queued collisions");
332 std::cout<<
"BENCHMARK: "<<queue.front().size()<<
" queued events; search="<<searchSeconds
333 <<
" s; refresh="<<refreshSeconds<<
" s; ratio="<<refreshSeconds/searchSeconds<<
'\n';
335 std::filesystem::remove_all(folder);
336 std::cout<<
"PASS: X-separated candidates, stale collisions, physical endpoints and shared periodic junctions\n";
339 catch(
const std::exception& error)
341 std::cerr<<error.what()<<
'\n';