7FastRipsPersistenceDiagram2::FastRipsPersistenceDiagram2(
11 this->setDebugMsgPrefix(
"GeometricRipsPD");
14 points_ = std::vector<std::pair<Point, unsigned>>(n_);
15 for(
unsigned i = 0; i < n_; ++i)
16 points_[i] = std::make_pair(
Point(points[i][0], points[i][1]), i);
17 printMsg(
"Loaded", 0., tm_.getElapsedTime());
22FastRipsPersistenceDiagram2::FastRipsPersistenceDiagram2(
float *data,
int n)
25 this->setDebugMsgPrefix(
"GeometricRipsPD");
28 points_ = std::vector<std::pair<Point, unsigned>>(n_);
29 for(
unsigned i = 0; i < n_; ++i)
30 points_[i] = std::make_pair(
Point(data[2 * i], data[2 * i + 1]), i);
31 printMsg(
"Loaded", 0., tm_.getElapsedTime());
37void FastRipsPersistenceDiagram2::compute0Persistence(T &ph0,
42 UnionFind
UF(nFaces_);
43 std::vector<FiltratedEdge> max_delaunay(nFaces_, FiltratedEdge{{-1, -1}, 0.});
44 computeUrquhart(UF, max_delaunay, parallelSort);
48 for(FiltratedQuadEdge
const &e : urquhart_) {
49 if(UF_p.find(e.e.first)
50 != UF_p.find(e.e.second)) {
51 UF_p.merge(e.e.first, e.e.second);
55 if constexpr(std::is_same_v<T, Diagram>)
59 printMsg(
"MST computed", 0., tm_.getElapsedTime());
62 FastRipsPersistenceDiagram2::compute0Persistence(
Diagram &ph0,
65 FastRipsPersistenceDiagram2::compute0Persistence(
EdgeSet &ph0,
69void FastRipsPersistenceDiagram2::computeDelaunayRips0And1Persistence(
70 T &ph,
bool parallelSort) {
71 if constexpr(std::is_same_v<T, MultidimensionalDiagram>)
75 UnionFind
UF(nFaces_);
76 deathPoly_.resize(nFaces_, {{-1, -1}, 0.});
77 computeUrquhart(UF, deathPoly_, parallelSort);
81 std::vector<FiltratedQuadEdge> critical(0);
82 for(FiltratedQuadEdge
const &e : urquhart_) {
83 if(UF_p.find(e.e.first)
84 != UF_p.find(e.e.second)) {
85 UF_p.merge(e.e.first, e.e.second);
88 critical.push_back(e);
89 if constexpr(std::is_same_v<T, EdgeSets3>)
90 ph[1].emplace_back(e.e);
93 if constexpr(std::is_same_v<T, MultidimensionalDiagram>)
97 printMsg(
"MST computed", 0., tm_.getElapsedTime());
99 for(FiltratedQuadEdge &e : urquhart_) {
103 for(FiltratedQuadEdge &e : critical) {
108 if constexpr(std::is_same_v<T, MultidimensionalDiagram>)
109 compute1PH(critical, UF, ph);
110 else if constexpr(std::is_same_v<T, EdgeSets3>) {
111 for(
unsigned poly = 0; poly < deathPoly_.size(); ++poly) {
112 if(
UF.isRoot(poly) && deathPoly_[poly].d != inf)
113 ph[2].emplace_back(deathPoly_[poly].e);
117template void FastRipsPersistenceDiagram2::computeDelaunayRips0And1Persistence(
119template void FastRipsPersistenceDiagram2::computeDelaunayRips0And1Persistence(
123void FastRipsPersistenceDiagram2::computeRips0And1Persistence(
124 T &ph,
bool parallelSort,
bool parallelMML) {
125 if constexpr(std::is_same_v<T, MultidimensionalDiagram>)
130 for(
auto const &p : points_)
131 tree.insert(p.first);
132 printMsg(
"k-d tree initialized", 0., tm_.getElapsedTime());
135 UnionFind
UF(nFaces_);
136 std::vector<FiltratedEdge> max_delaunay(nFaces_, FiltratedEdge{{-1, -1}, 0.});
137 computeUrquhart(UF, max_delaunay, parallelSort);
141 std::vector<FiltratedQuadEdge> critical(0);
143 for(FiltratedQuadEdge
const &e : urquhart_) {
144 if(UF_p.find(e.e.first)
145 != UF_p.find(e.e.second)) {
146 UF_p.merge(e.e.first, e.e.second);
151 if(isLensEmpty(points_[e.e.first].first, points_[e.e.second].first, tree,
153 critical.push_back(e);
155 if constexpr(std::is_same_v<T,
156 EdgeSets3> || std::is_same_v<T, EdgeSets4>)
157 ph[1].emplace_back(e.e);
159 const int poly1 =
UF.
find(e.f1);
160 const int poly2 =
UF.
find(e.f2);
161 UF.merge(poly1, poly2);
162 max_delaunay[
UF.
find(poly1)]
163 =
max(FiltratedEdge{e.e, e.d},
164 max(max_delaunay[poly1], max_delaunay[poly2]));
168 if constexpr(std::is_same_v<T, MultidimensionalDiagram>)
172 printMsg(
"MST and RNG computed", 0., tm_.getElapsedTime());
175 std::vector<int> index_polys(0);
176 reindexPolygons(UF, max_delaunay, index_polys);
177 for(FiltratedQuadEdge &e : rng_) {
178 e.f1 = index_polys[
UF.
find(e.f1)];
179 e.f2 = index_polys[
UF.
find(e.f2)];
181 for(FiltratedQuadEdge &e : critical) {
182 e.f1 = index_polys[
UF.
find(e.f1)];
183 e.f2 = index_polys[
UF.
find(e.f2)];
186 if constexpr(std::is_same_v<T, MultidimensionalDiagram>) {
187 computePolygonRipsDeath(parallelMML, UF, index_polys);
188 printMsg(
"MML edges computed", 0., tm_.getElapsedTime());
189 UnionFind UF_poly(deathPoly_.size());
190 compute1PH(critical, UF_poly, ph);
191 }
else if constexpr(std::is_same_v<T, EdgeSets3>) {
192 computePolygonRipsDeath(parallelMML, UF, index_polys);
193 printMsg(
"MML edges computed", 0., tm_.getElapsedTime());
194 for(FiltratedEdge
const &poly : deathPoly_) {
196 ph[2].emplace_back(poly.e);
198 }
else if constexpr(std::is_same_v<T, EdgeSets4>) {
199 executePolygonPairCells(parallelMML, UF, index_polys, ph);
200 printMsg(
"Local cascades computed", 0., tm_.getElapsedTime());
203template void FastRipsPersistenceDiagram2::computeRips0And1Persistence(
205template void FastRipsPersistenceDiagram2::computeRips0And1Persistence(
206 EdgeSets3 &ph,
bool parallelSort,
bool parallelMML);
207template void FastRipsPersistenceDiagram2::computeRips0And1Persistence(
208 EdgeSets4 &ph,
bool parallelSort,
bool parallelMML);
210void FastRipsPersistenceDiagram2::computeDelaunay() {
213 delaunay_ = Delaunay(points_.begin(), points_.end());
215 for(
auto const &f : delaunay_.all_face_handles())
219 printMsg(
"Delaunay triangulation computed", 0., tm_.getElapsedTime());
222void FastRipsPersistenceDiagram2::computeUrquhart(
223 UnionFind &UF, std::vector<FiltratedEdge> &maxDelaunay,
bool parallelSort) {
224 for(Delaunay::Edge
const &e : delaunay_.finite_edges()) {
225 const unsigned a = e.first->vertex((e.second + 1) % 3)->info();
226 const unsigned b = e.first->vertex((e.second + 2) % 3)->info();
228 = CGAL::squared_distance(points_[a].first, points_[b].first);
229 const double d = sqrt(d2);
230 const Delaunay::Edge e_m = delaunay_.mirror_edge(e);
233 bool is_urquhart =
true;
234 const Point p_k = e.first->vertex(e.second)->point();
235 if(!delaunay_.is_infinite(e.first)
236 && CGAL::squared_distance(points_[a].first, p_k) < d2
237 && CGAL::squared_distance(points_[b].first, p_k) < d2)
240 const Point p_l = e_m.first->vertex(e_m.second)->point();
241 if(!delaunay_.is_infinite(e_m.first)
242 && CGAL::squared_distance(points_[a].first, p_l) < d2
243 && CGAL::squared_distance(points_[b].first, p_l) < d2)
250 FiltratedQuadEdge{{a, b}, e.first->id(), e_m.first->id(), d});
252 const int poly1 =
UF.
find(e.first->id());
253 const int poly2 =
UF.
find(e_m.first->id());
254 maxDelaunay[
UF.mergeRet(poly1, poly2)] =
max(
255 FiltratedEdge{{a, b}, d},
max(maxDelaunay[poly1], maxDelaunay[poly2]));
259 if(delaunay_.is_infinite(e.first))
260 maxDelaunay[
UF.
find(e.first->id())].d =
inf;
261 else if(delaunay_.is_infinite(e_m.first))
262 maxDelaunay[
UF.
find(e_m.first->id())].d =
inf;
264 printMsg(
"Urquhart graph computed", 0., tm_.getElapsedTime());
268 globalThreadNumber_, urquhart_.begin(), urquhart_.end(),
269 [](FiltratedQuadEdge e1, FiltratedQuadEdge e2) { return e1.d < e2.d; })
272 urquhart_.begin(), urquhart_.end(),
273 [](FiltratedQuadEdge e1, FiltratedQuadEdge e2) { return e1.d < e2.d; });
274 printMsg(
"Urquhart graph sorted", 0., tm_.getElapsedTime());
277void FastRipsPersistenceDiagram2::compute1PH(
278 std::vector<FiltratedQuadEdge>
const &critical,
281 std::vector<int> latest(deathPoly_.size());
282 std::iota(latest.begin(), latest.end(), 0);
283 birthPoly_.resize(deathPoly_.size());
285 for(
auto it = critical.rbegin(); it != critical.rend(); ++it) {
286 const FiltratedQuadEdge e = *it;
292 const int latest1 = latest[v1];
293 const int latest2 = latest[v2];
295 if(deathPoly_[latest1].d < deathPoly_[latest2].d) {
296 ph[1].emplace_back(FiltratedSimplex{{e.
e.first, e.
e.second}, e.
d},
298 deathPoly_[latest1].e.second},
299 deathPoly_[latest1].d});
300 birthPoly_[latest1] = e.
d;
301 latest[
UF.
find(v1)] = latest2;
303 ph[1].emplace_back(FiltratedSimplex{{e.
e.first, e.
e.second}, e.
d},
305 deathPoly_[latest2].e.second},
306 deathPoly_[latest2].d});
307 birthPoly_[latest2] = e.
d;
308 latest[
UF.
find(v1)] = latest1;
312 printMsg(
"Dual Kruskal complete", 0., tm_.getElapsedTime());
315bool FastRipsPersistenceDiagram2::isLensEmpty(
Point const &p1,
319 const Fuzzy_sphere fs(p1, d);
320 std::vector<Point> ball;
321 tree.search(back_inserter(ball), fs);
322 for(
Point const &p : ball) {
323 if(p != p1 && p != p2) {
324 if(CGAL::squared_distance(p, p2) < d * d)
331bool FastRipsPersistenceDiagram2::isRightSemiLensEmpty(
Point const &p1,
334 const double d2 = CGAL::squared_distance(p1, p2);
335 const Fuzzy_sphere fs(p1, sqrt(d2));
336 std::vector<Point> ball;
337 tree.search(back_inserter(ball), fs);
338 for(
Point const &p : ball) {
339 if(CGAL::squared_distance(p, p2) < d2 && CGAL::right_turn(p, p1, p2))
345void FastRipsPersistenceDiagram2::computePolygonRipsDeath(
346 bool parallel,
UnionFind &UF, std::vector<int>
const &indexPolys) {
347 const unsigned N_polys = deathPoly_.size();
348 std::vector<std::vector<int>> vertices(N_polys);
351 for(
const FiltratedQuadEdge &e : rng_) {
352 vertices[e.
f1].push_back(e.
e.first);
353 vertices[e.
f1].push_back(e.
e.second);
354 vertices[e.
f2].push_back(e.
e.first);
355 vertices[e.
f2].push_back(e.
e.second);
357 std::vector<Delaunay::Face_handle> repr_face(N_polys);
358 for(
auto const &fh : delaunay_.finite_face_handles()) {
359 if(
UF.isRoot(fh->id()))
360 repr_face[indexPolys[fh->id()]] = fh;
364#ifdef TTK_ENABLE_OPENMP
365#pragma omp parallel for if(parallel)
369 for(
unsigned poly = 0; poly < N_polys; ++poly) {
370 if(deathPoly_[poly].d != inf) {
371 const double bound_max = deathPoly_[poly].d;
372 const double bound_min = sqrt(3) / 2 * bound_max;
375 sort(vertices[poly].
begin(), vertices[poly].
end());
376 auto last = unique(vertices[poly].
begin(), vertices[poly].
end());
377 vertices[poly].erase(last, vertices[poly].
end());
379 std::vector<Point> pts(0);
380 std::vector<int> global(0);
381 for(
int const &v : vertices[poly]) {
382 pts.push_back(points_[v].first);
385 const unsigned N_pts = pts.size();
386 const Tree loc_tree(pts.begin(), pts.end());
389 FiltratedEdge death{{-1, -1},
inf};
390 for(
unsigned i = 0; i < N_pts - 1; ++i) {
391 for(
unsigned j = i + 1; j < N_pts; ++j) {
392 const double d2 = CGAL::squared_distance(pts[i], pts[j]);
393 const double d = sqrt(d2);
394 if(bound_min <= d && d <= bound_max
397 const Point p_i = pts[i];
398 const Point p_j = pts[j];
400 const Fuzzy_sphere fs(p_i, d);
401 std::vector<Point> ball;
402 loc_tree.search(back_inserter(ball), fs);
405 std::vector<Point> lens_l;
406 std::vector<Point> lens_r;
407 for(
auto const &p : ball) {
408 if(p != p_i && p != p_j) {
409 if(CGAL::squared_distance(p, p_j) < d2) {
410 if(CGAL::right_turn(p_i, p_j, p))
419 if(!lens_l.empty() && !lens_r.empty()) {
420 bool l_expandable =
false, r_expandable =
false;
421 for(
Point const &p : lens_l) {
422 if(!isRightSemiLensEmpty(p_i, p, loc_tree))
424 else if(isRightSemiLensEmpty(p, p_j, loc_tree)) {
431 for(
Point const &p : lens_r) {
432 if(!isRightSemiLensEmpty(p_j, p, loc_tree))
434 else if(isRightSemiLensEmpty(p, p_i, loc_tree)) {
443 .locate(CGAL::midpoint(p_i, p_j), repr_face[poly])
446 death = FiltratedEdge{{global[i], global[j]}, d};
452 deathPoly_[poly] = death;
457void FastRipsPersistenceDiagram2::reindexPolygons(
459 std::vector<FiltratedEdge>
const &maxDelaunay,
460 std::vector<int> &indexPolys) {
462 indexPolys = std::vector<int>(nFaces_, -1);
464 for(
unsigned x = 0; x < nFaces_; ++x) {
466 indexPolys[x] = N_polys;
467 deathPoly_.push_back(maxDelaunay[x]);
473void FastRipsPersistenceDiagram2::pComputePolygonRipsDeath(
474 UnionFind &UF, std::vector<int>
const &indexPolys) {
475 const unsigned N_polys = deathPoly_.size();
476 std::vector<std::vector<int>> vertices(N_polys);
479 for(FiltratedQuadEdge
const &e : rng_) {
480 vertices[e.
f1].push_back(e.
e.first);
481 vertices[e.
f1].push_back(e.
e.second);
482 vertices[e.
f2].push_back(e.
e.first);
483 vertices[e.
f2].push_back(e.
e.second);
485 std::vector<Delaunay::Face_handle> repr_face(N_polys);
486 for(
auto const &fh : delaunay_.finite_face_handles()) {
487 if(
UF.isRoot(fh->id()))
488 repr_face[indexPolys[fh->id()]] = fh;
491 std::vector<std::pair<FiltratedEdge, int>> candidates;
492 std::vector<Tree> trees(N_polys);
495 for(
unsigned poly = 0; poly < N_polys; ++poly) {
496 if(deathPoly_[poly].d != inf) {
497 const double bound_max = deathPoly_[poly].d;
498 const double bound_min = sqrt(3) / 2 * bound_max;
501 sort(vertices[poly].
begin(), vertices[poly].
end());
502 auto last = unique(vertices[poly].
begin(), vertices[poly].
end());
503 vertices[poly].erase(last, vertices[poly].
end());
505 std::vector<Point> pts(0);
506 std::vector<int> global(0);
507 for(
int const &v : vertices[poly]) {
508 pts.push_back(points_[v].first);
511 const unsigned N_pts = pts.size();
512 trees[poly].insert(pts.begin(), pts.end());
515 for(
unsigned i = 0; i < N_pts - 1; ++i) {
516 for(
unsigned j = i + 1; j < N_pts; ++j) {
517 const double d2 = CGAL::squared_distance(pts[i], pts[j]);
518 const double d = sqrt(d2);
519 if(bound_min <= d && d <= bound_max)
521 candidates.emplace_back(
522 FiltratedEdge{{global[i], global[j]}, d}, poly);
528 std::vector<bool> validCandidate(candidates.size());
529#ifdef TTK_ENABLE_OPENMP
530#pragma omp parallel for
532 for(
unsigned i = 0; i < candidates.size(); ++i) {
533 const Point p_i = points_[candidates[i].first.e.first].first;
534 const Point p_j = points_[candidates[i].first.e.second].first;
535 const double d = candidates[i].first.d;
536 const int poly = candidates[i].second;
538 const Fuzzy_sphere fs(p_i, d);
539 std::vector<Point> ball;
540 trees[poly].search(back_inserter(ball), fs);
543 std::vector<Point> lens_l;
544 std::vector<Point> lens_r;
545 for(
auto const &p : ball) {
546 if(p != p_i && p != p_j) {
547 if(CGAL::squared_distance(p, p_j) < d * d) {
548 if(CGAL::right_turn(p_i, p_j, p))
557 if(!lens_l.empty() && !lens_r.empty()) {
558 bool l_expandable =
false, r_expandable =
false;
559 for(
Point const &p : lens_l) {
560 if(!isRightSemiLensEmpty(p_i, p, trees[poly]))
562 else if(isRightSemiLensEmpty(p, p_j, trees[poly])) {
569 for(
Point const &p : lens_r) {
570 if(!isRightSemiLensEmpty(p_j, p, trees[poly]))
572 else if(isRightSemiLensEmpty(p, p_i, trees[poly])) {
580 delaunay_.locate(CGAL::midpoint(p_i, p_j), repr_face[poly])->id())]
582 validCandidate[i] =
true;
587 deathPoly_ = std::vector<FiltratedEdge>(N_polys, {{-1, -1},
inf});
588 for(
unsigned i = 0; i < candidates.size(); ++i) {
589 if(validCandidate[i]) {
590 const int poly = candidates[i].second;
591 if(candidates[i].first.d < deathPoly_[poly].d)
592 deathPoly_[poly] = candidates[i].first;
597void FastRipsPersistenceDiagram2::exportRips1Generators(
598 std::vector<Generator1> &generators) {
599 const unsigned N_polys = deathPoly_.size();
601 std::vector<Generator1> generators_(N_polys);
602 for(
unsigned i = 0; i < deathPoly_.size(); ++i)
603 generators_[i].second = {birthPoly_[i], deathPoly_[i].d};
604 for(
const FiltratedQuadEdge &e : (N_polys == nFaces_) ? urquhart_ : rng_) {
606 if(deathPoly_[e.
f1].d != inf)
607 generators_[e.
f1].first.push_back(e.
e);
608 if(deathPoly_[e.
f2].d != inf)
609 generators_[e.
f2].first.push_back(e.
e);
613 generators.resize(0);
614 for(Generator1
const &g : generators_) {
616 generators.push_back(g);
620void FastRipsPersistenceDiagram2::executePolygonPairCells(
623 std::vector<int>
const &indexPolys,
625 std::set<Edge> cascadeSet{};
627 const unsigned N_polys = deathPoly_.size();
628 std::vector<std::vector<int>> vertices(N_polys);
631 for(
const FiltratedQuadEdge &e : rng_) {
632 vertices[e.
f1].push_back(e.
e.first);
633 vertices[e.
f1].push_back(e.
e.second);
634 vertices[e.
f2].push_back(e.
e.first);
635 vertices[e.
f2].push_back(e.
e.second);
637 std::vector<Delaunay::Face_handle> repr_face(N_polys);
638 for(
auto const &fh : delaunay_.finite_face_handles()) {
639 if(
UF.isRoot(fh->id()))
640 repr_face[indexPolys[fh->id()]] = fh;
644#ifdef TTK_ENABLE_OPENMP
645#pragma omp parallel for if(parallel)
649 for(
unsigned poly = 0; poly < N_polys; ++poly) {
650 if(deathPoly_[poly].d != inf) {
651 const double bound_max = deathPoly_[poly].d;
654 sort(vertices[poly].
begin(), vertices[poly].
end());
655 auto last = unique(vertices[poly].
begin(), vertices[poly].
end());
656 vertices[poly].erase(last, vertices[poly].
end());
658 std::vector<Point> pts(0);
659 std::vector<int> global(0);
660 for(
int const &v : vertices[poly]) {
661 pts.push_back(points_[v].first);
665 PairCells pc(pts, bound_max);
667 pc.enrichCascades(cascadeSet, ph, global);
671 for(
const Edge &e : cascadeSet)
672 ph[3].emplace_back(e);
#define TTK_FORCE_USE(x)
Force the compiler to use the function/method parameter.
#define TTK_PSORT(NTHREADS,...)
Parallel sort macro.
boost::tuple< double, double > Point
FiltratedEdge max(const FiltratedEdge &a, const FiltratedEdge &b)
std::array< EdgeSet, 4 > EdgeSets4
std::vector< std::vector< value_t > > PointCloud
std::vector< Edge > EdgeSet
std::vector< Diagram > MultidimensionalDiagram
std::pair< Simplex, value_t > FiltratedSimplex
std::array< EdgeSet, 3 > EdgeSets3
std::vector< PersistencePair > Diagram
T end(std::pair< T, T > &p)
T begin(std::pair< T, T > &p)
printMsg(debug::output::BOLD+" | | | | | . \\ | | (__| | / __/| |_| / __/| (_) |"+debug::output::ENDCOLOR, debug::Priority::PERFORMANCE, debug::LineMode::NEW, stream)