Mechanics of Defect Evolution Library
Loading...
Searching...
No Matches
testPolycrystalEnclosedBoundary.cpp
Go to the documentation of this file.
4#include <filesystem>
5#include <fstream>
6#include <chrono>
7#include <iostream>
8#include <numbers>
9
10void require(bool condition,const char* message)
11{
12 if(!condition)
13 {
14 throw std::runtime_error(message);
15 }
16}
17
18int main(int argc,char** argv)
19{
20 try
21 {
22 require(argc==2,"Expected repository root");
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"})
32 {
33 std::filesystem::copy_file(fixture/name,input/name);
34 }
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");
37 {
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";
40 }
41 model::DefectiveCrystalBase<3> base(folder.string());
43 model::DislocationNetwork<3> network(container);
44 model::DefectiveCrystalConfigIO<3> saved(folder.string());
45 std::ifstream stream(fixture/"evl_1103.txt");
46 require(bool(stream),"Missing frame 1103 fixture");
47 saved.readTxtStream(stream);
48 network.initializeConfiguration(saved,std::ofstream(),std::ofstream());
49 // Collision selection must compare planes from distinct grains in the
50 // global frame, not cross their unrelated integer lattice coordinates.
51 size_t crossGrainChecks=0;
52 for(const auto& entry : network.networkLinks())
53 {
54 const auto segment=entry.second.lock();
55 if(segment->glidePlanes().empty())
56 {
57 continue;
58 }
59 for(const auto& other : network.loops())
60 {
61 const auto target=other.second.lock()->glidePlane;
62 if(!target)
63 {
64 continue;
65 }
66 const auto pairs=segment->parallelAndCoincidentGlidePlanes({target.get()});
67 for(const auto* source : segment->glidePlanes())
68 {
69 if(&source->n.lattice!=&target->n.lattice)
70 {
71 bool found=false;
72 for(const auto& pair : pairs)
73 {
74 found=found || (pair.first==source && pair.second==target.get());
75 }
76 require(found==(source->unitNormal.cross(target->unitNormal).norm()<FLT_EPSILON),
77 "Cross-grain plane comparison used crystal coordinates");
78 ++crossGrainChecks;
79 }
80 }
81 }
82 if(crossGrainChecks)
83 {
84 break;
85 }
86 }
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
91 {
92 V result=V::Zero();
93 for(const auto* link : loop->linkSequence())
94 {
95 result+=0.5*link->source->get_P().cross(link->sink->get_P());
96 }
97 return result;
98 };
99 auto patch=loop->periodicGlidePlane->getPatch(V::Zero());
100 V expected=V::Zero();
101 for(const auto& edge : patch->edges())
102 {
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);
106 }
107 if(expected.dot(area())<0)
108 {
109 expected=-expected;
110 }
111 require(expected.norm()>1,"Expected a resolved grain cross-section");
112 network.moveNodes(168.9662550850772);
113 const auto check=[&]()
114 {
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())
118 {
119 require(link->source->isPhysicalBoundaryNode(),"Enclosed contour is not confined to physical faces");
120 }
121 for(const auto& entry : network.loopNodes())
122 {
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");
126 }
127 };
128 check();
129 const auto count=network.loopNodes().size();
130 network.updateBoundaryNodes();
131 check();
132 require(network.loopNodes().size()==count,"Repeated boundary update changed topology");
134 check();
135 // Drive every eligible segment, so a missing GB exclusion cannot pass
136 // merely because this snapshot happens to have zero resolved force.
137 size_t drivenPoints=0;
138 for(const auto& entry : network.networkLinks())
139 {
140 const auto segment=entry.second.lock();
141 segment->createQuadraturePoints(false);
142 if(segment->isGrainBoundarySegment())
143 {
144 require(segment->quadraturePoints().empty(),"GB segment generated glide quadrature points");
145 }
146 for(auto& point : segment->quadraturePoints())
147 {
148 point.velocity=V(0.1,0.2,0.3);
149 ++drivenPoints;
150 }
151 }
152 require(drivenPoints>0,"No mobile segments exercise the solver");
153 for(const auto* link : loop->linkSequence())
154 {
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");
158 }
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())
163 {
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");
167 // Verify that committing the solution clears any previous motion.
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());
172 }
173 const Eigen::Matrix3d expectedPlastic=-loop->burgers()*expected.transpose()/base.mesh.volume();
174 const Eigen::Matrix3d fullPlastic=network.averagePlasticDistortion();
175 require(expectedPlastic.allFinite() && expectedPlastic.norm()>1.e-12,
176 "GB loop has no resolved plastic distortion");
177 const auto checkPlastic=[&]()
178 {
179 require((loop->averagePlasticDistortion()-expectedPlastic).norm()<1.e-8*expectedPlastic.norm(),
180 "Stationary GB loop lost its plastic distortion");
181 require((network.averagePlasticDistortion()-fullPlastic).norm()<1.e-8*expectedPlastic.norm(),
182 "Network plastic distortion changed without motion");
183 loop->updateRates();
184 require(loop->averagePlasticDistortionRate().allFinite() &&
185 loop->averagePlasticDistortionRate().norm()==0,
186 "Stationary GB loop has a nonzero plastic distortion rate");
187 };
188 checkPlastic();
189 // Probe off the loop plane to avoid dislocation cores and symmetry zeros.
190 V center=V::Zero();
191 for(const auto& entry : positions)
192 {
193 center+=entry.second;
194 }
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})
204 {
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)
211 {
212 displacement-=loop->solidAngle(point+shift)*loop->burgers()/(4.0*std::numbers::pi);
213 }
214 Eigen::Matrix3d contribution=Eigen::Matrix3d::Zero();
215 for(const auto* link : loop->linkSequence())
216 {
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)
220 {
221 contribution+=segment->straight.stress(point+shift);
222 displacement+=segment->straight.displacement(point+shift);
223 }
224 }
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);
231 }
232 // Hold unrelated loops fixed to isolate persistence of the enclosed loop.
233 for(const auto& entry : network.networkNodes())
234 {
235 entry.second.lock()->set_V(V::Zero(),false);
236 }
237 for(int step=0;step<3;++step)
238 {
239 network.moveNodes(100.0);
240 check();
241 checkPlastic();
242 for(const auto& entry : positions)
243 {
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");
246 }
247 }
248 for(size_t k=0;k<probes.size();++k)
249 {
250 const Eigen::Matrix3d current=network.stress(probes[k],nullptr,nullptr,nullptr);
251 require((current-fullStress[k]).norm()<1.e-8*boundaryStress[k].norm(),
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");
256 }
257 // Test the public network field, not just the segment kernel: removing
258 // this isolated loop must remove exactly its nonzero field contributions.
259 network.deleteLoop(loop->sID);
260 require(loop->linkSequence().empty(),"GB loop was not removed for the field comparison");
261 // Release the test's owning reference so the erased loop is absent from
262 // the network's weak-pointer loop container and its field sums.
263 patch.reset();
264 loop.reset();
265 require((fullPlastic-network.averagePlasticDistortion()-expectedPlastic).norm()<1.e-8*expectedPlastic.norm(),
266 "Network plastic distortion omitted or miscounted the GB loop");
267 for(size_t k=0;k<probes.size();++k)
268 {
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");
275 }
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";
278 return 0;
279 }
280 catch(const std::exception& error)
281 {
282 std::cerr<<error.what()<<'\n';
283 return 1;
284 }
285}
Eigen::Vector3d V
void updateBoundaryNodes(const std::function< void()> &checkpoint={})
void executeSingleGlideStepDiscreteEvents(const long int &runID)
VectorDim displacement(const VectorDim &, const NodeType *const, const ElementType *const, const SimplexDim *const) const override
std::shared_ptr< DislocationGlideSolverBase< dim > > glideSolver
MatrixDim averagePlasticDistortion() const override
MatrixDim stress(const VectorDim &, const NodeType *const, const ElementType *const ele, const SimplexDim *const guess) const override
void moveNodes(const double &dt_in)
void initializeConfiguration(const DefectiveCrystalConfigIO< dim > &configIO, const std::ofstream &f_file, const std::ofstream &F_labels) override
const LoopContainerType & loops() const
Definition LoopNetwork.h:98
const NetworkNodeContainerType & networkNodes() const
const LoopNodeContainerType & loopNodes() const
NetworkLinkContainerType & networkLinks()
void deleteLoop(const size_t &loopID)
a
Definition int_R.m:14
b
Definition int_R.m:16
fs::path fixture(const fs::path &root, int rank, int reactions, bool source, const std::string &name="unitCube24.msh")
void check(bool ok, const char *message)
void require(bool condition, const char *message)
void out
void argv
void b int argc
int k
REAL area
result
int current