Mechanics of Defect Evolution Library
Loading...
Searching...
No Matches
PeriodicMeshFacePairing.h
Go to the documentation of this file.
1#ifndef model_PeriodicMeshFacePairing_h_
2#define model_PeriodicMeshFacePairing_h_
3
4#include <Polyhedron.h>
6#include <Eigen/LU>
7#include <algorithm>
8#include <cmath>
9#include <limits>
10#include <functional>
11#include <map>
12#include <set>
13#include <stdexcept>
14#include <vector>
15
16namespace model
17{
18 // Compare the complete boundary discretization, not just its convex hull.
19 template<int dim>
21 Eigen::Matrix<double,dim,1>& shift, double tolerance,
22 PeriodicSimplexPairMap<dim>* correspondence=nullptr)
23 {
24 using Vertex = Simplex<dim,0>;
25 if(&a == &b || !a.isExternal() || !b.isExternal() || a.simplices().size() != b.simplices().size()
26 || (a.outNormal()+b.outNormal()).norm() > 1.e-6)
27 {
28 return false;
29 }
30 std::set<const Vertex*> av, bv;
31 for(const auto* t : a.simplices())
32 {
33 for(const auto* v : t->vertices())
34 {
35 av.insert(v);
36 }
37 }
38 for(const auto* t : b.simplices())
39 {
40 for(const auto* v : t->vertices())
41 {
42 bv.insert(v);
43 }
44 }
45 if(av.empty() || av.size() != bv.size())
46 {
47 return false;
48 }
49 Eigen::Matrix<double,dim,1> ca = Eigen::Matrix<double,dim,1>::Zero();
50 Eigen::Matrix<double,dim,1> cb = ca;
51 for(const auto* v : av)
52 {
53 ca += (v->P0-(*av.begin())->P0)/double(av.size());
54 }
55 for(const auto* v : bv)
56 {
57 cb += (v->P0-(*bv.begin())->P0)/double(bv.size());
58 }
59 shift = ((*bv.begin())->P0-(*av.begin())->P0)+cb-ca;
60 if(shift.norm() <= tolerance || std::abs(a.outNormal().dot(shift.normalized())) <= 1.e-6)
61 {
62 return false;
63 }
65 auto remaining = b.simplices();
66 for(const auto* ta : a.simplices())
67 {
68 auto match = remaining.end();
69 for(auto it = remaining.begin(); it != remaining.end(); ++it)
70 {
71 const auto* tb = *it;
72 if((ta->outNormal()+tb->outNormal()).norm() > 1.e-6
73 || (ta->center()+shift-tb->center()).norm() > tolerance)
74 {
75 continue;
76 }
77 std::set<const Vertex*> vertices;
78 for(const auto* v : tb->vertices())
79 {
80 vertices.insert(v);
81 }
82 for(const auto* va : ta->vertices())
83 {
84 auto found = std::find_if(vertices.begin(), vertices.end(), [&](const Vertex* vb)
85 {
86 return (va->P0+shift-vb->P0).norm() <= tolerance;
87 });
88 if(found == vertices.end())
89 {
90 break;
91 }
92 vertices.erase(found);
93 }
94 if(vertices.empty())
95 {
96 if(match != remaining.end())
97 {
98 return false; // Ambiguous triangle match.
99 }
100 match = it;
101 }
102 }
103 if(match == remaining.end())
104 {
105 return false;
106 }
107 if(correspondence)
108 {
109 PeriodicSimplexPair<dim> record{*match,shift,{}};
110 const auto va=ta->vertices();
111 const auto vb=(*match)->vertices();
112 std::set<size_t> used;
113 for(size_t i=0;i<dim;++i)
114 {
115 size_t count=0;
116 for(size_t j=0;j<dim;++j)
117 {
118 if((va[i]->P0+shift-vb[j]->P0).norm()<=tolerance)
119 {
120 record.vertexPermutation[i]=j;
121 ++count;
122 }
123 }
124 if(count!=1 || !used.insert(record.vertexPermutation[i]).second)
125 {
126 return false;
127 }
128 }
129 matched.emplace(ta,record);
130 }
131 remaining.erase(match);
132 }
133 if(!remaining.empty())
134 {
135 return false;
136 }
137 if(correspondence)
138 {
139 *correspondence=std::move(matched);
140 }
141 return true;
142 }
143
144 template<int dim>
146 {
147 using Face = Polyhedron<dim,dim-1>;
148 using Vector = Eigen::Matrix<double,dim,1>;
149 using Basis = Eigen::Matrix<double,dim,Eigen::Dynamic>;
150 Basis basis = Basis(dim,0);
151 std::map<Face*,std::pair<Vector,Face*>> pairs;
152 };
153
154 template<int dim>
155 bool periodicLatticeContains(const Eigen::Matrix<double,dim,Eigen::Dynamic>& basis,
156 const Eigen::Matrix<double,dim,1>& shift, double tolerance)
157 {
158 if(basis.cols() == 0)
159 {
160 return shift.norm() <= tolerance;
161 }
162 const auto coefficients = basis.fullPivLu().solve(shift).eval();
163 // Check reconstruction as well as integer coefficients: for partial
164 // periodicity the translation must also lie in the basis span.
165 return (basis*coefficients.array().round().matrix()-shift).norm() <= tolerance;
166 }
167
168 template<int dim>
170 const std::vector<Polyhedron<dim,dim-1>*>& faces, double tolerance)
171 {
172 using Vector = Eigen::Matrix<double,dim,1>;
174 using Basis = typename Lattice::Basis;
175 struct Candidate
176 {
177 size_t a, b;
178 Vector shift;
179 };
180 Lattice best;
181 std::vector<Candidate> candidates;
182 std::vector<Vector> generators;
183 for(size_t a=0; a<faces.size(); ++a)
184 {
185 for(size_t b=a+1; b<faces.size(); ++b)
186 {
187 Vector shift;
188 if(!isPeriodicFacePair(*faces[a],*faces[b],shift,tolerance))
189 {
190 continue;
191 }
192 candidates.push_back({a,b,shift});
193 }
194 }
195 // Restore the original disambiguation using the complete, unfiltered
196 // candidate sets. Unique matches are retained, including XY diagonals.
197 std::vector<std::vector<size_t>> incident(faces.size());
198 for(size_t k=0; k<candidates.size(); ++k)
199 {
200 incident[candidates[k].a].push_back(k);
201 incident[candidates[k].b].push_back(k);
202 }
203 std::vector<std::set<size_t>> allowed(faces.size());
204 for(size_t a=0; a<faces.size(); ++a)
205 {
206 for(const size_t k : incident[a])
207 {
208 const Vector& shift = candidates[k].shift;
209 bool compatible = true;
210 if(incident[a].size() > 1)
211 {
212 for(size_t other=0; other<faces.size(); ++other)
213 {
214 if(other == a || incident[other].empty()
215 || std::abs(faces[other]->outNormal().dot(shift.normalized())) <= 1.e-6)
216 {
217 continue;
218 }
219 const bool sameShift = std::any_of(incident[other].begin(),incident[other].end(),[&](size_t j)
220 {
221 return (candidates[j].shift-shift).norm() <= tolerance
222 || (candidates[j].shift+shift).norm() <= tolerance;
223 });
224 if(!sameShift)
225 {
226 compatible = false;
227 break;
228 }
229 }
230 }
231 if(compatible)
232 {
233 allowed[a].insert(k);
234 }
235 }
236 if(allowed[a].size() > 1)
237 {
238 throw std::runtime_error("Multiple compatible periodic partners for face " + std::to_string(faces[a]->sID));
239 }
240 }
241 std::vector<Candidate> unambiguous;
242 for(size_t k=0; k<candidates.size(); ++k)
243 {
244 const auto& pair = candidates[k];
245 const bool forward = allowed[pair.a].count(k);
246 const bool reverse = allowed[pair.b].count(k);
247 if(forward != reverse)
248 {
249 throw std::runtime_error("Periodic face disambiguation is not reciprocal");
250 }
251 if(forward && reverse)
252 {
253 unambiguous.push_back(pair);
254 }
255 }
256 candidates.swap(unambiguous);
257 for(const auto& pair : candidates)
258 {
259 Vector shift = pair.shift;
260 // Fix the sign for repeatable basis selection.
261 for(int k=0; k<dim; ++k)
262 {
263 if(std::abs(shift[k]) > tolerance)
264 {
265 if(shift[k] < 0.0)
266 {
267 shift = -shift;
268 }
269 break;
270 }
271 }
272 bool duplicate = false;
273 for(const auto& previous : generators)
274 {
275 if((shift-previous).norm() <= tolerance || (shift+previous).norm() <= tolerance)
276 {
277 duplicate = true;
278 break;
279 }
280 }
281 if(!duplicate)
282 {
283 generators.push_back(shift);
284 }
285 }
286 // Prefer short independent generators, but test alternatives: independence
287 // alone does not ensure that diagonal translations have integer coordinates.
288 std::sort(generators.begin(),generators.end(),[](const Vector& a,const Vector& b)
289 {
290 return a.squaredNorm() < b.squaredNorm();
291 });
292 double bestScore = std::numeric_limits<double>::infinity();
293 auto validate = [&](const Basis& basis)
294 {
295 Lattice lattice;
296 lattice.basis = basis;
297 for(const auto& pair : candidates)
298 {
299 if(periodicLatticeContains<dim>(basis,pair.shift,tolerance))
300 {
301 if(!lattice.pairs.emplace(faces[pair.a],std::make_pair(pair.shift,faces[pair.b])).second
302 || !lattice.pairs.emplace(faces[pair.b],std::make_pair(Vector(-pair.shift),faces[pair.a])).second)
303 {
304 return; // More than one admissible partner for a face.
305 }
306 }
307 }
308 for(auto* face : faces)
309 {
310 bool required = false;
311 for(int k=0; k<basis.cols(); ++k)
312 {
313 required = required || std::abs(face->outNormal().dot(basis.col(k).normalized())) > 1.e-6;
314 }
315 if(required && !lattice.pairs.count(face))
316 {
317 return;
318 }
319 }
320 const double score = basis.squaredNorm();
321 if(basis.cols() > best.basis.cols() || (basis.cols() == best.basis.cols() && score < bestScore))
322 {
323 best = std::move(lattice);
324 bestScore = score;
325 }
326 };
327 std::function<void(size_t,const Basis&)> search;
328 search = [&](size_t begin,const Basis& basis)
329 {
330 for(size_t j=begin; j<generators.size(); ++j)
331 {
332 Basis extended(dim,basis.cols()+1);
333 extended.leftCols(basis.cols()) = basis;
334 extended.col(basis.cols()) = generators[j];
335 Basis normalized = extended.colwise().normalized();
336 Eigen::FullPivLU<Basis> lu(normalized);
337 lu.setThreshold(1.e-6);
338 if(lu.rank() != extended.cols())
339 {
340 continue;
341 }
342 validate(extended);
343 // A rejected one-direction basis may become valid jointly with
344 // another direction. Never prune based on per-vector coverage.
345 if(extended.cols() < dim)
346 {
347 search(j+1,extended);
348 }
349 }
350 };
351 search(0,Basis(dim,0));
352 if(best.basis.cols() == 0)
353 {
354 throw std::runtime_error("No globally valid periodic lattice found");
355 }
356 return best;
357 }
358
359 // Selection uses partner translations from the validated full lattice. Face
360 // normals and boundary-coverage requirements apply only during discovery.
361 template<int dim>
363 const PeriodicMeshLattice<dim>& full, const std::set<int>& seeds, double tolerance)
364 {
366 using Basis = typename Lattice::Basis;
367 using Vector = typename Lattice::Vector;
368 if(seeds.count(-1))
369 {
370 return full;
371 }
372 Lattice active;
373 if(seeds.empty())
374 {
375 return active;
376 }
377 std::vector<Vector> generators;
378 for(const int id : seeds)
379 {
380 const auto pair = std::find_if(full.pairs.begin(),full.pairs.end(),[&](const auto& item)
381 {
382 return item.first->sID == static_cast<size_t>(id);
383 });
384 if(pair == full.pairs.end())
385 {
386 throw std::runtime_error("Face " + std::to_string(id) + " has no partner in the full periodic lattice");
387 }
388 const Vector shift = pair->second.first;
389 const bool duplicate = std::any_of(generators.begin(),generators.end(),[&](const Vector& previous)
390 {
391 return (shift-previous).norm() <= tolerance || (shift+previous).norm() <= tolerance;
392 });
393 if(!duplicate)
394 {
395 generators.push_back(shift);
396 }
397 }
398 double bestScore = std::numeric_limits<double>::infinity();
399 std::function<void(size_t,const Basis&)> search;
400 search = [&](size_t begin,const Basis& basis)
401 {
402 for(size_t j=begin; j<generators.size(); ++j)
403 {
404 Basis extended(dim,basis.cols()+1);
405 extended.leftCols(basis.cols()) = basis;
406 extended.col(basis.cols()) = generators[j];
407 Eigen::FullPivLU<Basis> lu(extended.colwise().normalized());
408 lu.setThreshold(1.e-6);
409 if(lu.rank() != extended.cols())
410 {
411 continue;
412 }
413 const bool representsAll = std::all_of(generators.begin(),generators.end(),[&](const Vector& shift)
414 {
415 return periodicLatticeContains<dim>(extended,shift,tolerance);
416 });
417 if(representsAll && extended.squaredNorm() < bestScore)
418 {
419 active.basis = extended;
420 bestScore = extended.squaredNorm();
421 }
422 if(extended.cols() < dim)
423 {
424 search(j+1,extended);
425 }
426 }
427 };
428 search(0,Basis(dim,0));
429 if(active.basis.cols() == 0)
430 {
431 throw std::runtime_error("Selected translations require a lattice basis that cannot be formed from the selected face translations");
432 }
433 for(const auto& pair : full.pairs)
434 {
435 if(periodicLatticeContains<dim>(active.basis,pair.second.first,tolerance))
436 {
437 active.pairs.emplace(pair);
438 }
439 }
440 return active;
441 }
442
443}
444#endif
final point of the source segment t
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)
size_t InstanceCount< Derived >::count
PeriodicMeshLattice< dim > discoverPeriodicMeshLattice(const std::vector< Polyhedron< dim, dim-1 > * > &faces, double tolerance)
std::map< const Simplex< dim, dim-1 > *, PeriodicSimplexPair< dim > > PeriodicSimplexPairMap
PeriodicMeshLattice< dim > selectPeriodicMeshLattice(const PeriodicMeshLattice< dim > &full, const std::set< int > &seeds, 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)
Eigen::Matrix< double, dim, 1 > Vector
std::map< Face *, std::pair< Vector, Face * > > pairs
Eigen::Matrix< double, dim, Eigen::Dynamic > Basis
std::array< size_t, dim > vertexPermutation
int end[2]
int j
REAL ca[4]
int k
int i
void vertices
REAL v[4]