15void check(
const std::string& path,
int expected,
const Eigen::Matrix3d& rotation)
18 const auto basis =
mesh.periodicBasis();
19 require(basis.cols() == expected,
"Wrong detected periodic rank: " + path);
23 for(
const auto& entry : region.second->faces())
25 if(entry.second->isExternal())
27 faces.push_back(entry.second.get());
31 const double tol = 1.e-7*(
mesh.xMax()-
mesh.xMin()).norm();
34 size_t diagonalFaces=0;
35 for(
const auto* face : faces)
37 const auto& pair=face->periodicFacePair;
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;
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)
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");
60 "Detected basis does not generate an expected unit translation");
61 const auto seed=std::find_if(faces.begin(),faces.end(),[&](
const auto* face)
63 return face->periodicFacePair.second
64 && face->periodicFacePair.first.normalized().cross(rotation.col(axis)).norm()<1.e-6;
66 require(seed!=faces.end(),
"Missing axis seed face");
67 seeds.insert((*seed)->sID);
68 const int oppositeSeed=(*seed)->periodicFacePair.second->sID;
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");
75 mesh.identifyParallelFaces({
static_cast<int>((*seed)->sID)});
76 require(
mesh.periodicBasis().cols()==1,
"Single face enabled extra directions");
78 for(
const auto* face : faces)
80 if(face->periodicFacePair.second)
83 require(face->periodicFacePair.first.normalized().cross(rotation.col(
axis)).norm()<1.e-6,
84 "Single-axis selection enabled another direction or diagonal");
87 require(activeFaces==
size_t(expected==1?24:(
axis==0?20:18)),
"Incomplete single-axis activation");
89 require(rediscovered.basis.cols()==expected && rediscovered.pairs.size()==pairedFaces,
90 "Active selection changed discovery of the full available lattice");
91 mesh.identifyParallelFaces({oppositeSeed});
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});
99 std::map<size_t,size_t> original;
100 for(
const auto* face : faces)
102 if(face->periodicFacePair.second)
104 original.emplace(face->sID,face->periodicFacePair.second->sID);
107 mesh.identifyParallelFaces(seeds);
108 require(
mesh.periodicBasis().cols()==expected,
"Wrong explicitly selected rank");
109 for(
const auto* face : faces)
111 require(
bool(face->periodicFacePair.second)==
bool(original.count(face->sID)),
"Selected face coverage changed");
112 if(face->periodicFacePair.second)
114 require(original.at(face->sID)==face->periodicFacePair.second->sID,
"Selected partner changed");
119 std::set<int> diagonalSeeds;
120 for(
const int sign : {-1,1})
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)
125 return item->periodicFacePair.second
126 && item->periodicFacePair.first.normalized().cross(diagonal).norm()<1.e-6;
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)
135 if(item->periodicFacePair.second)
138 require(item->periodicFacePair.first.normalized().cross(diagonal).norm()<1.e-6,
139 "One diagonal face activated a different translation");
142 require(activeCount==
size_t(sign==1?8:4),
"Wrong single-diagonal pair count");
143 mesh.identifyParallelFaces(seeds);
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)
152 if(face->periodicFacePair.second)
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");
159 require(activeDiagonals==12,
"Missing diagonal sublattice pairs");
160 diagonalSeeds.insert(*seeds.begin());
161 mesh.identifyParallelFaces(diagonalSeeds);
162 for(
const auto* face : faces)
164 if(original.count(face->sID))
166 require(face->periodicFacePair.second
167 && original.at(face->sID)==face->periodicFacePair.second->sID,
168 "Alternative generators lost a periodic pair");
171 mesh.identifyParallelFaces(seeds);
174 auto incomplete=faces;
175 const auto removed=std::find_if(incomplete.begin(),incomplete.end(),[](
const auto* face)
177 return face->periodicFacePair.second!=nullptr;
179 require(removed!=incomplete.end(),
"Missing test face");
180 incomplete.erase(removed);
181 bool incompleteRejected=
false;
186 catch(
const std::exception&)
188 incompleteRejected=
true;
190 require(incompleteRejected,
"Accepted lattice despite a missing required face partner");
192 for(
auto* face : faces)
194 if(face->periodicFacePair.second)
196 const auto*
triangle = *face->simplices().begin();
198 Eigen::Vector3d shift;
201 require(!matched,
"Accepted differing triangulations with matching hulls");
206 const auto oldBasis=
mesh.periodicBasis();
210 mesh.identifyParallelFaces({2147483647});
212 catch(
const std::exception&)
216 require(rejected,
"Unknown face ID accepted");
217 require((oldBasis-
mesh.periodicBasis()).norm()<tol,
"Failed selection changed mesh");
218 for(
const auto* face : faces)
220 if(!face->periodicFacePair.second)
225 mesh.identifyParallelFaces({
static_cast<int>(face->sID)});
227 catch(
const std::exception&)
231 require(rejected,
"Nonperiodic face accepted");
235 mesh.identifyParallelFaces({});
236 require(
mesh.periodicBasis().cols()==0,
"Empty selection did not clear periodicity");
242 const auto basis=
mesh.periodicBasis();
243 require(basis.cols()==3,
"Bicrystal must have three periodic directions");
245 for(
int k=0;
k<3; ++
k)
247 const Eigen::Vector3d local=rotation.transpose()*basis.col(
k);
249 local.cwiseAbs().maxCoeff(&
axis);
250 Eigen::Vector3d expected=Eigen::Vector3d::Zero();
252 require((local-expected).norm()<1.e-7,
"Bicrystal basis is not box aligned");
253 axes.insert(
static_cast<int>(
axis));
255 require(axes.size()==3,
"Repeated bicrystal direction");
256 size_t pairedFaces=0;
259 for(
const auto& entry : region.second->faces())
261 const auto& face=*entry.second;
262 if(face.periodicFacePair.second)
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");
274 require(pairedFaces==10,
"Expected all five bicrystal face pairs");
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());
292 Eigen::Matrix<double,3,Eigen::Dynamic> xy(3,2);
293 xy.col(0)=Eigen::Vector3d::UnitX();
294 xy.col(1)=Eigen::Vector3d::UnitY();
298 std::cout<<
"Periodic mesh tests passed\n";
300 catch(
const std::exception& error)
302 std::cerr<<error.what()<<std::endl;