TTK
Loading...
Searching...
No Matches
geoPH3.h
Go to the documentation of this file.
1#pragma once
2
3#include <geoPHUtils.h>
4
5#ifdef TTK_ENABLE_CGAL
6
7#include <CGAL/Delaunay_triangulation_3.h>
8#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
9#include <CGAL/Triangulation_cell_base_with_info_3.h>
10#include <CGAL/Triangulation_vertex_base_with_info_3.h>
11
12namespace ttk::gph {
13
14 using rpd::Edge;
15 using rpd::FiltratedEdge;
17 using rpd::Generator1;
18 using rpd::Generator2;
19 using rpd::inf;
21 using rpd::UnionFind;
22
23 using Facet = std::array<id_t, 3>;
24
25 struct FiltratedFacet {
26 Facet f;
27 double d;
28 };
29
30 inline FiltratedFacet max(FiltratedFacet const &a, FiltratedFacet const &b) {
31 if(a.d > b.d)
32 return a;
33 return b;
34 }
35
36 struct FiltratedQuadFacet {
37 Facet f;
38 int c1;
39 int c2;
40 double d;
41 double a;
42 };
43
44 class DRPersistence3 {
45 using K = CGAL::Exact_predicates_inexact_constructions_kernel;
46 using Vb = CGAL::Triangulation_vertex_base_with_info_3<int, K>;
47 using Fb = CGAL::Triangulation_cell_base_with_info_3<int, K>;
48 using Tds = CGAL::Triangulation_data_structure_3<Vb, Fb>;
49 using Delaunay = CGAL::Delaunay_triangulation_3<K, Tds>;
50 using Point = Delaunay::Point_3;
51
52 using AdjacencyList = std::vector<int>;
53 using ConnectivityHashMap = HashMap<Edge, std::pair<AdjacencyList, bool>>;
54
55 public:
56 explicit DRPersistence3(const PointCloud<3> &points)
57 : N_p(points.size()), p(points) {
58 }
59
64 void run(MultidimensionalDiagram &ph) {
66
67 computeDelaunay();
68
69 UnionFind UF(N_c);
70 computeFacetUrquhart(UF);
71 compute2PH(UF, ph);
72
73 connectivity();
74 computePolygons(ph);
75 compute1PH(ph);
76 }
77
82 void run(MultidimensionalDiagram &ph,
83 std::vector<Generator1> &generators1,
84 std::vector<Generator2> &generators2) {
86 generators1.resize(0);
87 generators2.resize(0);
88
89 computeDelaunay();
90
91 UnionFind UF(N_c);
92 computeFacetUrquhart(UF);
93 compute2PH(UF, ph, generators2);
94
95 connectivity();
96 computePolygons(ph);
97 compute1PH(ph, generators1);
98 }
99
100 private:
101 const unsigned N_p;
102 unsigned N_c{0};
103 const PointCloud<3> &p;
104 Delaunay del;
105
106 // 1-dimensional
107 std::vector<FiltratedEdge> urquhart;
108 std::vector<FiltratedEdge> critical1;
109
110 ConnectivityHashMap msa_edges{};
111
112 UnionFind UF_msa{0};
113
114 // 2-dimensional
115 std::vector<FiltratedQuadFacet> hyperUrquhart;
116 std::vector<FiltratedFacet> msa;
117 std::vector<FiltratedFacet> maxDelaunay;
118
119 [[nodiscard]] double squaredDistance(const unsigned i1,
120 const unsigned i2) const {
121 PointD<3> const p1 = p[i1], p2 = p[i2];
122 return (p1[0] - p2[0]) * (p1[0] - p2[0])
123 + (p1[1] - p2[1]) * (p1[1] - p2[1])
124 + (p1[2] - p2[2]) * (p1[2] - p2[2]);
125 }
126 [[nodiscard]] static std::pair<double, double>
127 perturbedDiameter(const double d1, const double d2, const double d3) {
128 return {std::max(d1, std::max(d2, d3)), d1 + d2 + d3};
129 }
130
135 void computeDelaunay() {
136 std::vector<std::pair<Point, unsigned>> points(N_p);
137 for(unsigned i = 0; i < N_p; ++i)
138 points[i] = std::make_pair(Point(p[i][0], p[i][1], p[i][2]), i);
139 del = Delaunay(points.begin(), points.end());
140 int k = 0;
141 for(auto const &c : del.all_cell_handles())
142 c->info() = k++;
143 N_c = k;
144 }
145
146 void computeFacetUrquhart(UnionFind &UF) {
147 maxDelaunay.resize(N_c, FiltratedFacet{{-1, -1, -1}, 0.});
148 for(Delaunay::Facet const &f : del.all_facets()) {
149 if(del.is_infinite(f))
150 UF.merge(f.first->info(), del.mirror_facet(f).first->info());
151 else {
152 Facet facet{f.first->vertex((f.second + 1) % 4)->info(),
153 f.first->vertex((f.second + 2) % 4)->info(),
154 f.first->vertex((f.second + 3) % 4)->info()};
155 const double d01 = squaredDistance(facet[0], facet[1]);
156 const double d02 = squaredDistance(facet[0], facet[2]);
157 const double d12 = squaredDistance(facet[1], facet[2]);
158 const auto diam = perturbedDiameter(d01, d02, d12);
159 const Delaunay::Facet f_m = del.mirror_facet(f);
160
161 // check if facet is Urquhart
162 bool is_urquhart = true;
163 if(!del.is_infinite(f.first->vertex(f.second))) {
164 const unsigned k = f.first->vertex(f.second)->info();
165 const double d0k = squaredDistance(facet[0], k);
166 const double d1k = squaredDistance(facet[1], k);
167 const double d2k = squaredDistance(facet[2], k);
168 if(perturbedDiameter(d01, d0k, d1k) < diam
169 && perturbedDiameter(d02, d0k, d2k) < diam
170 && perturbedDiameter(d12, d1k, d2k) < diam)
171 is_urquhart = false;
172 }
173 if(is_urquhart && !del.is_infinite(f_m.first->vertex(f_m.second))) {
174 const unsigned l = f_m.first->vertex(f_m.second)->info();
175 const double d0l = squaredDistance(facet[0], l);
176 const double d1l = squaredDistance(facet[1], l);
177 const double d2l = squaredDistance(facet[2], l);
178 if(perturbedDiameter(d01, d0l, d1l) < diam
179 && perturbedDiameter(d02, d0l, d2l) < diam
180 && perturbedDiameter(d12, d1l, d2l) < diam)
181 is_urquhart = false;
182 }
183
184 if(is_urquhart) { // UH facet
185 std::sort(facet.begin(), facet.end());
186 hyperUrquhart.push_back({facet, f.first->info(), f_m.first->info(),
187 sqrt(diam.first), diam.second});
188 } else {
189 const int poly1 = UF.find(f.first->info());
190 const int poly2 = UF.find(f_m.first->info());
191 maxDelaunay[UF.mergeRet(poly1, poly2)]
192 = max({facet, sqrt(diam.first)},
193 max(maxDelaunay[poly1], maxDelaunay[poly2]));
194 }
195
196 // detect infinite polyhedrons (going beyond convex hull)
197 if(del.is_infinite(f.first))
198 maxDelaunay[UF.find(f.first->info())].d = inf;
199 else if(del.is_infinite(f_m.first))
200 maxDelaunay[UF.find(f_m.first->info())].d = inf;
201 }
202 }
203
204 std::sort(hyperUrquhart.begin(), hyperUrquhart.end(),
205 [](const FiltratedQuadFacet &f1, const FiltratedQuadFacet &f2) {
206 if(f1.d == f2.d)
207 return f1.a > f2.a;
208 return f1.d > f2.d;
209 });
210 }
211
212 void compute2PH(UnionFind &UF, MultidimensionalDiagram &ph) {
213 std::vector<int> latest(maxDelaunay.size());
214 std::iota(latest.begin(), latest.end(), 0);
215
216 for(FiltratedQuadFacet const &f :
217 hyperUrquhart) { // sorted by decreasing order
218 const int v1 = UF.find(f.c1);
219 const int v2 = UF.find(f.c2);
220 if(v1
221 != v2) { // two distinct cavities: merge them by deleting the facet
222 UF.merge(v1, v2);
223
224 const int latest1 = latest[v1];
225 const int latest2 = latest[v2];
226 const FiltratedFacet &death1 = maxDelaunay[latest1];
227 const FiltratedFacet &death2 = maxDelaunay[latest2];
228
229 if(death1.d < death2.d) {
230 if(f.d < death1.d)
231 ph[2].emplace_back(
232 FiltratedSimplex{{f.f[0], f.f[1], f.f[2]}, f.d},
234 {death1.f[0], death1.f[1], death1.f[2]}, death1.d});
235 latest[UF.find(v1)] = latest2;
236 } else if(death2.d < death1.d) {
237 if(f.d < death2.d)
238 ph[2].emplace_back(
239 FiltratedSimplex{{f.f[0], f.f[1], f.f[2]}, f.d},
241 {death2.f[0], death2.f[1], death2.f[2]}, death2.d});
242 latest[UF.find(v1)] = latest1;
243 }
244 } else // this is a facet from the minimal spanning acycle
245 msa.push_back({f.f, f.d});
246 }
247 }
248
249 void compute2PH(UnionFind &UF,
250 MultidimensionalDiagram &ph,
251 std::vector<Generator2> &generators2) {
252 std::vector<int> latest(maxDelaunay.size());
253 std::iota(latest.begin(), latest.end(), 0);
254
255 std::vector<std::vector<Facet>> elementary_generators(N_c);
256 for(const FiltratedQuadFacet &f : hyperUrquhart) {
257 if(UF.find(f.c1) != UF.find(f.c2)) {
258 if(maxDelaunay[UF.find(f.c1)].d != inf)
259 elementary_generators[UF.find(f.c1)].push_back(f.f);
260 if(maxDelaunay[UF.find(f.c2)].d != inf)
261 elementary_generators[UF.find(f.c2)].push_back(f.f);
262 }
263 }
264 std::vector<std::vector<int>> cascade(N_c, {0});
265 for(unsigned i = 0; i < N_c; i++)
266 cascade[i][0] = i;
267
268 for(FiltratedQuadFacet const &f :
269 hyperUrquhart) { // sorted by decreasing order
270 const int v1 = UF.find(f.c1);
271 const int v2 = UF.find(f.c2);
272 if(v1
273 != v2) { // two distinct cavities: merge them by deleting the facet
274 UF.merge(v1, v2);
275
276 const int latest1 = latest[v1];
277 const int latest2 = latest[v2];
278 const FiltratedFacet &death1 = maxDelaunay[latest1];
279 const FiltratedFacet &death2 = maxDelaunay[latest2];
280
281 if(death1.d < death2.d) {
282 if(f.d < death1.d) {
283 ph[2].emplace_back(
284 FiltratedSimplex{{f.f[0], f.f[1], f.f[2]}, f.d},
286 {death1.f[0], death1.f[1], death1.f[2]}, death1.d});
287 HashMap<Facet, unsigned> generator;
288 for(auto const &c : cascade[latest1]) {
289 for(Facet const &f_ : elementary_generators[c])
290 generator[f_]++;
291 }
292 std::vector<Facet> generator_facets;
293 for(auto const &[f_, v] : generator) {
294 if(v % 2 == 1)
295 generator_facets.push_back(f_);
296 }
297 generators2.push_back({generator_facets, {f.d, death1.d}});
298 }
299 latest[UF.find(v1)] = latest2;
300 cascade[latest2].insert(cascade[latest2].end(),
301 cascade[latest1].begin(),
302 cascade[latest1].end());
303 } else if(death2.d < death1.d) {
304 if(f.d < death2.d) {
305 ph[2].emplace_back(
306 FiltratedSimplex{{f.f[0], f.f[1], f.f[2]}, f.d},
308 {death2.f[0], death2.f[1], death2.f[2]}, death2.d});
309 HashMap<Facet, unsigned> generator;
310 for(auto const &c : cascade[latest2]) {
311 for(Facet const &f_ : elementary_generators[c])
312 generator[f_]++;
313 }
314 std::vector<Facet> generator_facets;
315 for(auto const &[f_, v] : generator) {
316 if(v % 2 == 1)
317 generator_facets.push_back(f_);
318 }
319 generators2.push_back({generator_facets, {f.d, death2.d}});
320 }
321 latest[UF.find(v1)] = latest1;
322 cascade[latest1].insert(cascade[latest1].end(),
323 cascade[latest2].begin(),
324 cascade[latest2].end());
325 }
326 } else // this is a facet from the minimal spanning acycle
327 msa.push_back({f.f, f.d});
328 }
329 }
330
331 void connectivity() {
332 // edges to facets adjacency
333 msa_edges.reserve(
334 1.15 * N_c); // this is an estimation of the number of edges in Delaunay
335 for(unsigned i = 0; i < msa.size(); ++i) {
336 const FiltratedFacet f = msa[i];
337 const double d1 = squaredDistance(f.f[0], f.f[1]);
338 const double d2 = squaredDistance(f.f[0], f.f[2]);
339 const double d3 = squaredDistance(f.f[1], f.f[2]);
340 auto &edge1 = msa_edges[std::make_pair(f.f[0], f.f[1])];
341 edge1.first.reserve(4);
342 edge1.first.push_back(i);
343 edge1.second |= d1 > d2 && d1 > d3;
344 auto &edge2 = msa_edges[std::make_pair(f.f[0], f.f[2])];
345 edge2.first.reserve(4);
346 edge2.first.push_back(i);
347 edge2.second |= d2 > d1 && d2 > d3;
348 auto &edge3 = msa_edges[std::make_pair(f.f[1], f.f[2])];
349 edge3.first.reserve(4);
350 edge3.first.push_back(i);
351 edge3.second |= d3 > d1 && d3 > d2;
352 }
353 }
354
355 void computePolygons(MultidimensionalDiagram &ph) {
356 const unsigned N_msa = msa.size();
357 UF_msa = UnionFind(N_msa);
358
359 for(const auto &[e, val] : msa_edges) {
360 auto &[adj_f, isNotUrquhart] = val;
361 if(isNotUrquhart) { // not UG edge
362 if(adj_f.size() == 1)
363 msa[UF_msa.find(adj_f[0])].d = inf;
364 else if(adj_f.size() == 2)
365 UF_msa.merge(adj_f[0], adj_f[1]);
366 else // non-manifold junction edge
367 critical1.push_back({e, sqrt(squaredDistance(e.first, e.second))});
368 } else
369 urquhart.push_back({e, sqrt(squaredDistance(e.first, e.second))});
370 }
371
372 for(unsigned x = 0; x < N_msa; ++x) {
373 const int poly = UF_msa.find(x);
374 msa[poly] = max(msa[poly], msa[x]);
375 }
376
377 std::sort(urquhart.begin(), urquhart.end(),
378 [](const FiltratedEdge &a, const FiltratedEdge &b) {
379 return a.d < b.d;
380 });
381 UnionFind UF_p(N_p);
382 ph[0].reserve(N_p - 1);
383 for(FiltratedEdge const &e : urquhart) {
384 if(UF_p.find(e.e.first)
385 != UF_p.find(e.e.second)) { // we know e is a EMST edge
386 UF_p.merge(e.e.first, e.e.second);
387 ph[0].emplace_back(FiltratedSimplex{{-1}, 0.},
388 FiltratedSimplex{{e.e.first, e.e.second}, e.d});
389 } else
390 critical1.emplace_back(e);
391 }
392 }
393
394 void compute1PH(MultidimensionalDiagram &ph) {
395 const unsigned N_msa = msa.size();
396 std::vector<int> polys;
397 for(unsigned x = 0; x < N_msa; ++x) {
398 if(UF_msa.isRoot(x) && msa[x].d < inf)
399 polys.push_back(x);
400 }
401
402 std::sort(polys.begin(), polys.end(), [&](const int x1, const int x2) {
403 return msa[x1].d < msa[x2].d;
404 });
405
406 std::vector<int> criticalIndices(critical1.size());
407 std::iota(criticalIndices.begin(), criticalIndices.end(), 0);
408 std::sort(criticalIndices.begin(), criticalIndices.end(),
409 [&](const int e1_id, const int e2_id) {
410 return critical1[e1_id].d < critical1[e2_id].d;
411 });
412 std::vector<int> criticalOrder(critical1.size());
413 for(unsigned i = 0; i < criticalIndices.size(); ++i)
414 criticalOrder[criticalIndices[i]] = i;
415
416 std::vector<std::vector<int>> poly_to_crit(N_msa);
417 for(const int poly : polys)
418 poly_to_crit[poly].reserve(3);
419 for(unsigned i = 0; i < critical1.size(); ++i) {
420 const FiltratedEdge &e = critical1[i];
421 for(const int poly : msa_edges[e.e].first) {
422 if(msa[UF_msa.find(poly)].d < inf) {
423 auto &neighbors = poly_to_crit[UF_msa.find(poly)];
424 auto it
425 = std::find(neighbors.begin(), neighbors.end(), criticalOrder[i]);
426 if(it == neighbors.end())
427 neighbors.push_back(criticalOrder[i]);
428 else
429 neighbors.erase(it);
430 }
431 }
432 }
433
434 std::vector<int> partner(critical1.size(), -1);
435 for(const int poly : polys) {
436 HashSet<int> boundary(poly_to_crit[poly].begin(),
437 poly_to_crit[poly].end(),
438 poly_to_crit[poly].size());
439 while(true) {
440 const int youngest_id
441 = *std::max_element(boundary.begin(), boundary.end());
442 if(partner[youngest_id] == -1) {
443 partner[youngest_id] = poly;
444 const FiltratedEdge &e = critical1[criticalIndices[youngest_id]];
445 const FiltratedFacet &death = msa[poly];
446 if(e.d < death.d)
447 ph[1].emplace_back(
448 FiltratedSimplex{{e.e.first, e.e.second}, e.d},
450 {death.f[0], death.f[1], death.f[2]}, death.d});
451 break;
452 } else {
453 for(const int crit : poly_to_crit[partner[youngest_id]]) {
454 const auto it = boundary.find(crit);
455 if(it == boundary.end())
456 boundary.insert(crit);
457 else
458 boundary.erase(it);
459 }
460 }
461 }
462 poly_to_crit[poly].assign(boundary.begin(), boundary.end());
463 }
464 }
465
466 void compute1PH(MultidimensionalDiagram &ph,
467 std::vector<Generator1> &generators1) {
468 const unsigned N_msa = msa.size();
469 std::vector<int> polys;
470 for(unsigned x = 0; x < N_msa; ++x) {
471 if(UF_msa.isRoot(x) && msa[x].d < inf)
472 polys.push_back(x);
473 }
474
475 std::sort(polys.begin(), polys.end(), [&](const int x1, const int x2) {
476 return msa[x1].d < msa[x2].d;
477 });
478
479 std::vector<int> criticalIndices(critical1.size());
480 std::iota(criticalIndices.begin(), criticalIndices.end(), 0);
481 std::sort(criticalIndices.begin(), criticalIndices.end(),
482 [&](const int e1_id, const int e2_id) {
483 return critical1[e1_id].d < critical1[e2_id].d;
484 });
485 std::vector<int> criticalOrder(critical1.size());
486 for(unsigned i = 0; i < criticalIndices.size(); ++i)
487 criticalOrder[criticalIndices[i]] = i;
488
489 std::vector<std::vector<int>> poly_to_crit(N_msa);
490 for(const int poly : polys)
491 poly_to_crit[poly].reserve(3);
492 for(unsigned i = 0; i < critical1.size(); ++i) {
493 const FiltratedEdge &e = critical1[i];
494 for(const int poly : msa_edges[e.e].first) {
495 if(msa[UF_msa.find(poly)].d < inf) {
496 auto &neighbors = poly_to_crit[UF_msa.find(poly)];
497 auto it
498 = std::find(neighbors.begin(), neighbors.end(), criticalOrder[i]);
499 if(it == neighbors.end())
500 neighbors.push_back(criticalOrder[i]);
501 else
502 neighbors.erase(it);
503 }
504 }
505 }
506
507 std::vector<std::vector<Edge>> elementary_generators(N_msa);
508 auto action = [&](const FiltratedEdge &e) {
509 auto adj_f = msa_edges[e.e].first;
510 for(int &f : adj_f)
511 f = UF_msa.find(f);
512 std::sort(adj_f.begin(), adj_f.end());
513 const auto last = std::unique(adj_f.begin(), adj_f.end());
514 for(auto it = adj_f.begin(); it != last; ++it) {
515 if(msa[UF_msa.find(*it)].d != inf)
516 elementary_generators[UF_msa.find(*it)].push_back(e.e);
517 }
518 };
519 for(auto const &e : urquhart)
520 action(e);
521 for(auto const &e : critical1) {
522 if(msa_edges[e.e]
523 .second) // if critical but not urquhart -> non manifold junctions
524 action(e);
525 }
526
527 std::vector<int> partner(critical1.size(), -1);
528 for(const int poly : polys) {
529 HashSet<int> boundary(poly_to_crit[poly].begin(),
530 poly_to_crit[poly].end(),
531 poly_to_crit[poly].size());
532 std::vector<Edge> &generator = elementary_generators[poly];
533 while(true) {
534 const int youngest_id
535 = *std::max_element(boundary.begin(), boundary.end());
536 if(partner[youngest_id] == -1) {
537 partner[youngest_id] = poly;
538 const FiltratedEdge &e = critical1[criticalIndices[youngest_id]];
539 const FiltratedFacet &death = msa[poly];
540 if(e.d < death.d) {
541 ph[1].emplace_back(
542 FiltratedSimplex{{e.e.first, e.e.second}, e.d},
544 {death.f[0], death.f[1], death.f[2]}, death.d});
545 generators1.emplace_back(generator, std::make_pair(e.d, death.d));
546 }
547 break;
548 } else {
549 for(const int crit : poly_to_crit[partner[youngest_id]]) {
550 const auto it = boundary.find(crit);
551 if(it == boundary.end())
552 boundary.insert(crit);
553 else
554 boundary.erase(it);
555 }
556 for(Edge const &e : elementary_generators[partner[youngest_id]]) {
557 auto it = std::find(generator.begin(), generator.end(), e);
558 if(it == generator.end())
559 generator.push_back(e);
560 else
561 generator.erase(it);
562 }
563 }
564 }
565 poly_to_crit[poly].assign(boundary.begin(), boundary.end());
566 }
567 }
568 };
569
570#ifdef TTK_GPH_PARALLEL
571 class DRPersistence3_p {
572 using K = CGAL::Exact_predicates_inexact_constructions_kernel;
573 using Vb = CGAL::Triangulation_vertex_base_with_info_3<int, K>;
574 using Fb = CGAL::Triangulation_cell_base_with_info_3<int, K>;
575 using Tds = CGAL::
576 Triangulation_data_structure_3<Vb, Fb, CGAL::Parallel_if_available_tag>;
577 using Delaunay = CGAL::Delaunay_triangulation_3<K, Tds>;
578 using Point = Delaunay::Point_3;
579
580 using AdjacencyList = std::vector<int>;
581 using ConnectivityHashMap = HashMap<Edge, std::pair<AdjacencyList, bool>>;
582 using ConcurrentConnectivityHashMap
583 = ConcurrentHashMap<Edge, std::pair<AdjacencyList, bool>>;
584
585 public:
586 explicit DRPersistence3_p(const PointCloud<3> &points,
587 const unsigned nThreads = 1)
588 : N_p(points.size()), p(points), nThreads_(nThreads) {
589 }
590
595 void run(MultidimensionalDiagram &ph) {
597
598 computeDelaunay();
599
600 UnionFind UF(N_c);
601 computeFacetUrquhart(UF);
602 compute2PH(UF, ph);
603
604 connectivity();
605 computePolygons(ph);
606 compute1PH(ph);
607 }
608
613 void run(MultidimensionalDiagram &ph,
614 std::vector<Generator1> &generators1,
615 std::vector<Generator2> &generators2) {
617 generators1.resize(0);
618 generators2.resize(0);
619
620 computeDelaunay();
621
622 UnionFind UF(N_c);
623 computeFacetUrquhart(UF);
624 compute2PH(UF, ph, generators2);
625
626 connectivity();
627 computePolygons(ph);
628 compute1PH(ph, generators1);
629 }
630
631 private:
632 const unsigned N_p;
633 unsigned N_c{0};
634 const PointCloud<3> &p;
635 Delaunay del;
636 const int nThreads_{1};
637
638 // 1-dimensional
639 tbb::concurrent_vector<FiltratedEdge> urquhart;
640 tbb::concurrent_vector<FiltratedEdge> critical1;
641
642 ConcurrentConnectivityHashMap msa_edges{};
643
644 DisjointSets UF_msa{0};
645
646 // 2-dimensional
647 std::vector<FiltratedQuadFacet> hyperUrquhart;
648 std::vector<FiltratedFacet> msa;
649 std::vector<FiltratedFacet> maxDelaunay;
650
651 [[nodiscard]] double squaredDistance(const unsigned i1,
652 const unsigned i2) const {
653 PointD<3> const p1 = p[i1], p2 = p[i2];
654 return (p1[0] - p2[0]) * (p1[0] - p2[0])
655 + (p1[1] - p2[1]) * (p1[1] - p2[1])
656 + (p1[2] - p2[2]) * (p1[2] - p2[2]);
657 }
658 [[nodiscard]] static std::pair<double, double>
659 perturbedDiameter(const double d1, const double d2, const double d3) {
660 return {std::max(d1, std::max(d2, d3)), d1 + d2 + d3};
661 }
662
667 void computeDelaunay() {
668 tbb::global_control gc(
669 tbb::global_control::max_allowed_parallelism, nThreads_);
670 std::vector<std::pair<Point, unsigned>> points(N_p);
671 CGAL::Bbox_3 bbox(p[0][0], p[0][1], p[0][2], p[0][0], p[0][1], p[0][2]);
672 for(unsigned i = 0; i < N_p; ++i) {
673 points[i] = std::make_pair(Point(p[i][0], p[i][1], p[i][2]), i);
674 bbox
675 += CGAL::Bbox_3(p[i][0], p[i][1], p[i][2], p[i][0], p[i][1], p[i][2]);
676 }
677 Delaunay::Lock_data_structure lock_ds(bbox, 100);
678 del = Delaunay(points.begin(), points.end(), &lock_ds);
679 int k = 0;
680 for(auto const &c : del.all_cell_handles())
681 c->info() = k++;
682 N_c = k;
683 }
684
685 void computeFacetUrquhart(UnionFind &UF) {
686 maxDelaunay.resize(N_c, FiltratedFacet{{-1, -1, -1}, 0.});
687 for(Delaunay::Facet const &f : del.all_facets()) {
688 if(del.is_infinite(f))
689 UF.merge(f.first->info(), del.mirror_facet(f).first->info());
690 else {
691 Facet facet{f.first->vertex((f.second + 1) % 4)->info(),
692 f.first->vertex((f.second + 2) % 4)->info(),
693 f.first->vertex((f.second + 3) % 4)->info()};
694 const double d01 = squaredDistance(facet[0], facet[1]);
695 const double d02 = squaredDistance(facet[0], facet[2]);
696 const double d12 = squaredDistance(facet[1], facet[2]);
697 const auto diam = perturbedDiameter(d01, d02, d12);
698 const Delaunay::Facet f_m = del.mirror_facet(f);
699
700 // check if facet is Urquhart
701 bool is_urquhart = true;
702 if(!del.is_infinite(f.first->vertex(f.second))) {
703 const unsigned k = f.first->vertex(f.second)->info();
704 const double d0k = squaredDistance(facet[0], k);
705 const double d1k = squaredDistance(facet[1], k);
706 const double d2k = squaredDistance(facet[2], k);
707 if(perturbedDiameter(d01, d0k, d1k) < diam
708 && perturbedDiameter(d02, d0k, d2k) < diam
709 && perturbedDiameter(d12, d1k, d2k) < diam)
710 is_urquhart = false;
711 }
712 if(is_urquhart && !del.is_infinite(f_m.first->vertex(f_m.second))) {
713 const unsigned l = f_m.first->vertex(f_m.second)->info();
714 const double d0l = squaredDistance(facet[0], l);
715 const double d1l = squaredDistance(facet[1], l);
716 const double d2l = squaredDistance(facet[2], l);
717 if(perturbedDiameter(d01, d0l, d1l) < diam
718 && perturbedDiameter(d02, d0l, d2l) < diam
719 && perturbedDiameter(d12, d1l, d2l) < diam)
720 is_urquhart = false;
721 }
722
723 if(is_urquhart) { // UH facet
724 std::sort(facet.begin(), facet.end());
725 hyperUrquhart.push_back({facet, f.first->info(), f_m.first->info(),
726 sqrt(diam.first), diam.second});
727 } else {
728 const int poly1 = UF.find(f.first->info());
729 const int poly2 = UF.find(f_m.first->info());
730 maxDelaunay[UF.mergeRet(poly1, poly2)]
731 = max({facet, sqrt(diam.first)},
732 max(maxDelaunay[poly1], maxDelaunay[poly2]));
733 }
734
735 // detect infinite polyhedrons (going beyond convex hull)
736 if(del.is_infinite(f.first))
737 maxDelaunay[UF.find(f.first->info())].d = inf;
738 else if(del.is_infinite(f_m.first))
739 maxDelaunay[UF.find(f_m.first->info())].d = inf;
740 }
741 }
742
743 TTK_PSORT(nThreads_, hyperUrquhart.begin(), hyperUrquhart.end(),
744 [](const FiltratedQuadFacet &f1, const FiltratedQuadFacet &f2) {
745 if(f1.d == f2.d)
746 return f1.a > f2.a;
747 return f1.d > f2.d;
748 });
749 }
750
751 void compute2PH(UnionFind &UF, MultidimensionalDiagram &ph) {
752 std::vector<int> latest(maxDelaunay.size());
753 std::iota(latest.begin(), latest.end(), 0);
754
755 for(FiltratedQuadFacet const &f :
756 hyperUrquhart) { // sorted by decreasing order
757 const int v1 = UF.find(f.c1);
758 const int v2 = UF.find(f.c2);
759 if(v1
760 != v2) { // two distinct cavities: merge them by deleting the facet
761 UF.merge(v1, v2);
762
763 const int latest1 = latest[v1];
764 const int latest2 = latest[v2];
765 const FiltratedFacet &death1 = maxDelaunay[latest1];
766 const FiltratedFacet &death2 = maxDelaunay[latest2];
767
768 if(death1.d < death2.d) {
769 if(f.d < death1.d)
770 ph[2].emplace_back(
771 FiltratedSimplex{{f.f[0], f.f[1], f.f[2]}, f.d},
773 {death1.f[0], death1.f[1], death1.f[2]}, death1.d});
774 latest[UF.find(v1)] = latest2;
775 } else if(death2.d < death1.d) {
776 if(f.d < death2.d)
777 ph[2].emplace_back(
778 FiltratedSimplex{{f.f[0], f.f[1], f.f[2]}, f.d},
780 {death2.f[0], death2.f[1], death2.f[2]}, death2.d});
781 latest[UF.find(v1)] = latest1;
782 }
783 } else // this is a facet from the minimal spanning acycle
784 msa.push_back({f.f, f.d});
785 }
786 }
787
788 void compute2PH(UnionFind &UF,
789 MultidimensionalDiagram &ph,
790 std::vector<Generator2> &generators2) {
791 std::vector<int> latest(maxDelaunay.size());
792 std::iota(latest.begin(), latest.end(), 0);
793
794 std::vector<std::vector<Facet>> elementary_generators(N_c);
795 for(const FiltratedQuadFacet &f : hyperUrquhart) {
796 if(UF.find(f.c1) != UF.find(f.c2)) {
797 if(maxDelaunay[UF.find(f.c1)].d != inf)
798 elementary_generators[UF.find(f.c1)].push_back(f.f);
799 if(maxDelaunay[UF.find(f.c2)].d != inf)
800 elementary_generators[UF.find(f.c2)].push_back(f.f);
801 }
802 }
803 std::vector<std::vector<int>> cascade(N_c, {0});
804 for(unsigned i = 0; i < N_c; i++)
805 cascade[i][0] = i;
806
807 for(FiltratedQuadFacet const &f :
808 hyperUrquhart) { // sorted by decreasing order
809 const int v1 = UF.find(f.c1);
810 const int v2 = UF.find(f.c2);
811 if(v1
812 != v2) { // two distinct cavities: merge them by deleting the facet
813 UF.merge(v1, v2);
814
815 const int latest1 = latest[v1];
816 const int latest2 = latest[v2];
817 const FiltratedFacet &death1 = maxDelaunay[latest1];
818 const FiltratedFacet &death2 = maxDelaunay[latest2];
819
820 if(death1.d < death2.d) {
821 if(f.d < death1.d) {
822 ph[2].emplace_back(
823 FiltratedSimplex{{f.f[0], f.f[1], f.f[2]}, f.d},
825 {death1.f[0], death1.f[1], death1.f[2]}, death1.d});
826 HashMap<Facet, unsigned> generator;
827 for(auto const &c : cascade[latest1]) {
828 for(Facet const &f_ : elementary_generators[c])
829 generator[f_]++;
830 }
831 std::vector<Facet> generator_facets;
832 for(auto const &[f_, v] : generator) {
833 if(v % 2 == 1)
834 generator_facets.push_back(f_);
835 }
836 generators2.push_back({generator_facets, {f.d, death1.d}});
837 }
838 latest[UF.find(v1)] = latest2;
839 cascade[latest2].insert(cascade[latest2].end(),
840 cascade[latest1].begin(),
841 cascade[latest1].end());
842 } else if(death2.d < death1.d) {
843 if(f.d < death2.d) {
844 ph[2].emplace_back(
845 FiltratedSimplex{{f.f[0], f.f[1], f.f[2]}, f.d},
847 {death2.f[0], death2.f[1], death2.f[2]}, death2.d});
848 HashMap<Facet, unsigned> generator;
849 for(auto const &c : cascade[latest2]) {
850 for(Facet const &f_ : elementary_generators[c])
851 generator[f_]++;
852 }
853 std::vector<Facet> generator_facets;
854 for(auto const &[f_, v] : generator) {
855 if(v % 2 == 1)
856 generator_facets.push_back(f_);
857 }
858 generators2.push_back({generator_facets, {f.d, death2.d}});
859 }
860 latest[UF.find(v1)] = latest1;
861 cascade[latest1].insert(cascade[latest1].end(),
862 cascade[latest2].begin(),
863 cascade[latest2].end());
864 }
865 } else // this is a facet from the minimal spanning acycle
866 msa.push_back({f.f, f.d});
867 }
868 }
869
870 void connectivity() {
871 omp_set_num_threads(nThreads_);
872 // edges to facets adjacency
873 msa_edges.reserve(
874 1.15 * N_c); // this is an estimation of the number of edges in Delaunay
875#pragma omp parallel for
876 for(unsigned i = 0; i < msa.size(); ++i) {
877 const FiltratedFacet f = msa[i];
878 const double d1 = squaredDistance(f.f[0], f.f[1]);
879 const double d2 = squaredDistance(f.f[0], f.f[2]);
880 const double d3 = squaredDistance(f.f[1], f.f[2]);
881 msa_edges.emplace_or_visit(
882 std::make_pair(f.f[0], f.f[1]),
883 std::make_pair(AdjacencyList{(int)i}, d1 > d2 && d1 > d3),
884 [&](auto &x) {
885 x.second.first.push_back(i);
886 x.second.second |= d1 > d2 && d1 > d3;
887 });
888 msa_edges.emplace_or_visit(
889 std::make_pair(f.f[0], f.f[2]),
890 std::make_pair(AdjacencyList{(int)i}, d2 > d1 && d2 > d3),
891 [&](auto &x) {
892 x.second.first.push_back(i);
893 x.second.second |= d2 > d1 && d2 > d3;
894 });
895 msa_edges.emplace_or_visit(
896 std::make_pair(f.f[1], f.f[2]),
897 std::make_pair(AdjacencyList{(int)i}, d3 > d1 && d3 > d2),
898 [&](auto &x) {
899 x.second.first.push_back(i);
900 x.second.second |= d3 > d1 && d3 > d2;
901 });
902 }
903 }
904
905 void computePolygons(MultidimensionalDiagram &ph) {
906 omp_set_num_threads(nThreads_);
907 const unsigned N_msa = msa.size();
908 UF_msa = DisjointSets(N_msa);
909
910 msa_edges.cvisit_all(
911#ifdef __cpp_lib_execution
912 std::execution::par,
913#endif
914 [&](const auto &x) {
915 auto &[e, val] = x;
916 auto &[adj_f, isNotUrquhart] = val;
917 if(isNotUrquhart) { // not UG edge
918 if(adj_f.size() == 1)
919 msa[adj_f[0]].d = inf;
920 if(adj_f.size() == 2)
921 UF_msa.unite(adj_f[0], adj_f[1]);
922 else // non-manifold junction edge
923 critical1.push_back(
924 {e, sqrt(squaredDistance(e.first, e.second))});
925 } else
926 urquhart.push_back({e, sqrt(squaredDistance(e.first, e.second))});
927 });
928
929 std::vector<std::mutex> maxDelaunayLocks(N_msa);
930#pragma omp parallel for
931 for(unsigned x = 0; x < N_msa; ++x) {
932 const int poly = UF_msa.find(x);
933 std::lock_guard lock(maxDelaunayLocks[poly]);
934 msa[poly] = max(msa[poly], msa[x]);
935 }
936
937 TTK_PSORT(nThreads_, urquhart.begin(), urquhart.end(),
938 [](const FiltratedEdge &a, const FiltratedEdge &b) {
939 return a.d < b.d;
940 });
941 UnionFind UF_p(N_p);
942 ph[0].reserve(N_p - 1);
943 for(FiltratedEdge const &e : urquhart) {
944 if(UF_p.find(e.e.first)
945 != UF_p.find(e.e.second)) { // we know e is a EMST edge
946 UF_p.merge(e.e.first, e.e.second);
947 ph[0].emplace_back(FiltratedSimplex{{-1}, 0.},
948 FiltratedSimplex{{e.e.first, e.e.second}, e.d});
949 } else
950 critical1.emplace_back(e);
951 }
952 }
953
954 void compute1PH(MultidimensionalDiagram &ph) {
955 omp_set_num_threads(nThreads_);
956 const ConnectivityHashMap sequential_msa_edges = std::move(msa_edges);
957
958 const unsigned N_msa = msa.size();
959 std::vector<int> polys;
960 for(unsigned x = 0; x < N_msa; ++x) {
961 if(UF_msa.isRoot(x) && msa[x].d < inf)
962 polys.push_back(x);
963 }
964
965 TTK_PSORT(
966 nThreads_, polys.begin(), polys.end(),
967 [&](const int x1, const int x2) { return msa[x1].d < msa[x2].d; });
968
969 std::vector<int> criticalIndices(critical1.size());
970 std::iota(criticalIndices.begin(), criticalIndices.end(), 0);
971 TTK_PSORT(nThreads_, criticalIndices.begin(), criticalIndices.end(),
972 [&](const int e1_id, const int e2_id) {
973 return critical1[e1_id].d < critical1[e2_id].d;
974 });
975 std::vector<int> criticalOrder(critical1.size());
976 for(unsigned i = 0; i < criticalIndices.size(); ++i)
977 criticalOrder[criticalIndices[i]] = i;
978
979 std::vector<std::vector<int>> poly_to_crit(N_msa);
980 for(const int poly : polys)
981 poly_to_crit[poly].reserve(3);
982 std::vector<std::mutex> poly_mutex(N_msa);
983 std::vector<std::mutex> crit_mutex(critical1.size());
984
985#pragma omp parallel for
986 for(unsigned i = 0; i < critical1.size(); ++i) {
987 const FiltratedEdge &e = critical1[i];
988 for(const int poly : sequential_msa_edges.find(e.e)->second.first) {
989 const int uf_poly = UF_msa.find(poly);
990 if(msa[uf_poly].d < inf) {
991 auto &neighbors = poly_to_crit[uf_poly];
992 std::lock_guard lock(poly_mutex[uf_poly]);
993 auto it
994 = std::find(neighbors.begin(), neighbors.end(), criticalOrder[i]);
995 if(it == neighbors.end())
996 neighbors.push_back(criticalOrder[i]);
997 else
998 neighbors.erase(it);
999 }
1000 }
1001 }
1002
1003 std::vector<int> partner(critical1.size(), -1);
1004
1005 auto eliminateBoundary = [&](const int poly) -> int {
1006 std::lock_guard lock(poly_mutex[poly]);
1007 HashSet<int> boundary(poly_to_crit[poly].begin(),
1008 poly_to_crit[poly].end(),
1009 poly_to_crit[poly].size());
1010 while(true) {
1011 const int youngest_id
1012 = *std::max_element(boundary.begin(), boundary.end());
1013 std::lock_guard lock_crit(crit_mutex[youngest_id]);
1014 if(partner[youngest_id] == -1) {
1015 partner[youngest_id] = poly;
1016 poly_to_crit[poly].assign(boundary.begin(), boundary.end());
1017 return -1;
1018 }
1019 std::lock_guard lock_partner(poly_mutex[partner[youngest_id]]);
1020 if(msa[partner[youngest_id]].d > msa[poly].d) {
1021 const int tmp = partner[youngest_id];
1022 partner[youngest_id] = poly;
1023 poly_to_crit[poly].assign(boundary.begin(), boundary.end());
1024 return tmp;
1025 }
1026 for(const int crit : poly_to_crit[partner[youngest_id]]) {
1027 const auto it = boundary.find(crit);
1028 if(it == boundary.end())
1029 boundary.insert(crit);
1030 else
1031 boundary.erase(it);
1032 }
1033 }
1034 };
1035
1036#pragma omp parallel for
1037 for(const int poly : polys) {
1038 int tmp_p = poly;
1039 while(tmp_p >= 0)
1040 tmp_p = eliminateBoundary(tmp_p);
1041 }
1042
1043 for(unsigned i = 0; i < critical1.size(); ++i) {
1044 const int poly = partner[i];
1045 const FiltratedEdge &e = critical1[criticalIndices[i]];
1046 const FiltratedFacet &death = msa[poly];
1047 if(e.d < death.d)
1048 ph[1].emplace_back(
1049 FiltratedSimplex{{e.e.first, e.e.second}, e.d},
1050 FiltratedSimplex{{death.f[0], death.f[1], death.f[2]}, death.d});
1051 }
1052 }
1053
1054 void compute1PH(MultidimensionalDiagram &ph,
1055 std::vector<Generator1> &generators1) {
1056 omp_set_num_threads(nThreads_);
1057 ConnectivityHashMap sequential_msa_edges = std::move(msa_edges);
1058
1059 const unsigned N_msa = msa.size();
1060 std::vector<int> polys;
1061 for(unsigned x = 0; x < N_msa; ++x) {
1062 if(UF_msa.isRoot(x) && msa[x].d < inf)
1063 polys.push_back(x);
1064 }
1065
1066 TTK_PSORT(
1067 nThreads_, polys.begin(), polys.end(),
1068 [&](const int x1, const int x2) { return msa[x1].d < msa[x2].d; });
1069
1070 std::vector<int> criticalIndices(critical1.size());
1071 std::iota(criticalIndices.begin(), criticalIndices.end(), 0);
1072 TTK_PSORT(nThreads_, criticalIndices.begin(), criticalIndices.end(),
1073 [&](const int e1_id, const int e2_id) {
1074 return critical1[e1_id].d < critical1[e2_id].d;
1075 });
1076 std::vector<int> criticalOrder(critical1.size());
1077 for(unsigned i = 0; i < criticalIndices.size(); ++i)
1078 criticalOrder[criticalIndices[i]] = i;
1079
1080 std::vector<std::vector<int>> poly_to_crit(N_msa);
1081 for(const int poly : polys)
1082 poly_to_crit[poly].reserve(3);
1083 std::vector<std::mutex> poly_mutex(N_msa);
1084 std::vector<std::mutex> crit_mutex(critical1.size());
1085
1086#pragma omp parallel for
1087 for(unsigned i = 0; i < critical1.size(); ++i) {
1088 const FiltratedEdge &e = critical1[i];
1089 for(const int poly : sequential_msa_edges.find(e.e)->second.first) {
1090 const int uf_poly = UF_msa.find(poly);
1091 if(msa[uf_poly].d < inf) {
1092 auto &neighbors = poly_to_crit[uf_poly];
1093 std::lock_guard lock(poly_mutex[uf_poly]);
1094 auto it
1095 = std::find(neighbors.begin(), neighbors.end(), criticalOrder[i]);
1096 if(it == neighbors.end())
1097 neighbors.push_back(criticalOrder[i]);
1098 else
1099 neighbors.erase(it);
1100 }
1101 }
1102 }
1103
1104 std::vector<std::vector<Edge>> elementary_generators(N_msa);
1105 auto action = [&](const FiltratedEdge &e) {
1106 auto adj_f = sequential_msa_edges[e.e].first;
1107 for(int &f : adj_f)
1108 f = UF_msa.find(f);
1109 std::sort(adj_f.begin(), adj_f.end());
1110 const auto last = std::unique(adj_f.begin(), adj_f.end());
1111 for(auto it = adj_f.begin(); it != last; ++it) {
1112 if(msa[UF_msa.find(*it)].d != inf)
1113 elementary_generators[UF_msa.find(*it)].push_back(e.e);
1114 }
1115 };
1116 for(auto const &e : urquhart)
1117 action(e);
1118 for(auto const &e : critical1) {
1119 if(sequential_msa_edges[e.e]
1120 .second) // if critical but not urquhart -> non manifold junctions
1121 action(e);
1122 }
1123
1124 std::vector<int> partner(critical1.size(), -1);
1125
1126 auto eliminateBoundary = [&](const int poly) -> int {
1127 std::lock_guard lock(poly_mutex[poly]);
1128 HashSet<int> boundary(poly_to_crit[poly].begin(),
1129 poly_to_crit[poly].end(),
1130 poly_to_crit[poly].size());
1131 std::vector<Edge> &generator = elementary_generators[poly];
1132 while(true) {
1133 const int youngest_id
1134 = *std::max_element(boundary.begin(), boundary.end());
1135 std::lock_guard lock_crit(crit_mutex[youngest_id]);
1136 if(partner[youngest_id] == -1) {
1137 partner[youngest_id] = poly;
1138 poly_to_crit[poly].assign(boundary.begin(), boundary.end());
1139 return -1;
1140 }
1141 std::lock_guard lock_partner(poly_mutex[partner[youngest_id]]);
1142 if(msa[partner[youngest_id]].d > msa[poly].d) {
1143 const int tmp = partner[youngest_id];
1144 partner[youngest_id] = poly;
1145 poly_to_crit[poly].assign(boundary.begin(), boundary.end());
1146 return tmp;
1147 }
1148 for(const int crit : poly_to_crit[partner[youngest_id]]) {
1149 const auto it = boundary.find(crit);
1150 if(it == boundary.end())
1151 boundary.insert(crit);
1152 else
1153 boundary.erase(it);
1154 }
1155 for(Edge const &e : elementary_generators[partner[youngest_id]]) {
1156 auto it = std::find(generator.begin(), generator.end(), e);
1157 if(it == generator.end())
1158 generator.push_back(e);
1159 else
1160 generator.erase(it);
1161 }
1162 }
1163 };
1164
1165#pragma omp parallel for
1166 for(const int poly : polys) {
1167 int tmp_p = poly;
1168 while(tmp_p >= 0)
1169 tmp_p = eliminateBoundary(tmp_p);
1170 }
1171
1172 for(unsigned i = 0; i < critical1.size(); ++i) {
1173 const int poly = partner[i];
1174 const FiltratedEdge &e = critical1[criticalIndices[i]];
1175 const FiltratedFacet &death = msa[poly];
1176 const std::vector<Edge> &generator = elementary_generators[poly];
1177 if(e.d < death.d) {
1178 ph[1].emplace_back(
1179 FiltratedSimplex{{e.e.first, e.e.second}, e.d},
1180 FiltratedSimplex{{death.f[0], death.f[1], death.f[2]}, death.d});
1181 generators1.emplace_back(generator, std::make_pair(e.d, death.d));
1182 }
1183 }
1184 }
1185 };
1186#endif
1187
1188 inline void
1189 runDelaunayRipsPersistenceDiagram3(rpd::PointCloud const &points,
1190 MultidimensionalDiagram &diagram,
1191 int threads = 1) {
1192 PointCloud<3> p(points.size());
1193 for(unsigned i = 0; i < points.size(); ++i)
1194 p[i] = {points[i][0], points[i][1], points[i][2]};
1195 if(threads > 1) {
1196#ifdef TTK_GPH_PARALLEL
1197 DRPersistence3_p drpd(p, threads);
1198#else
1199 DRPersistence3 drpd(p);
1200#endif
1201 drpd.run(diagram);
1202 } else {
1203 DRPersistence3 drpd(p);
1204 drpd.run(diagram);
1205 }
1206 }
1207
1208 inline void
1209 runDelaunayRipsPersistenceDiagram3(rpd::PointCloud const &points,
1210 MultidimensionalDiagram &diagram,
1211 std::vector<Generator1> &generators1,
1212 std::vector<Generator2> &generators2,
1213 int threads = 1) {
1214 PointCloud<3> p(points.size());
1215 for(unsigned i = 0; i < points.size(); ++i)
1216 p[i] = {points[i][0], points[i][1], points[i][2]};
1217 if(threads > 1) {
1218#ifdef TTK_GPH_PARALLEL
1219 DRPersistence3_p drpd(p, threads);
1220#else
1221 DRPersistence3 drpd(p);
1222#endif
1223 drpd.run(diagram, generators1, generators2);
1224 } else {
1225 DRPersistence3 drpd(p);
1226 drpd.run(diagram, generators1, generators2);
1227 }
1228 }
1229
1230} // namespace ttk::gph
1231
1232#endif
#define TTK_PSORT(NTHREADS,...)
Parallel sort macro.
Definition OpenMP.h:46
boost::tuple< double, double > Point
Definition TopoMap.cpp:56
AtomicUF * find()
Definition FTMAtomicUF.h:38
AtomicUF * UF
Definition FTMTree_MT.h:39
std::vector< PointD< DIM > > PointCloud
Definition geoPHUtils.h:48
FiltratedEdge max(const FiltratedEdge &a, const FiltratedEdge &b)
std::pair< id_t, id_t > Edge
std::array< id_t, 3 > Facet
constexpr value_t inf
std::vector< std::vector< value_t > > PointCloud
std::vector< Diagram > MultidimensionalDiagram
std::pair< std::vector< Edge >, std::pair< value_t, value_t > > Generator1
std::pair< Simplex, value_t > FiltratedSimplex
std::pair< std::vector< Facet >, std::pair< value_t, value_t > > Generator2
T end(std::pair< T, T > &p)
Definition ripser.cpp:503
T begin(std::pair< T, T > &p)
Definition ripser.cpp:499