Mechanics of Defect Evolution Library
Loading...
Searching...
No Matches
testPeriodicFEMNodes.cpp
Go to the documentation of this file.
1#include <FiniteElement.h>
3#include <Eigen/Geometry>
4#include <filesystem>
5#include <iostream>
6#include <numeric>
7#include <set>
8
9void require(bool value,const std::string& message)
10{
11 if(!value)
12 {
13 throw std::runtime_error(message);
14 }
15}
16
18{
19 size_t expected=0;
20 std::set<const model::Polyhedron<3,3-1>*> seen;
21 const double tol=1.e-7*(mesh.xMax()-mesh.xMin()).norm();
22 for(const auto& region : mesh.regions())
23 {
24 for(const auto& entry : region.second->faces())
25 {
26 const auto* face=entry.second.get();
27 if(!seen.insert(face).second)
28 {
29 continue;
30 }
31 for(const auto* triangle : face->simplices())
32 {
33 const auto found=mesh.periodicSimplexPairs().find(triangle);
34 if(!face->periodicFacePair.second)
35 {
36 require(found==mesh.periodicSimplexPairs().end(),"Inactive or internal simplex was paired");
37 continue;
38 }
39 ++expected;
40 require(found!=mesh.periodicSimplexPairs().end(),"Active face triangle missing");
41 const auto& pair=found->second;
42 require(triangle->isBoundarySimplex() && pair.partner->isBoundarySimplex(),"Internal simplex paired");
43 require(face->periodicFacePair.second->simplices().count(pair.partner),"Partner on wrong face");
44 const auto& reverse=mesh.periodicSimplexPairs().at(pair.partner);
45 require(reverse.partner==triangle && (reverse.shift+pair.shift).norm()<tol,"Bad reciprocal simplex pair");
46 require((triangle->outNormal()+pair.partner->outNormal()).norm()<1.e-7,"Normals not antiparallel");
47 for(size_t i=0;i<3;++i)
48 {
49 const size_t j=pair.vertexPermutation[i];
50 require(j<3 && reverse.vertexPermutation[j]==i,"Bad inverse vertex permutation");
51 require((triangle->vertices()[i]->P0+pair.shift-pair.partner->vertices()[j]->P0).norm()<tol,"Bad vertex translation");
52 }
53 }
54 }
55 }
56 require(mesh.periodicSimplexPairs().size()==expected,"Unexpected simplex pairs");
57}
58
59template<int order>
61{
62 const auto& mesh=fe.mesh;
63 const double tol=1.e-7*(mesh.xMax()-mesh.xMin()).norm();
64 using Key=std::pair<size_t,size_t>;
65 std::set<Key> expected;
66 // Independent geometric oracle: use plane distances and translated nodal
67 // positions, not barycentric integer permutations used by production code.
68 for(const auto& entry : mesh.periodicSimplexPairs())
69 {
70 const auto* triangle=entry.first;
71 const auto& pair=entry.second;
72 const auto& a=fe.elements().at(triangle->parents().begin()->first);
73 const auto& b=fe.elements().at(pair.partner->parents().begin()->first);
74 size_t count=0;
75 for(size_t i=0;i<a.nodesPerElement;++i)
76 {
77 const auto& source=a.node(i);
78 if(std::abs(triangle->outNormal().dot(source.P0-triangle->center()))>tol)
79 {
80 continue;
81 }
82 ++count;
83 size_t matches=0;
84 for(size_t j=0;j<b.nodesPerElement;++j)
85 {
86 const auto& sink=b.node(j);
87 if((source.P0+pair.shift-sink.P0).norm()<tol)
88 {
89 expected.emplace(std::minmax(source.gID,sink.gID));
90 ++matches;
91 }
92 }
93 require(matches==1,"Geometric FEM partner is missing or ambiguous");
94 }
95 require(count==size_t((order+1)*(order+2)/2),"Wrong number of face nodes for polynomial order");
96 }
97 require(fe.periodicNodePairs().size()==expected.size(),"FEM pairing coverage or deduplication failure");
98 std::vector<size_t> parents(fe.nodeSize());
99 std::iota(parents.begin(),parents.end(),0);
100 const auto root=[&](size_t i)
101 {
102 while(parents[i]!=i)
103 {
104 i=parents[i];
105 }
106 return i;
107 };
108 for(const auto& pair : expected)
109 {
110 const size_t a=root(pair.first),b=root(pair.second);
111 parents[std::max(a,b)]=std::min(a,b);
112 }
113 std::set<size_t> vertexNodes;
114 for(const auto& entry : fe.mesh2femIDmap())
115 {
116 vertexNodes.insert(entry.second->gID);
117 }
118 size_t extraNodes=0;
119 for(const auto& pair : fe.periodicNodePairs())
120 {
121 require(expected.count({pair.source,pair.sink}),"Spurious periodic FEM pair");
122 require((fe.node(pair.source).P0+pair.shift-fe.node(pair.sink).P0).norm()<tol,"Wrong FEM pair offset");
123 require(model::periodicLatticeContains<3>(mesh.periodicBasis(),pair.shift,tol),"Inactive periodic direction used");
124 require((fe.periodicShift(pair.source)+pair.shift-fe.periodicShift(pair.sink)).norm()<tol,"Edge/corner path inconsistency");
125 extraNodes+=vertexNodes.count(pair.source)==0;
126 }
127 std::map<size_t,size_t> orbitSizes;
128 for(size_t n=0;n<fe.nodeSize();++n)
129 {
130 require(fe.periodicRepresentative(n)==root(n),"Wrong periodic equivalence class");
131 require((fe.node(root(n)).P0+fe.periodicShift(n)-fe.node(n).P0).norm()<tol,"Wrong representative offset");
132 ++orbitSizes[root(n)];
133 }
134 if(rank==0)
135 {
136 require(expected.empty(),"Nonperiodic domain has paired FEM nodes");
137 }
138 else
139 {
140 require(!expected.empty(),"Periodic domain has no FEM pairs");
141 require(order==1 || extraNodes>0,"Higher-order nodes were not paired");
142 }
143 // Only the box-shaped bicrystal guarantees four/eight coincident copies.
144 // Non-convex cells are checked against the independent pairing graph above.
145 if(rank>=2 && mesh.regions().size()==2)
146 {
147 size_t largest=0;
148 for(const auto& orbit : orbitSizes)
149 {
150 largest=std::max(largest,orbit.second);
151 }
152 require(largest>=4,"No edge/corner equivalence class exercised");
153 if(rank==3)
154 {
155 require(largest>=8,"Bicrystal corner equivalence class is incomplete");
156 }
157 }
158}
159
160template<int order>
161void exerciseOrder(model::SimplicialMesh<3>& mesh,const std::set<int>& partial)
162{
164 checkNodes(fe,mesh.periodicBasis().cols());
165 // Exercise a two-direction subset of a three-dimensional lattice as well.
166 if(mesh.periodicBasis().cols()==3)
167 {
168 auto twoDirections=partial;
169 for(const auto& region : mesh.regions())
170 {
171 for(const auto& entry : region.second->faces())
172 {
173 const auto& pair=entry.second->periodicFacePair;
174 if(pair.second && pair.first.cross(mesh.periodicBasis().col(1)).norm()<1.e-7*pair.first.norm()*mesh.periodicBasis().col(1).norm())
175 {
176 twoDirections.insert(int(entry.first));
177 break;
178 }
179 }
180 if(twoDirections.size()==2)
181 {
182 break;
183 }
184 }
185 require(twoDirections.size()==2,"No second independent seed face");
186 mesh.identifyParallelFaces(twoDirections);
187 require(mesh.periodicBasis().cols()==2,"Two-face selection did not select two vectors");
190 checkNodes(fe,2);
191 }
192 // Explicit refresh must clear obsolete pairs and preserve FE node IDs.
193 const auto nodeCount=fe.nodeSize();
194 mesh.identifyParallelFaces(partial);
196 require(mesh.periodicBasis().cols()==1,"Partial selection did not select one vector");
198 checkNodes(fe,1);
199 mesh.identifyParallelFaces({});
202 checkNodes(fe,0);
203 mesh.identifyParallelFaces({-1});
206 checkNodes(fe,mesh.periodicBasis().cols());
207 require(fe.nodeSize()==nodeCount,"Periodicity changed FEM node numbering");
208}
209
210int main(int argc,char** argv)
211{
212 try
213 {
214 require(argc==2,"Expected repository root");
215 const std::filesystem::path meshes=std::filesystem::path(argv[1])/"Library/Meshes";
216 for(const auto* name : {"bicrystal_48.msh","poly30_periodicXYZ.msh","polycrystalCube_10_periodicX.msh","polycrystalCube_10_periodicXY.msh"})
217 {
218 // Rotated, translated coordinates ensure pairing does not assume
219 // global axes or matching local tetrahedron vertex order.
220 const Eigen::Matrix3d rotation=Eigen::AngleAxisd(.37,Eigen::Vector3d(1,2,3).normalized()).toRotationMatrix();
221 model::SimplicialMesh<3> mesh((meshes/name).string(),rotation,Eigen::Vector3d(2,-3,1),{-1});
223 const int expectedRank=std::string(name).find("periodicXY.msh")!=std::string::npos ? 2 :
224 (std::string(name).find("periodicX.msh")!=std::string::npos ? 1 : 3);
225 require(mesh.periodicBasis().cols()==expectedRank,"Unexpected tutorial lattice rank");
226 std::set<int> partial;
227 for(const auto& region : mesh.regions())
228 {
229 for(const auto& entry : region.second->faces())
230 {
231 const auto& pair=entry.second->periodicFacePair;
232 if(pair.second && pair.first.cross(mesh.periodicBasis().col(0)).norm()<1.e-7*pair.first.norm()*mesh.periodicBasis().col(0).norm())
233 {
234 partial={int(entry.first)};
235 break;
236 }
237 }
238 if(!partial.empty())
239 {
240 break;
241 }
242 }
243 require(!partial.empty(),"No single-vector seed face");
244 exerciseOrder<1>(mesh,partial);
245 exerciseOrder<2>(mesh,partial);
246 exerciseOrder<3>(mesh,partial);
247 std::cout<<"PASS periodic FEM orders 1,2,3: "<<name<<std::endl;
248 }
249 }
250 catch(const std::exception& error)
251 {
252 std::cerr<<error.what()<<std::endl;
253 return 1;
254 }
255 return 0;
256}
size_t nodeSize() const
Class template for a Lagrange finite element of degree p on a d-dimensional Simplex.
a
Definition int_R.m:14
b
Definition int_R.m:16
bool periodicLatticeContains(const Eigen::Matrix< double, dim, Eigen::Dynamic > &basis, const Eigen::Matrix< double, dim, 1 > &shift, double tolerance)
int regions
Definition triangle.cpp:696
void exerciseOrder(model::SimplicialMesh< 3 > &mesh, const std::set< int > &partial)
void checkSimplices(const model::SimplicialMesh< 3 > &mesh)
void require(bool value, const std::string &message)
void checkNodes(model::FiniteElement< model::LagrangeElement< 3, order > > &fe, int rank)
int j
REAL ** triangle
Definition triangle.cpp:508
void argv
void b int argc
b order
int i