21 Eigen::Matrix<double,dim,1>& shift,
double tolerance,
25 if(&
a == &
b || !
a.isExternal() || !
b.isExternal() ||
a.simplices().size() !=
b.simplices().size()
26 || (
a.outNormal()+
b.outNormal()).norm() > 1.e-6)
30 std::set<const Vertex*> av, bv;
31 for(
const auto*
t :
a.simplices())
33 for(
const auto*
v :
t->vertices())
38 for(
const auto*
t :
b.simplices())
40 for(
const auto*
v :
t->vertices())
45 if(av.empty() || av.size() != bv.size())
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)
53 ca += (
v->P0-(*av.begin())->P0)/double(av.size());
55 for(
const auto*
v : bv)
57 cb += (
v->P0-(*bv.begin())->P0)/double(bv.size());
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)
65 auto remaining =
b.simplices();
66 for(
const auto* ta :
a.simplices())
68 auto match = remaining.end();
69 for(
auto it = remaining.begin(); it != remaining.end(); ++it)
72 if((ta->outNormal()+tb->outNormal()).norm() > 1.e-6
73 || (ta->center()+shift-tb->center()).norm() > tolerance)
78 for(
const auto*
v : tb->vertices())
82 for(
const auto* va : ta->vertices())
84 auto found = std::find_if(
vertices.begin(),
vertices.end(), [&](
const Vertex* vb)
86 return (va->P0+shift-vb->P0).norm() <= tolerance;
96 if(match != remaining.end())
103 if(match == remaining.end())
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)
116 for(
size_t j=0;
j<dim;++
j)
118 if((va[
i]->P0+shift-vb[
j]->P0).norm()<=tolerance)
129 matched.emplace(ta,record);
131 remaining.erase(match);
133 if(!remaining.empty())
139 *correspondence=std::move(matched);
156 const Eigen::Matrix<double,dim,1>& shift,
double tolerance)
158 if(basis.cols() == 0)
160 return shift.norm() <= tolerance;
162 const auto coefficients = basis.fullPivLu().solve(shift).eval();
165 return (basis*coefficients.array().round().matrix()-shift).norm() <= tolerance;
172 using Vector = Eigen::Matrix<double,dim,1>;
174 using Basis =
typename Lattice::Basis;
181 std::vector<Candidate> candidates;
182 std::vector<Vector> generators;
183 for(
size_t a=0;
a<faces.size(); ++
a)
185 for(
size_t b=
a+1;
b<faces.size(); ++
b)
192 candidates.push_back({
a,
b,shift});
197 std::vector<std::vector<size_t>> incident(faces.size());
198 for(
size_t k=0;
k<candidates.size(); ++
k)
200 incident[candidates[
k].a].push_back(
k);
201 incident[candidates[
k].b].push_back(
k);
203 std::vector<std::set<size_t>> allowed(faces.size());
204 for(
size_t a=0;
a<faces.size(); ++
a)
206 for(
const size_t k : incident[
a])
208 const Vector& shift = candidates[
k].shift;
209 bool compatible =
true;
210 if(incident[
a].size() > 1)
212 for(
size_t other=0; other<faces.size(); ++other)
214 if(other ==
a || incident[other].empty()
215 || std::abs(faces[other]->outNormal().dot(shift.normalized())) <= 1.e-6)
219 const bool sameShift = std::any_of(incident[other].begin(),incident[other].
end(),[&](
size_t j)
221 return (candidates[
j].shift-shift).norm() <= tolerance
222 || (candidates[
j].shift+shift).norm() <= tolerance;
233 allowed[
a].insert(
k);
236 if(allowed[
a].size() > 1)
238 throw std::runtime_error(
"Multiple compatible periodic partners for face " + std::to_string(faces[
a]->sID));
241 std::vector<Candidate> unambiguous;
242 for(
size_t k=0;
k<candidates.size(); ++
k)
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)
249 throw std::runtime_error(
"Periodic face disambiguation is not reciprocal");
251 if(forward && reverse)
253 unambiguous.push_back(pair);
256 candidates.swap(unambiguous);
257 for(
const auto& pair : candidates)
259 Vector shift = pair.shift;
261 for(
int k=0;
k<dim; ++
k)
263 if(std::abs(shift[
k]) > tolerance)
272 bool duplicate =
false;
273 for(
const auto& previous : generators)
275 if((shift-previous).norm() <= tolerance || (shift+previous).norm() <= tolerance)
283 generators.push_back(shift);
288 std::sort(generators.begin(),generators.end(),[](
const Vector&
a,
const Vector&
b)
290 return a.squaredNorm() < b.squaredNorm();
292 double bestScore = std::numeric_limits<double>::infinity();
293 auto validate = [&](
const Basis& basis)
296 lattice.basis = basis;
297 for(
const auto& pair : candidates)
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)
308 for(
auto* face : faces)
310 bool required =
false;
311 for(
int k=0;
k<basis.cols(); ++
k)
313 required = required || std::abs(face->outNormal().dot(basis.col(
k).normalized())) > 1.e-6;
315 if(required && !lattice.pairs.count(face))
320 const double score = basis.squaredNorm();
321 if(basis.cols() > best.basis.cols() || (basis.cols() == best.basis.cols() && score < bestScore))
323 best = std::move(lattice);
327 std::function<void(
size_t,
const Basis&)> search;
328 search = [&](
size_t begin,
const Basis& basis)
330 for(
size_t j=begin;
j<generators.size(); ++
j)
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())
345 if(extended.cols() < dim)
347 search(
j+1,extended);
351 search(0,Basis(dim,0));
352 if(best.basis.cols() == 0)
354 throw std::runtime_error(
"No globally valid periodic lattice found");
366 using Basis =
typename Lattice::Basis;
367 using Vector =
typename Lattice::Vector;
377 std::vector<Vector> generators;
378 for(
const int id : seeds)
380 const auto pair = std::find_if(full.
pairs.begin(),full.
pairs.end(),[&](
const auto& item)
382 return item.first->sID == static_cast<size_t>(id);
384 if(pair == full.
pairs.end())
386 throw std::runtime_error(
"Face " + std::to_string(
id) +
" has no partner in the full periodic lattice");
388 const Vector shift = pair->second.first;
389 const bool duplicate = std::any_of(generators.begin(),generators.end(),[&](
const Vector& previous)
391 return (shift-previous).norm() <= tolerance || (shift+previous).norm() <= tolerance;
395 generators.push_back(shift);
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)
402 for(
size_t j=begin;
j<generators.size(); ++
j)
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())
413 const bool representsAll = std::all_of(generators.begin(),generators.end(),[&](
const Vector& shift)
415 return periodicLatticeContains<dim>(extended,shift,tolerance);
417 if(representsAll && extended.squaredNorm() < bestScore)
419 active.basis = extended;
420 bestScore = extended.squaredNorm();
422 if(extended.cols() < dim)
424 search(
j+1,extended);
428 search(0,Basis(dim,0));
429 if(active.basis.cols() == 0)
431 throw std::runtime_error(
"Selected translations require a lattice basis that cannot be formed from the selected face translations");
433 for(
const auto& pair : full.
pairs)
437 active.pairs.emplace(pair);
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)