23 const std::filesystem::path root(
argv[1]);
24 const auto fixture=root/
"tests/data/polycrystal1103";
25 const auto folder=std::filesystem::temp_directory_path()/
26 (
"polycrystal-enclosed-"+std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()));
27 const auto input=folder/
"inputFiles";
28 std::filesystem::create_directories(input);
29 std::filesystem::create_directories(folder/
"F");
30 std::filesystem::create_directories(folder/
"config");
31 for(
const auto* name : {
"polycrystal.txt",
"DD.txt"})
33 std::filesystem::copy_file(
fixture/name,input/name);
35 std::filesystem::copy_file(root/
"Library/Materials/W.txt",input/
"W.txt");
36 std::filesystem::copy_file(root/
"Library/Meshes/poly30_periodicXYZ.msh",input/
"poly30_periodicXYZ.msh");
38 std::ofstream
out(input/
"DefectiveCrystal.txt");
39 out<<
"physics=DislocationDynamics;\nuseFEM=0;\nNsteps=0;\nmaxResolveSteps=0;\ndtMax=1;\noutputFrequency=1;\noutputBinary=0;\nstartAtTimeStep=0;\nperiodicImageSize=0 0 0;\nEwaldLengthFactor=0;\n";
45 std::ifstream stream(
fixture/
"evl_1103.txt");
46 require(
bool(stream),
"Missing frame 1103 fixture");
51 size_t crossGrainChecks=0;
54 const auto segment=entry.second.lock();
55 if(segment->glidePlanes().empty())
59 for(
const auto& other : network.
loops())
61 const auto target=other.second.lock()->glidePlane;
66 const auto pairs=segment->parallelAndCoincidentGlidePlanes({target.get()});
67 for(
const auto* source : segment->glidePlanes())
69 if(&source->n.lattice!=&target->n.lattice)
72 for(
const auto& pair : pairs)
74 found=found || (pair.first==source && pair.second==target.get());
76 require(found==(source->unitNormal.cross(target->unitNormal).norm()<FLT_EPSILON),
77 "Cross-grain plane comparison used crystal coordinates");
87 require(crossGrainChecks>0,
"Missing cross-grain plane comparison coverage");
88 using V=Eigen::Vector3d;
89 auto loop=network.
loops().at(87).lock();
90 const auto area=[&]()->
V
93 for(
const auto* link : loop->linkSequence())
95 result+=0.5*link->source->get_P().cross(link->sink->get_P());
99 auto patch=loop->periodicGlidePlane->getPatch(V::Zero());
100 V expected=V::Zero();
101 for(
const auto& edge : patch->edges())
103 const V a=loop->periodicGlidePlane->referencePlane->globalPosition(*edge->source);
104 const V b=loop->periodicGlidePlane->referencePlane->globalPosition(*edge->sink);
105 expected+=0.5*
a.cross(
b);
107 if(expected.dot(
area())<0)
111 require(expected.norm()>1,
"Expected a resolved grain cross-section");
113 const auto check=[&]()
115 require((
area()-expected).norm()<1.e-8*expected.norm(),
"Fully enclosed grain lost slipped area");
116 require(loop->linkSequence().size()==patch->edges().size(),
"Retained contour is not the grain boundary");
117 for(
const auto* link : loop->linkSequence())
119 require(link->source->isPhysicalBoundaryNode(),
"Enclosed contour is not confined to physical faces");
121 for(
const auto& entry : network.
loopNodes())
123 const auto node=entry.second.lock();
124 require((node->get_P()+node->periodicPlanePatch()->shift-node->networkNode->get_P()).norm()<1.e-7,
125 "Boundary update broke loop/network periodic mapping");
129 const auto count=network.
loopNodes().size();
132 require(network.
loopNodes().size()==count,
"Repeated boundary update changed topology");
137 size_t drivenPoints=0;
140 const auto segment=entry.second.lock();
141 segment->createQuadraturePoints(
false);
142 if(segment->isGrainBoundarySegment())
144 require(segment->quadraturePoints().empty(),
"GB segment generated glide quadrature points");
146 for(
auto& point : segment->quadraturePoints())
148 point.velocity=
V(0.1,0.2,0.3);
152 require(drivenPoints>0,
"No mobile segments exercise the solver");
153 for(
const auto* link : loop->linkSequence())
155 require(
bool(link->networkLink()),
"Boundary loop lost its network segment");
156 require(link->networkLink()->isGrainBoundarySegment(),
"Expected loop entirely on GBs");
157 require(link->source->networkNode->loopNodes().size()==1,
"Stationary-loop fixture shares a mobile node");
159 const Eigen::VectorXd velocities=network.
glideSolver->getNodeVelocities();
160 require(velocities.allFinite() && velocities.norm()>0,
"No finite mobile response");
161 std::map<size_t,V> positions;
162 for(
const auto* link : loop->linkSequence())
164 const auto node=link->source->networkNode;
165 const V velocity=velocities.segment<3>(3*node->networkID());
166 require(velocity.norm()==0,
"Fully GB-confined loop has nonzero solver velocity");
168 node->set_V(
V(0.1,0.2,0.3),
false);
169 node->set_V(velocity,
false);
170 require(node->get_V().norm()==0,
"GB node retained its previous velocity");
171 positions.emplace(node->sID,node->get_P());
173 const Eigen::Matrix3d expectedPlastic=-loop->burgers()*expected.transpose()/base.mesh.volume();
175 require(expectedPlastic.allFinite() && expectedPlastic.norm()>1.e-12,
176 "GB loop has no resolved plastic distortion");
177 const auto checkPlastic=[&]()
179 require((loop->averagePlasticDistortion()-expectedPlastic).norm()<1.e-8*expectedPlastic.norm(),
180 "Stationary GB loop lost its plastic distortion");
182 "Network plastic distortion changed without motion");
184 require(loop->averagePlasticDistortionRate().allFinite() &&
185 loop->averagePlasticDistortionRate().norm()==0,
186 "Stationary GB loop has a nonzero plastic distortion rate");
191 for(
const auto& entry : positions)
193 center+=entry.second;
195 center/=positions.size();
196 const double scale=std::sqrt(expected.norm());
197 const V normal=expected.normalized();
198 std::vector<V> probes;
199 std::vector<Eigen::Matrix3d> fullStress;
200 std::vector<Eigen::Matrix3d> boundaryStress;
201 std::vector<V> fullDisplacement;
202 std::vector<V> boundaryDisplacement;
203 for(
const double offset : {0.13,0.31,0.57})
205 const V point=center+offset*scale*normal;
206 probes.push_back(point);
207 fullStress.push_back(network.
stress(point,
nullptr,
nullptr,
nullptr));
208 fullDisplacement.push_back(network.
displacement(point,
nullptr,
nullptr,
nullptr));
209 V displacement=V::Zero();
210 for(
const auto& shift : base.periodicShifts)
212 displacement-=loop->solidAngle(point+shift)*loop->burgers()/(4.0*std::numbers::pi);
214 Eigen::Matrix3d contribution=Eigen::Matrix3d::Zero();
215 for(
const auto* link : loop->linkSequence())
217 const auto segment=link->networkLink();
218 require(segment->quadraturePoints().empty(),
"Stress-source GB segment has quadrature points");
219 for(
const auto& shift : base.periodicShifts)
221 contribution+=segment->straight.stress(point+shift);
222 displacement+=segment->straight.displacement(point+shift);
225 require(contribution.allFinite() && contribution.norm()>1.e-12,
226 "Stationary GB loop produces no resolved stress");
227 boundaryStress.push_back(contribution);
228 require(displacement.allFinite() && displacement.norm()>1.e-12,
229 "Stationary GB loop produces no resolved displacement");
230 boundaryDisplacement.push_back(displacement);
235 entry.second.lock()->set_V(V::Zero(),
false);
237 for(
int step=0;step<3;++step)
242 for(
const auto& entry : positions)
244 const auto node=network.
networkNodes().at(entry.first).lock();
245 require((node->get_P()-entry.second).norm()<1.e-10,
"GB-confined loop moved");
248 for(
size_t k=0;
k<probes.size();++
k)
250 const Eigen::Matrix3d
current=network.
stress(probes[
k],
nullptr,
nullptr,
nullptr);
252 "Stress changed while the GB loop remained stationary");
253 const V displacement=network.
displacement(probes[
k],
nullptr,
nullptr,
nullptr);
254 require((displacement-fullDisplacement[
k]).norm()<1.e-8*boundaryDisplacement[
k].norm(),
255 "Displacement changed while the GB loop remained stationary");
260 require(loop->linkSequence().empty(),
"GB loop was not removed for the field comparison");
266 "Network plastic distortion omitted or miscounted the GB loop");
267 for(
size_t k=0;
k<probes.size();++
k)
269 const Eigen::Matrix3d withoutLoop=network.
stress(probes[
k],
nullptr,
nullptr,
nullptr);
270 require((fullStress[
k]-withoutLoop-boundaryStress[
k]).norm()<1.e-8*boundaryStress[
k].norm(),
271 "Network stress omitted or miscounted the stationary GB loop");
272 const V displacementWithoutLoop=network.
displacement(probes[
k],
nullptr,
nullptr,
nullptr);
273 require((fullDisplacement[
k]-displacementWithoutLoop-boundaryDisplacement[
k]).norm()<1.e-8*boundaryDisplacement[
k].norm(),
274 "Network displacement omitted or miscounted the stationary GB loop");
276 std::filesystem::remove_all(folder);
277 std::cout<<
"PASS: frame 1103 stationary grain-boundary loop retains stress, displacement and plastic distortion with zero distortion rate\n";
280 catch(
const std::exception& error)
282 std::cerr<<error.what()<<
'\n';