TTK
Loading...
Searching...
No Matches
geoPHd.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.h>
8#include <CGAL/Epick_d.h>
9#include <CGAL/Triangulation_full_cell.h>
10#include <CGAL/Triangulation_vertex.h>
11
12namespace ttk::gph {
13
14 using rpd::Diagram;
15 using rpd::inf;
17 using rpd::Simplex;
18 using rpd::UnionFind;
19
20 constexpr static unsigned DYN_DIM = 0;
21
22 template <unsigned D>
23 using DSimplex = std::
24 conditional_t<D == DYN_DIM, std::vector<id_t>, std::array<id_t, D + 1>>;
25
26 template <unsigned D>
27 using ValueArray = std::conditional_t<D == DYN_DIM,
28 std::vector<rpd::value_t>,
29 std::array<rpd::value_t, D>>;
30
31 template <unsigned D>
32 using ConnectivityHashMap
33 = HashMap<DSimplex<D>, std::vector<std::pair<int, int>>>;
34
35#ifdef TTK_CONCURRENT_HASHTABLE_AVAILABLE
36 template <unsigned D>
37 using ConcurrentConnectivityHashMap
38 = ConcurrentHashMap<DSimplex<D>, std::vector<std::pair<int, int>>>;
39#endif
40
41 template <unsigned D>
42 struct FiltratedDSimplex {
43 DSimplex<D> s;
44 double d;
45 double a;
46 };
47
48 template <unsigned D>
49 bool operator<(FiltratedDSimplex<D> const &s1,
50 FiltratedDSimplex<D> const &s2) {
51 return std::make_pair(s1.d, s1.a) < std::make_pair(s2.d, s2.a);
52 }
53
54 template <unsigned D>
55 FiltratedDSimplex<D> max(FiltratedDSimplex<D> const &s1,
56 FiltratedDSimplex<D> const &s2) {
57 return s1 < s2 ? s2 : s1;
58 }
59
60 template <unsigned D>
61 using DSimplicialComplex = std::vector<FiltratedDSimplex<D>>;
62
63 template <unsigned DIM>
64 class DRPersistenceD {
65 using DimTag = std::conditional_t<DIM == DYN_DIM,
66 CGAL::Dynamic_dimension_tag,
67 CGAL::Dimension_tag<DIM>>;
68 using K = CGAL::Epick_d<DimTag>;
69 using Vb = CGAL::Triangulation_vertex<K, id_t>;
70 using FCb = CGAL::Triangulation_full_cell<K, id_t>;
71 using Tds = CGAL::Triangulation_data_structure<DimTag, Vb, FCb>;
72 using Delaunay = CGAL::Delaunay_triangulation<K, Tds>;
73 using Point = typename Delaunay::Point;
74 using Facet = typename Delaunay::Facet;
75 using VertexHandle = typename Delaunay::Vertex_handle;
76 using CellHandle = typename Delaunay::Full_cell_handle;
77
78 static constexpr unsigned minus1(const unsigned D) {
79 return D == DYN_DIM ? DYN_DIM : D - 1;
80 }
81
82 static constexpr unsigned plus1(const unsigned D) {
83 return D == DYN_DIM ? DYN_DIM : D + 1;
84 }
85
86 struct FiltratedQuadFacet {
87 DSimplex<minus1(DIM)> s;
88 double d;
89 double a;
90 int c1;
91 int c2;
92 };
93
94 public:
95 explicit DRPersistenceD(PointCloud<DIM> &points, unsigned nThreads = 1);
96 void run(MultidimensionalDiagram &ph);
97
98 private:
99 const unsigned N_p;
100 unsigned N_c{};
101 Delaunay del_;
102 PointCloud<DIM> &points_;
103 const int nThreads_{1};
104
105 [[nodiscard]] double squaredDistance(const unsigned i1,
106 const unsigned i2) const {
107 return std::inner_product(
108 points_[i1].begin(), points_[i1].end(), points_[i2].begin(), 0.,
109 std::plus(),
110 [](const double u, const double v) { return (u - v) * (u - v); });
111 }
112
113 template <unsigned D>
114 std::pair<double, double>
115 squaredPerturbedDiameter(DSimplex<D> const &s) const {
116 double diam = 0.;
117 double a = 0.;
118 for(unsigned p1 = 0; p1 < s.size(); ++p1) {
119 for(unsigned p2 = p1 + 1; p2 < s.size(); ++p2) {
120 const double dist = squaredDistance(s[p1], s[p2]);
121 diam = std::max(diam, dist);
122 a += dist;
123 }
124 }
125 return {diam, a};
126 }
127
128 template <unsigned D>
129 void getLengths(DSimplex<D> const &s,
130 ValueArray<D *(D + 1) / 2> &lengths) const {
131 if constexpr(D == DYN_DIM)
132 lengths.resize((s.size() - 1) * s.size() / 2);
133 else
134 lengths.fill(0.); // to mute an uninitialization warning
135 int i = 0;
136 for(unsigned p1 = 0; p1 < s.size(); ++p1) {
137 for(unsigned p2 = p1 + 1; p2 < s.size(); ++p2)
138 lengths[i++] = squaredDistance(s[p1], s[p2]);
139 }
140 }
141
142 template <unsigned D>
143 bool checkLinkUrquhart(DSimplex<D> const &s,
144 ValueArray<D *(D + 1) / 2> const &lengths,
145 std::pair<double, double> diam,
146 id_t linkId) const {
147 ValueArray<plus1(D)> linkLengths;
148 if constexpr(D == DYN_DIM)
149 linkLengths.resize(s.size());
150 for(unsigned i = 0; i < s.size(); ++i)
151 linkLengths[i] = squaredDistance(s[i], linkId);
152
153 for(unsigned i = 0; i < s.size(); ++i) {
154 double d = 0.;
155 double a = 0.;
156 int k = 0;
157 for(unsigned p1 = 0; p1 < s.size(); ++p1) {
158 for(unsigned p2 = p1 + 1; p2 < s.size(); ++p2) {
159 double dist;
160 if(p1 == i)
161 dist = linkLengths[p2];
162 else if(p2 == i)
163 dist = linkLengths[p1];
164 else
165 dist = lengths[k];
166
167 d = std::max(d, dist);
168 a += dist;
169 ++k;
170 }
171 }
172 if(std::make_pair(d, a) > diam)
173 return false;
174 }
175
176 return true;
177 }
178
179 void computeDelaunay();
180
181 void computeDPH(Diagram &ph, DSimplicialComplex<minus1(DIM)> &MSA);
182
183 void computeDPH_p(Diagram &ph, DSimplicialComplex<minus1(DIM)> &MSA);
184
185 template <unsigned D>
186 void computeNextPH(Diagram &ph,
187 DSimplicialComplex<D> &MSA,
188 DSimplicialComplex<minus1(D)> &nextMSA) const;
189
190 template <unsigned D>
191 void computeNextPH_p(Diagram &ph,
192 DSimplicialComplex<D> &MSA,
193 DSimplicialComplex<minus1(D)> &nextMSA) const;
194
195 template <unsigned D>
196 void recurse(MultidimensionalDiagram &ph, DSimplicialComplex<D> &MSA) const;
197 };
198
199 template <unsigned DIM>
200 DRPersistenceD<DIM>::DRPersistenceD(PointCloud<DIM> &points,
201 const unsigned nThreads)
202 : N_p(points.size()), del_(DIM), points_(points), nThreads_(nThreads) {
203 }
204
205 template <>
206 inline DRPersistenceD<DYN_DIM>::DRPersistenceD(PointCloud<DYN_DIM> &points,
207 const unsigned nThreads)
208 : N_p(points.size()), del_(points[0].size()), points_(points),
209 nThreads_(nThreads) {
210 }
211
212 template <unsigned DIM>
213 void DRPersistenceD<DIM>::run(MultidimensionalDiagram &ph) {
214 ph = MultidimensionalDiagram(DIM);
215
216 computeDelaunay();
217
218 DSimplicialComplex<DIM - 1> MSA;
219 if(nThreads_ == 1)
220 computeDPH(ph[DIM - 1], MSA);
221 else
222 computeDPH_p(ph[DIM - 1], MSA);
223
224 recurse(ph, MSA);
225 }
226
227 template <>
228 inline void DRPersistenceD<DYN_DIM>::run(MultidimensionalDiagram &ph) {
229 const unsigned DIM = points_[0].size();
230 ph = MultidimensionalDiagram(DIM);
231
232 computeDelaunay();
233
234 DSimplicialComplex<DYN_DIM> MSA;
235 if(nThreads_ == 1)
236 computeDPH(ph[DIM - 1], MSA);
237 else
238 computeDPH_p(ph[DIM - 1], MSA);
239
240 unsigned D = DIM - 1;
241 while(D > 1) {
242 DSimplicialComplex<DYN_DIM> nextMSA;
243 if(nThreads_ == 1)
244 computeNextPH<DYN_DIM>(ph[D - 1], MSA, nextMSA);
245 else
246 computeNextPH_p<DYN_DIM>(ph[D - 1], MSA, nextMSA);
247 MSA = std::move(nextMSA);
248 D--;
249 }
250 for(FiltratedDSimplex<DYN_DIM> const &e : MSA)
251 ph[0].emplace_back(
252 FiltratedSimplex{{-1}, 0.}, FiltratedSimplex{e.s, sqrt(e.d)});
253 }
254
255 template <unsigned DIM>
256 template <unsigned D>
257 void DRPersistenceD<DIM>::recurse(MultidimensionalDiagram &ph,
258 DSimplicialComplex<D> &MSA) const {
259 if constexpr(D >= 2) {
260 DSimplicialComplex<D - 1> nextMSA;
261 if(nThreads_ == 1)
262 computeNextPH(ph[D - 1], MSA, nextMSA);
263 else
264 computeNextPH_p(ph[D - 1], MSA, nextMSA);
265 recurse(ph, nextMSA);
266 } else if constexpr(D == 1) { // this is the 0-dimensional homology
267 for(FiltratedDSimplex<1> const &e : MSA)
268 ph[0].emplace_back(FiltratedSimplex{{-1}, 0.},
269 FiltratedSimplex{{e.s[0], e.s[1]}, sqrt(e.d)});
270 }
271 }
272
273 template <unsigned DIM>
274 void DRPersistenceD<DIM>::computeDelaunay() {
275 del_.insert(points_.begin(), points_.end());
276
277 // index all vertices
278 int p = 0;
279 for(auto p_it = del_.finite_vertices_begin();
280 p_it != del_.finite_vertices_end(); ++p_it) {
281 points_[p] = p_it->point();
282 p_it->data() = p++;
283 }
284
285 // index all cells (including infinite ones)
286 int k = 0;
287 for(auto c_it = del_.full_cells_begin(); c_it != del_.full_cells_end();
288 ++c_it)
289 c_it->data() = k++;
290 N_c = k;
291 }
292
293 template <unsigned DIM>
294 void DRPersistenceD<DIM>::computeDPH(Diagram &ph,
295 DSimplicialComplex<minus1(DIM)> &MSA) {
296 DSimplicialComplex<minus1(DIM)> maxDelaunay(N_c);
297 UnionFind UF(N_c);
298 std::vector<FiltratedQuadFacet> hyperUrquhart;
299
300 /* computing urquhart hypergraph (codimension 1) */
301 for(auto f_it = del_.facets_begin(); f_it != del_.facets_end(); ++f_it) {
302
303 const Facet f = *f_it;
304 const CellHandle c = f.full_cell();
305 const CellHandle c_mirror = c->neighbor(f.index_of_covertex());
306
307 if(del_.is_infinite(f))
308 UF.merge(c->data(), c_mirror->data());
309
310 else { // we need to determine whether f is "Urquhart"
311 const auto linkPoint1 = c->vertex(f.index_of_covertex());
312 const auto linkPoint2
313 = c_mirror->vertex(c->mirror_index(f.index_of_covertex()));
314
315 DSimplex<minus1(DIM)> facet;
316 if constexpr(DIM == DYN_DIM)
317 facet.resize(del_.current_dimension());
318 for(unsigned i = 0; i < facet.size(); ++i)
319 facet[i]
320 = c->vertex((f.index_of_covertex() + i + 1) % (facet.size() + 1))
321 ->data();
322 std::sort(facet.begin(), facet.end());
323 const auto diam = squaredPerturbedDiameter<minus1(DIM)>(facet);
324
325 bool is_urquhart = true;
326 for(auto linkPoint : {linkPoint1, linkPoint2}) {
327 if(!del_.is_infinite(linkPoint)) {
328 const id_t k = linkPoint->data();
329 bool largest = true;
330 for(unsigned i = 0; i < facet.size(); ++i) {
331 DSimplex<minus1(DIM)> neighbor = facet;
332 neighbor[i] = k;
333 if(squaredPerturbedDiameter<minus1(DIM)>(neighbor) > diam) {
334 largest = false;
335 break;
336 }
337 }
338 if(largest) {
339 is_urquhart = false;
340 break;
341 }
342 }
343 }
344
345 if(is_urquhart)
346 hyperUrquhart.push_back(
347 {facet, diam.first, diam.second, c->data(), c_mirror->data()});
348 else {
349 const int poly1 = UF.find(c->data());
350 const int poly2 = UF.find(c_mirror->data());
351 maxDelaunay[UF.mergeRet(poly1, poly2)] = max(
352 FiltratedDSimplex<minus1(DIM)>{facet, diam.first, diam.second},
353 max(maxDelaunay[poly1], maxDelaunay[poly2]));
354 }
355
356 // detect infinite polyhedrons (going beyond convex hull)
357 if(del_.is_infinite(c))
358 maxDelaunay[UF.find(c->data())].d = inf;
359 else if(del_.is_infinite(c_mirror))
360 maxDelaunay[UF.find(c_mirror->data())].d = inf;
361 }
362 }
363
364 std::sort(hyperUrquhart.begin(), hyperUrquhart.end(),
365 [](const FiltratedQuadFacet &f1, const FiltratedQuadFacet &f2) {
366 if(f1.d == f2.d)
367 return f1.a > f2.a;
368 return f1.d > f2.d;
369 });
370
371 /* reverse-delete algorithm to determine MSA */
372 std::vector<int> latest(maxDelaunay.size());
373 std::iota(latest.begin(), latest.end(), 0);
374
375 for(FiltratedQuadFacet const &f :
376 hyperUrquhart) { // sorted by decreasing order
377 const int v1 = UF.find(f.c1);
378 const int v2 = UF.find(f.c2);
379 if(v1 != v2) { // two distinct codimension-1 cavities: merge them by
380 // deleting the facet
381 UF.merge(v1, v2);
382
383 const int latest1 = latest[v1];
384 const int latest2 = latest[v2];
385 const FiltratedDSimplex<minus1(DIM)> &death1 = maxDelaunay[latest1];
386 const FiltratedDSimplex<minus1(DIM)> &death2 = maxDelaunay[latest2];
387
388 if(death1.d < death2.d) {
389 if(f.d < death1.d)
390 ph.emplace_back(
391 FiltratedSimplex{Simplex(f.s.begin(), f.s.end()), sqrt(f.d)},
393 Simplex(death1.s.begin(), death1.s.end()), sqrt(death1.d)});
394 latest[UF.find(v1)] = latest2;
395 } else if(death2.d < death1.d) {
396 if(f.d < death2.d)
397 ph.emplace_back(
398 FiltratedSimplex{Simplex(f.s.begin(), f.s.end()), sqrt(f.d)},
400 Simplex(death2.s.begin(), death2.s.end()), sqrt(death2.d)});
401 latest[UF.find(v1)] = latest1;
402 }
403 } else // this is a facet from the minimal spanning acycle
404 MSA.push_back({f.s, f.d, f.a});
405 }
406 }
407
408 template <unsigned DIM>
409 void DRPersistenceD<DIM>::computeDPH_p(Diagram &ph,
410 DSimplicialComplex<minus1(DIM)> &MSA) {
411#ifndef TTK_GPH_PARALLEL
412 computeDPH(ph, MSA);
413#else
414 omp_set_num_threads(nThreads_);
415
416 DisjointSets UF(N_c);
417 tbb::concurrent_vector<FiltratedQuadFacet> hyperUrquhart;
418
419 std::vector<FiltratedDSimplex<DIM>> cells(N_c);
420 for(auto c_it = del_.finite_full_cells_begin();
421 c_it != del_.finite_full_cells_end(); ++c_it) {
422 FiltratedDSimplex<DIM> cell;
423 if constexpr(DIM == DYN_DIM)
424 cell.s.resize(del_.current_dimension() + 1);
425 cell.d = -1.;
426 for(unsigned i = 0; i < cell.s.size(); ++i)
427 cell.s[i] = c_it->vertex(i)->data();
428 cells[c_it->data()] = cell;
429 }
430
431#pragma omp parallel for
432 for(unsigned i = 0; i < cells.size(); ++i) {
433 if(cells[i].d == -1.) {
434 auto [d, a] = squaredPerturbedDiameter<DIM>(cells[i].s);
435 cells[i].d = d;
436 cells[i].a = a;
437 }
438 }
439
440 std::vector<std::tuple<DSimplex<minus1(DIM)>, int, int, int, int>> facets;
441 for(auto f_it = del_.facets_begin(); f_it != del_.facets_end(); ++f_it) {
442 const Facet f = *f_it;
443 const CellHandle c = f.full_cell();
444 const CellHandle c_mirror = c->neighbor(f.index_of_covertex());
445 if(del_.is_infinite(f))
446 UF.unite(c->data(), c_mirror->data());
447 else {
448 DSimplex<minus1(DIM)> facet;
449 if constexpr(DIM == DYN_DIM)
450 facet.resize(del_.current_dimension());
451 for(unsigned i = 0; i < facet.size(); ++i)
452 facet[i]
453 = c->vertex((f.index_of_covertex() + i + 1) % (facet.size() + 1))
454 ->data();
455
456 const auto linkPoint1 = c->vertex(f.index_of_covertex());
457 const auto linkPoint2
458 = c_mirror->vertex(c->mirror_index(f.index_of_covertex()));
459 const int n1 = del_.is_infinite(linkPoint1) ? -1 : linkPoint1->data();
460 const int n2 = del_.is_infinite(linkPoint2) ? -1 : linkPoint2->data();
461 const int c1 = c->data();
462 const int c2 = c_mirror->data();
463 facets.emplace_back(facet, c1, n1, c2, n2);
464 }
465 }
466
467#pragma omp parallel for
468 for(auto &[facet, c1, n1, c2, n2] : facets) {
469 // first determine whether s is Urquhart
470 std::sort(facet.begin(), facet.end());
471 const auto diam = squaredPerturbedDiameter<minus1(DIM)>(facet);
472
473 bool is_urquhart = true;
474 for(const id_t k : {n1, n2}) {
475 if(k != -1) {
476 bool largest = true;
477 for(unsigned i = 0; i < facet.size(); ++i) {
478 DSimplex<minus1(DIM)> neighbor = facet;
479 neighbor[i] = k;
480 if(squaredPerturbedDiameter<minus1(DIM)>(neighbor) > diam) {
481 largest = false;
482 break;
483 }
484 }
485 if(largest) {
486 is_urquhart = false;
487 break;
488 }
489 }
490 }
491
492 if(is_urquhart)
493 hyperUrquhart.push_back({facet, diam.first, diam.second, c1, c2});
494 else
495 UF.unite(c1, c2);
496
497 if(n1 == -1)
498 cells[c1].d = inf;
499 else if(n2 == -1)
500 cells[c2].d = inf;
501 }
502
503 std::vector<std::mutex> maxDelaunayLocks(N_c);
504#pragma omp parallel for
505 for(unsigned x = 0; x < N_c; ++x) {
506 const int poly = UF.find(x);
507 std::lock_guard lock(maxDelaunayLocks[poly]);
508 cells[poly] = max(cells[poly], cells[x]);
509 }
510
511 TTK_PSORT(nThreads_, hyperUrquhart.begin(), hyperUrquhart.end(),
512 [](const FiltratedQuadFacet &f1, const FiltratedQuadFacet &f2) {
513 if(f1.d == f2.d)
514 return f1.a > f2.a;
515 return f1.d > f2.d;
516 });
517
518 /* reverse-delete algorithm to determine MSA */
519 std::vector<int> latest(N_c);
520 std::iota(latest.begin(), latest.end(), 0);
521
522 for(FiltratedQuadFacet const &f :
523 hyperUrquhart) { // sorted by decreasing order
524 const int v1 = UF.find(f.c1);
525 const int v2 = UF.find(f.c2);
526 if(v1 != v2) { // two distinct codimension-1 cavities: merge them by
527 // deleting the facet
528 UF.unite(v1, v2);
529
530 const int latest1 = latest[v1];
531 const int latest2 = latest[v2];
532 const FiltratedDSimplex<DIM> &death1 = cells[latest1];
533 const FiltratedDSimplex<DIM> &death2 = cells[latest2];
534
535 if(death1.d < death2.d) {
536 if(f.d < death1.d)
537 ph.emplace_back(
538 FiltratedSimplex{Simplex(f.s.begin(), f.s.end()), sqrt(f.d)},
540 Simplex(death1.s.begin(), death1.s.end()), sqrt(death1.d)});
541 latest[UF.find(v1)] = latest2;
542 } else if(death2.d < death1.d) {
543 if(f.d < death2.d)
544 ph.emplace_back(
545 FiltratedSimplex{Simplex(f.s.begin(), f.s.end()), sqrt(f.d)},
547 Simplex(death2.s.begin(), death2.s.end()), sqrt(death2.d)});
548 latest[UF.find(v1)] = latest1;
549 }
550 } else // this is a facet from the minimal spanning acycle
551 MSA.push_back({f.s, f.d, f.a});
552 }
553#endif
554 }
555
556 template <unsigned DIM>
557 template <unsigned D>
558 void DRPersistenceD<DIM>::computeNextPH(
559 Diagram &ph,
560 DSimplicialComplex<D> &MSA,
561 DSimplicialComplex<minus1(D)> &nextMSA) const {
562 const unsigned N_msa = MSA.size();
563
564 /* Connectivity */
565
566 ConnectivityHashMap<minus1(D)> msa_connectivity;
567 msa_connectivity.reserve(N_msa);
568 for(unsigned i = 0; i < N_msa; ++i) {
569 DSimplex<minus1(D)> face;
570 if constexpr(D == DYN_DIM)
571 face.resize(MSA[0].s.size() - 1);
572 for(unsigned k = 0; k < MSA[0].s.size(); ++k) {
573 for(unsigned j = 0; j < MSA[0].s.size() - 1; ++j)
574 face[j] = MSA[i].s[j + (j >= k)];
575 msa_connectivity[face].reserve(4); // guess
576 msa_connectivity[face].emplace_back(i, MSA[i].s[k]);
577 }
578 }
579
580 /* Urquhart-ness and Urquhart-polytopes */
581
582 std::vector<FiltratedDSimplex<minus1(D)>> critical;
583 UnionFind UF_msa(N_msa);
584
585 for(auto const &[s, neighbors] : msa_connectivity) {
586
587 // first determine whether s is Urquhart
588 bool is_urquhart = true;
589 ValueArray<D * minus1(D) / 2> lengths;
590 getLengths<minus1(D)>(s, lengths);
591 std::pair diam{0.0, 0.0};
592 for(double const &l : lengths)
593 diam = {std::max(diam.first, l), diam.second + l};
594
595 for(auto [coface_id, linkPoint_id] : neighbors) {
596 if(checkLinkUrquhart<minus1(D)>(s, lengths, diam, linkPoint_id)) {
597 is_urquhart = false;
598 break;
599 }
600 }
601
602 // now maintain the polytope structure
603 if(is_urquhart)
604 critical.push_back({s, diam.first, diam.second});
605 else {
606 if(neighbors.size() == 1) // set infinite polytope
607 MSA[neighbors[0].first].d = inf;
608 else if(neighbors.size() == 2)
609 UF_msa.merge(neighbors[0].first, neighbors[1].first);
610 else
611 critical.push_back({s, diam.first, diam.second});
612 }
613 }
614
615 for(unsigned x = 0; x < N_msa; ++x) {
616 const int poly = UF_msa.find(x);
617 MSA[poly] = max(MSA[poly], MSA[x]);
618 }
619
620 /* Graph critical -- polytope */
621
622 std::vector<id_t> polytopes;
623 for(unsigned x = 0; x < N_msa; ++x) {
624 if(UF_msa.isRoot(x) && MSA[x].d < inf)
625 polytopes.emplace_back(x);
626 }
627
628 std::sort(polytopes.begin(), polytopes.end(),
629 [&](const int x1, const int x2) { return MSA[x1] < MSA[x2]; });
630
631 std::vector<int> criticalIndices(critical.size());
632 std::iota(criticalIndices.begin(), criticalIndices.end(), 0);
633 std::sort(criticalIndices.begin(), criticalIndices.end(),
634 [&](const int x1, const int x2) {
635 return critical[x1].d < critical[x2].d;
636 });
637 std::vector<int> criticalOrder(critical.size());
638 for(unsigned i = 0; i < criticalIndices.size(); ++i)
639 criticalOrder[criticalIndices[i]] = i;
640
641 std::vector<std::vector<int>> poly_to_crit(N_msa);
642 for(const int poly : polytopes)
643 poly_to_crit[poly].reserve(D + 1); // guess
644 for(unsigned i = 0; i < critical.size(); ++i) {
645 for(const auto &[poly, _] : msa_connectivity[critical[i].s]) {
646 if(MSA[UF_msa.find(poly)].d < inf) {
647 auto &neighbors = poly_to_crit[UF_msa.find(poly)];
648 auto it
649 = std::find(neighbors.begin(), neighbors.end(), criticalOrder[i]);
650 if(it == neighbors.end())
651 neighbors.push_back(criticalOrder[i]);
652 else
653 neighbors.erase(it);
654 }
655 }
656 }
657
658 /* PairCells */
659
660 std::vector<int> partner(critical.size(), -1);
661 for(const int poly : polytopes) {
662 HashSet<int> boundary(poly_to_crit[poly].begin(),
663 poly_to_crit[poly].end(),
664 poly_to_crit[poly].size());
665 while(true) {
666 const int youngest_id
667 = *std::max_element(boundary.begin(), boundary.end());
668 if(partner[youngest_id] == -1) {
669 partner[youngest_id] = poly;
670 const FiltratedDSimplex<minus1(D)> &c
671 = critical[criticalIndices[youngest_id]];
672 const FiltratedDSimplex<D> &death = MSA[poly];
673 if(c.d < death.d)
674 ph.emplace_back(
675 FiltratedSimplex{Simplex(c.s.begin(), c.s.end()), sqrt(c.d)},
677 Simplex(death.s.begin(), death.s.end()), sqrt(death.d)});
678 break;
679 } else {
680 for(const int crit : poly_to_crit[partner[youngest_id]]) {
681 const auto it = boundary.find(crit);
682 if(it == boundary.end())
683 boundary.insert(crit);
684 else
685 boundary.erase(it);
686 }
687 }
688 }
689 poly_to_crit[poly].assign(boundary.begin(), boundary.end());
690 }
691
692 /* Next MSA */
693 for(unsigned i = 0; i < critical.size(); ++i) {
694 if(partner[i] == -1) // unassigned -> go in next MSA
695 nextMSA.emplace_back(critical[criticalIndices[i]]);
696 }
697 }
698
699 template <unsigned DIM>
700 template <unsigned D>
701 void DRPersistenceD<DIM>::computeNextPH_p(
702 Diagram &ph,
703 DSimplicialComplex<D> &MSA,
704 DSimplicialComplex<minus1(D)> &nextMSA) const {
705#ifndef TTK_GPH_PARALLEL
706 computeNextPH(ph, MSA, nextMSA);
707#else
708 tbb::global_control gc(
709 tbb::global_control::max_allowed_parallelism, nThreads_);
710 omp_set_num_threads(nThreads_);
711 const unsigned N_msa = MSA.size();
712
713 /* Connectivity */
714
715 ConcurrentConnectivityHashMap<minus1(D)> concurrent_msa_connectivity;
716 concurrent_msa_connectivity.reserve(N_msa);
717
718#pragma omp parallel for
719 for(unsigned i = 0; i < N_msa; ++i) {
720 DSimplex<minus1(D)> face;
721 if constexpr(D == DYN_DIM)
722 face.resize(MSA[0].s.size() - 1);
723 for(unsigned k = 0; k < MSA[0].s.size(); ++k) {
724 for(unsigned j = 0; j < MSA[0].s.size() - 1; ++j)
725 face[j] = MSA[i].s[j + (j >= k)];
726 concurrent_msa_connectivity.emplace_or_visit(
727 face,
728 std::vector<std::pair<int, int>>{std::make_pair(i, MSA[i].s[k])},
729 [&](auto &x) { x.second.emplace_back(i, MSA[i].s[k]); });
730 }
731 }
732
733 /* Urquhart-ness and Urquhart-polytopes */
734
735 tbb::concurrent_vector<FiltratedDSimplex<minus1(D)>> critical;
736 DisjointSets UF_msa(N_msa);
737
738 concurrent_msa_connectivity.cvisit_all(
739#ifdef __cpp_lib_execution
740 std::execution::par,
741#endif
742 [&](const auto &x) {
743 const auto &[s, neighbors] = x;
744
745 // first determine whether s is Urquhart
746 bool is_urquhart = true;
747 ValueArray<D * minus1(D) / 2> lengths;
748 getLengths<minus1(D)>(s, lengths);
749 std::pair diam{0.0, 0.0};
750 for(double const &l : lengths)
751 diam = {std::max(diam.first, l), diam.second + l};
752
753 for(auto [coface_id, linkPoint_id] : neighbors) {
754 if(checkLinkUrquhart<minus1(D)>(s, lengths, diam, linkPoint_id)) {
755 is_urquhart = false;
756 break;
757 }
758 }
759
760 // now maintain the polytope structure
761 if(is_urquhart)
762 critical.push_back({s, diam.first, diam.second});
763 else {
764 if(neighbors.size() == 1) // set infinite polytope
765 MSA[neighbors[0].first].d = inf;
766 else if(neighbors.size() == 2)
767 UF_msa.unite(neighbors[0].first, neighbors[1].first);
768 else
769 critical.push_back({s, diam.first, diam.second});
770 }
771 });
772
773 std::vector<std::mutex> maxDelaunayLocks(N_msa);
774#pragma omp parallel for
775 for(unsigned x = 0; x < N_msa; ++x) {
776 const int poly = UF_msa.find(x);
777 std::lock_guard lock(maxDelaunayLocks[poly]);
778 MSA[poly] = max(MSA[poly], MSA[x]);
779 }
780
781 const ConnectivityHashMap<minus1(D)> msa_connectivity
782 = std::move(concurrent_msa_connectivity);
783
784 /* Graph critical -- polytope */
785
786 std::vector<id_t> polytopes;
787 for(unsigned x = 0; x < N_msa; ++x) {
788 if(UF_msa.isRoot(x) && MSA[x].d < inf)
789 polytopes.emplace_back(x);
790 }
791
792 TTK_PSORT(nThreads_, polytopes.begin(), polytopes.end(),
793 [&](const int x1, const int x2) { return MSA[x1] < MSA[x2]; });
794
795 std::vector<int> criticalIndices(critical.size());
796 std::iota(criticalIndices.begin(), criticalIndices.end(), 0);
797 TTK_PSORT(nThreads_, criticalIndices.begin(), criticalIndices.end(),
798 [&](const int x1, const int x2) {
799 return critical[x1].d < critical[x2].d;
800 });
801 std::vector<int> criticalOrder(critical.size());
802 for(unsigned i = 0; i < criticalIndices.size(); ++i)
803 criticalOrder[criticalIndices[i]] = i;
804
805 std::vector<std::vector<int>> poly_to_crit(N_msa);
806 std::vector<std::mutex> poly_mutex(N_msa);
807 std::vector<std::mutex> crit_mutex(critical.size());
808
809#pragma omp parallel for
810 for(unsigned i = 0; i < critical.size(); ++i) {
811 for(const auto &[poly, _] :
812 msa_connectivity.find(critical[i].s)->second) {
813 const int uf_poly = UF_msa.find(poly);
814 if(MSA[uf_poly].d < inf) {
815 auto &neighbors = poly_to_crit[uf_poly];
816 std::lock_guard lock(poly_mutex[uf_poly]);
817 auto it
818 = std::find(neighbors.begin(), neighbors.end(), criticalOrder[i]);
819 if(it == neighbors.end())
820 neighbors.push_back(criticalOrder[i]);
821 else
822 neighbors.erase(it);
823 }
824 }
825 }
826
827 /* PairCells */
828
829 std::vector<int> partner(critical.size(), -1);
830
831 auto eliminateBoundary = [&](const int poly) -> int {
832 std::lock_guard lock(poly_mutex[poly]);
833 HashSet<int> boundary(poly_to_crit[poly].begin(),
834 poly_to_crit[poly].end(),
835 poly_to_crit[poly].size());
836 ;
837 while(true) {
838 const int youngest_id
839 = *std::max_element(boundary.begin(), boundary.end());
840 std::lock_guard lock_crit(crit_mutex[youngest_id]);
841 if(partner[youngest_id] == -1) {
842 partner[youngest_id] = poly;
843 poly_to_crit[poly].assign(boundary.begin(), boundary.end());
844 return -1;
845 }
846 std::lock_guard lock_partner(poly_mutex[partner[youngest_id]]);
847 if(MSA[partner[youngest_id]].d > MSA[poly].d) {
848 const int tmp = partner[youngest_id];
849 partner[youngest_id] = poly;
850 poly_to_crit[poly].assign(boundary.begin(), boundary.end());
851 return tmp;
852 }
853 for(const int crit : poly_to_crit[partner[youngest_id]]) {
854 const auto it = boundary.find(crit);
855 if(it == boundary.end())
856 boundary.insert(crit);
857 else
858 boundary.erase(it);
859 }
860 }
861 };
862
863#pragma omp parallel for
864 for(const int poly : polytopes) {
865 int p = poly;
866 while(p >= 0)
867 p = eliminateBoundary(p);
868 }
869
870 /* Next MSA */
871 for(unsigned i = 0; i < critical.size(); ++i) {
872 const int poly = partner[i];
873 if(poly == -1) // unassigned -> go in next MSA
874 nextMSA.emplace_back(critical[criticalIndices[i]]);
875 else {
876 const FiltratedDSimplex<minus1(D)> &c = critical[criticalIndices[i]];
877 const FiltratedDSimplex<D> &death = MSA[poly];
878 if(c.d < death.d)
879 ph.emplace_back(
880 FiltratedSimplex{Simplex(c.s.begin(), c.s.end()), sqrt(c.d)},
882 Simplex(death.s.begin(), death.s.end()), sqrt(death.d)});
883 }
884 }
885#endif
886 }
887
888 template <unsigned DIM>
889 void runDelaunayRipsPersistenceDiagram(rpd::PointCloud const &points,
890 MultidimensionalDiagram &diagram,
891 const int threads) {
892 PointCloud<DIM> p(points.size());
893 for(unsigned i = 0; i < points.size(); ++i) {
894 for(unsigned d = 0; d < DIM; ++d)
895 p[i][d] = points[i][d];
896 }
897 DRPersistenceD<DIM> drpd(p, threads);
898 drpd.run(diagram);
899 }
900
901 template <>
902 inline void
903 runDelaunayRipsPersistenceDiagram<DYN_DIM>(rpd::PointCloud const &points,
904 MultidimensionalDiagram &diagram,
905 const int threads) {
906 PointCloud<DYN_DIM> p = points;
907 DRPersistenceD<DYN_DIM> drpd(p, threads);
908 drpd.run(diagram);
909 }
910
911 template <unsigned DIM>
912 void tryDimension(rpd::PointCloud const &points,
913 MultidimensionalDiagram &diagram,
914 const int threads) {
915 if constexpr(DIM <= TTK_DELAUNAY_MAX_COMPILED_DIMENSION) {
916 if(points[0].size() == DIM)
917 runDelaunayRipsPersistenceDiagram<DIM>(points, diagram, threads);
918 else
919 tryDimension<DIM + 1>(points, diagram, threads);
920 } else
921 runDelaunayRipsPersistenceDiagram<DYN_DIM>(points, diagram, threads);
922 }
923
924 inline void tryDimensions(rpd::PointCloud const &points,
925 MultidimensionalDiagram &diagram,
926 const int threads) {
927 tryDimension<4>(points, diagram, threads);
928 }
929
930} // namespace ttk::gph
931
932#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
boost::unordered_map< X, Y > HashMap
Definition geoPHUtils.h:57
FiltratedEdge max(const FiltratedEdge &a, const FiltratedEdge &b)
std::array< id_t, 3 > Facet
constexpr value_t inf
bool operator<(const FiltratedEdge &e1, const FiltratedEdge &e2)
std::vector< Diagram > MultidimensionalDiagram
std::pair< Simplex, value_t > FiltratedSimplex
std::vector< PersistencePair > Diagram
std::vector< id_t > Simplex
T end(std::pair< T, T > &p)
Definition ripser.cpp:503
T begin(std::pair< T, T > &p)
Definition ripser.cpp:499