Mechanics of Defect Evolution Library
Loading...
Searching...
No Matches
testPeriodicMesh.cpp
Go to the documentation of this file.
1#include <SimplicialMesh.h>
3#include <Eigen/Geometry>
4#include <iostream>
5#include <stdexcept>
6
7void require(bool condition, const std::string& message)
8{
9 if(!condition)
10 {
11 throw std::runtime_error(message);
12 }
13}
14
15void check(const std::string& path, int expected, const Eigen::Matrix3d& rotation)
16{
17 model::SimplicialMesh<3> mesh(path,rotation,Eigen::Vector3d::Zero(),{-1});
18 const auto basis = mesh.periodicBasis();
19 require(basis.cols() == expected,"Wrong detected periodic rank: " + path);
20 std::vector<model::Polyhedron<3,3-1>*> faces;
21 for(const auto& region : mesh.regions())
22 {
23 for(const auto& entry : region.second->faces())
24 {
25 if(entry.second->isExternal())
26 {
27 faces.push_back(entry.second.get());
28 }
29 }
30 }
31 const double tol = 1.e-7*(mesh.xMax()-mesh.xMin()).norm();
32 std::set<int> seeds;
33 size_t pairedFaces=0;
34 size_t diagonalFaces=0;
35 for(const auto* face : faces)
36 {
37 const auto& pair=face->periodicFacePair;
38 if(!pair.second)
39 {
40 continue;
41 }
42 ++pairedFaces;
43 require(pair.second->periodicFacePair.second==face,"Nonreciprocal pairing");
44 require((pair.first+pair.second->periodicFacePair.first).norm()<tol,"Nonreciprocal translation");
45 Eigen::Vector3d shift;
46 require(model::isPeriodicFacePair(*face,*pair.second,shift,tol),"Invalid triangle pairing");
47 require(model::periodicLatticeContains<3>(basis,shift,tol),"Pair is outside the integer lattice");
48 const Eigen::Vector3d local=rotation.transpose()*shift;
49 require(std::abs(local.z())<tol,"Unexpected Z periodicity");
50 if(std::abs(local.x())>tol && std::abs(local.y())>tol)
51 {
52 ++diagonalFaces;
53 }
54 }
55 require(pairedFaces==size_t(expected==1?24:50),"Wrong number of paired faces");
56 require(diagonalFaces==size_t(expected==1?0:12),"Missing X+Y or X-Y diagonal pairs");
57 for(int axis=0; axis<expected; ++axis)
58 {
59 require(model::periodicLatticeContains<3>(basis,Eigen::Vector3d(rotation.col(axis)),tol),
60 "Detected basis does not generate an expected unit translation");
61 const auto seed=std::find_if(faces.begin(),faces.end(),[&](const auto* face)
62 {
63 return face->periodicFacePair.second
64 && face->periodicFacePair.first.normalized().cross(rotation.col(axis)).norm()<1.e-6;
65 });
66 require(seed!=faces.end(),"Missing axis seed face");
67 seeds.insert((*seed)->sID);
68 const int oppositeSeed=(*seed)->periodicFacePair.second->sID;
69 if(expected==2)
70 {
71 require(std::abs((*seed)->outNormal().dot(rotation.col(0)))>1.e-6
72 && std::abs((*seed)->outNormal().dot(rotation.col(1)))>1.e-6,
73 "XY selection fixture must exercise inclined face normals");
74 }
75 mesh.identifyParallelFaces({static_cast<int>((*seed)->sID)});
76 require(mesh.periodicBasis().cols()==1,"Single face enabled extra directions");
77 size_t activeFaces=0;
78 for(const auto* face : faces)
79 {
80 if(face->periodicFacePair.second)
81 {
82 ++activeFaces;
83 require(face->periodicFacePair.first.normalized().cross(rotation.col(axis)).norm()<1.e-6,
84 "Single-axis selection enabled another direction or diagonal");
85 }
86 }
87 require(activeFaces==size_t(expected==1?24:(axis==0?20:18)),"Incomplete single-axis activation");
88 const auto rediscovered=model::discoverPeriodicMeshLattice(faces,tol);
89 require(rediscovered.basis.cols()==expected && rediscovered.pairs.size()==pairedFaces,
90 "Active selection changed discovery of the full available lattice");
91 mesh.identifyParallelFaces({oppositeSeed});
92 require(mesh.periodicBasis().cols()==1
93 && mesh.periodicBasis().col(0).normalized().cross(rotation.col(axis)).norm()<1.e-6,
94 "Selecting the opposite face changed the active direction");
95 mesh.identifyParallelFaces({-1});
96
97 }
98 // Explicit X/Y seeds must activate the same complete pairing, including diagonals.
99 std::map<size_t,size_t> original;
100 for(const auto* face : faces)
101 {
102 if(face->periodicFacePair.second)
103 {
104 original.emplace(face->sID,face->periodicFacePair.second->sID);
105 }
106 }
107 mesh.identifyParallelFaces(seeds);
108 require(mesh.periodicBasis().cols()==expected,"Wrong explicitly selected rank");
109 for(const auto* face : faces)
110 {
111 require(bool(face->periodicFacePair.second)==bool(original.count(face->sID)),"Selected face coverage changed");
112 if(face->periodicFacePair.second)
113 {
114 require(original.at(face->sID)==face->periodicFacePair.second->sID,"Selected partner changed");
115 }
116 }
117 if(expected==2)
118 {
119 std::set<int> diagonalSeeds;
120 for(const int sign : {-1,1})
121 {
122 const Eigen::Vector3d diagonal=rotation*Eigen::Vector3d(1,sign,0).normalized();
123 const auto face=std::find_if(faces.begin(),faces.end(),[&](const auto* item)
124 {
125 return item->periodicFacePair.second
126 && item->periodicFacePair.first.normalized().cross(diagonal).norm()<1.e-6;
127 });
128 require(face!=faces.end(),"Missing diagonal face pair");
129 diagonalSeeds.insert((*face)->sID);
130 mesh.identifyParallelFaces({static_cast<int>((*face)->sID)});
131 require(mesh.periodicBasis().cols()==1,"One diagonal face enabled both axes");
132 size_t activeCount=0;
133 for(const auto* item : faces)
134 {
135 if(item->periodicFacePair.second)
136 {
137 ++activeCount;
138 require(item->periodicFacePair.first.normalized().cross(diagonal).norm()<1.e-6,
139 "One diagonal face activated a different translation");
140 }
141 }
142 require(activeCount==size_t(sign==1?8:4),"Wrong single-diagonal pair count");
143 mesh.identifyParallelFaces(seeds);
144 }
145 // Selecting diagonal generators activates their index-two sublattice,
146 // without implicitly enabling the X or Y translations.
147 mesh.identifyParallelFaces(diagonalSeeds);
148 require(mesh.periodicBasis().cols()==2,"Wrong diagonal sublattice rank");
149 size_t activeDiagonals=0;
150 for(const auto* face : faces)
151 {
152 if(face->periodicFacePair.second)
153 {
154 const Eigen::Vector3d local=rotation.transpose()*face->periodicFacePair.first;
155 require(std::abs(local.x())>tol && std::abs(local.y())>tol,"Diagonal selection enabled X or Y");
156 ++activeDiagonals;
157 }
158 }
159 require(activeDiagonals==12,"Missing diagonal sublattice pairs");
160 diagonalSeeds.insert(*seeds.begin());
161 mesh.identifyParallelFaces(diagonalSeeds);
162 for(const auto* face : faces)
163 {
164 if(original.count(face->sID))
165 {
166 require(face->periodicFacePair.second
167 && original.at(face->sID)==face->periodicFacePair.second->sID,
168 "Alternative generators lost a periodic pair");
169 }
170 }
171 mesh.identifyParallelFaces(seeds);
172 }
173 // A locally matching pair is insufficient when another required face has no partner.
174 auto incomplete=faces;
175 const auto removed=std::find_if(incomplete.begin(),incomplete.end(),[](const auto* face)
176 {
177 return face->periodicFacePair.second!=nullptr;
178 });
179 require(removed!=incomplete.end(),"Missing test face");
180 incomplete.erase(removed);
181 bool incompleteRejected=false;
182 try
183 {
185 }
186 catch(const std::exception&)
187 {
188 incompleteRejected=true;
189 }
190 require(incompleteRejected,"Accepted lattice despite a missing required face partner");
191 // The hull is unchanged when a contained triangle is removed; pairing must still fail.
192 for(auto* face : faces)
193 {
194 if(face->periodicFacePair.second)
195 {
196 const auto* triangle = *face->simplices().begin();
197 face->simplices().erase(triangle);
198 Eigen::Vector3d shift;
199 const bool matched = model::isPeriodicFacePair(*face,*face->periodicFacePair.second,shift,tol);
200 face->simplices().insert(triangle);
201 require(!matched,"Accepted differing triangulations with matching hulls");
202 break;
203 }
204 }
205 // Invalid selections must not change the current pairing.
206 const auto oldBasis=mesh.periodicBasis();
207 bool rejected=false;
208 try
209 {
210 mesh.identifyParallelFaces({2147483647});
211 }
212 catch(const std::exception&)
213 {
214 rejected=true;
215 }
216 require(rejected,"Unknown face ID accepted");
217 require((oldBasis-mesh.periodicBasis()).norm()<tol,"Failed selection changed mesh");
218 for(const auto* face : faces)
219 {
220 if(!face->periodicFacePair.second)
221 {
222 rejected=false;
223 try
224 {
225 mesh.identifyParallelFaces({static_cast<int>(face->sID)});
226 }
227 catch(const std::exception&)
228 {
229 rejected=true;
230 }
231 require(rejected,"Nonperiodic face accepted");
232 break;
233 }
234 }
235 mesh.identifyParallelFaces({});
236 require(mesh.periodicBasis().cols()==0,"Empty selection did not clear periodicity");
237}
238
239void checkBicrystal(const std::string& path,const Eigen::Matrix3d& rotation)
240{
241 model::SimplicialMesh<3> mesh(path,rotation,Eigen::Vector3d::Zero(),{-1});
242 const auto basis=mesh.periodicBasis();
243 require(basis.cols()==3,"Bicrystal must have three periodic directions");
244 std::set<int> axes;
245 for(int k=0; k<3; ++k)
246 {
247 const Eigen::Vector3d local=rotation.transpose()*basis.col(k);
248 Eigen::Index axis;
249 local.cwiseAbs().maxCoeff(&axis);
250 Eigen::Vector3d expected=Eigen::Vector3d::Zero();
251 expected[axis]=local[axis]>0.0 ? (axis==0?2.0:1.0) : -(axis==0?2.0:1.0);
252 require((local-expected).norm()<1.e-7,"Bicrystal basis is not box aligned");
253 axes.insert(static_cast<int>(axis));
254 }
255 require(axes.size()==3,"Repeated bicrystal direction");
256 size_t pairedFaces=0;
257 for(const auto& region : mesh.regions())
258 {
259 for(const auto& entry : region.second->faces())
260 {
261 const auto& face=*entry.second;
262 if(face.periodicFacePair.second)
263 {
264 ++pairedFaces;
265 require(face.isExternal() && face.periodicFacePair.second->isExternal(),"Internal face paired");
266 require(face.periodicFacePair.second->periodicFacePair.second==&face,"Nonreciprocal bicrystal pairing");
267 const Eigen::Vector3d shift=rotation.transpose()*face.periodicFacePair.first;
268 require((shift.cwiseAbs().array()>1.e-7).count()==1,"Diagonal bicrystal pairing survived");
269 require(face.regionIDs==face.periodicFacePair.second->regionIDs || std::abs(shift.x())>1.e-7,
270 "Y/Z pairing crosses grains");
271 }
272 }
273 }
274 require(pairedFaces==10,"Expected all five bicrystal face pairs");
275}
276
277int main(int argc,char** argv)
278{
279 try
280 {
281 require(argc==2,"Repository path required");
282 const std::string root=argv[1];
283 const std::string meshes=root+"/Library/Meshes/";
284 const Eigen::Matrix3d rotation=Eigen::AngleAxisd(0.71,Eigen::Vector3d(1,2,3).normalized()).toRotationMatrix();
285 check(meshes+"polycrystalCube_10_periodicX.msh",1,Eigen::Matrix3d::Identity());
286 check(meshes+"polycrystalCube_10_periodicX.msh",1,rotation);
287 check(meshes+"polycrystalCube_10_periodicXY.msh",2,Eigen::Matrix3d::Identity());
288 check(meshes+"polycrystalCube_10_periodicXY.msh",2,rotation);
289 checkBicrystal(meshes+"bicrystal_48.msh",Eigen::Matrix3d::Identity());
290 checkBicrystal(meshes+"bicrystal_48.msh",rotation);
291 // Membership must reject fractional coefficients and out-of-plane shifts.
292 Eigen::Matrix<double,3,Eigen::Dynamic> xy(3,2);
293 xy.col(0)=Eigen::Vector3d::UnitX();
294 xy.col(1)=Eigen::Vector3d::UnitY();
295 require(model::periodicLatticeContains<3>(xy,Eigen::Vector3d(1,-1,0),1.e-8),"Diagonal membership failed");
296 require(!model::periodicLatticeContains<3>(xy,Eigen::Vector3d(0.5,0,0),1.e-8),"Fractional translation accepted");
297 require(!model::periodicLatticeContains<3>(xy,Eigen::Vector3d(1,0,1),1.e-8),"Out-of-span translation accepted");
298 std::cout<<"Periodic mesh tests passed\n";
299 }
300 catch(const std::exception& error)
301 {
302 std::cerr<<error.what()<<std::endl;
303 return 1;
304 }
305 return 0;
306}
bool periodicLatticeContains(const Eigen::Matrix< double, dim, Eigen::Dynamic > &basis, const Eigen::Matrix< double, dim, 1 > &shift, double tolerance)
PeriodicMeshLattice< dim > discoverPeriodicMeshLattice(const std::vector< Polyhedron< dim, dim-1 > * > &faces, double tolerance)
bool isPeriodicFacePair(const Polyhedron< dim, dim-1 > &a, const Polyhedron< dim, dim-1 > &b, Eigen::Matrix< double, dim, 1 > &shift, double tolerance, PeriodicSimplexPairMap< dim > *correspondence=nullptr)
int regions
Definition triangle.cpp:696
void require(bool condition, const std::string &message)
void check(const std::string &path, int expected, const Eigen::Matrix3d &rotation)
void checkBicrystal(const std::string &path, const Eigen::Matrix3d &rotation)
REAL ** triangle
Definition triangle.cpp:508
void argv
int axis
void b int argc
int k