TTK
Loading...
Searching...
No Matches
TopologicalSkeleton.h
Go to the documentation of this file.
1
32
33#pragma once
34
35// base code includes
36#include <DiscreteVectorField.h>
37#include <Triangulation.h>
39
40#include <queue>
41
42namespace ttk {
43 class TopologicalSkeleton : public virtual Debug {
44 public:
46
49 std::vector<std::array<float, 3>> points_{};
50 std::vector<char> cellDimensions_{};
51 std::vector<SimplexId> cellIds_{};
52 std::vector<char> isOnBoundary_{};
53 std::vector<SimplexId> PLVertexIdentifiers_{};
54 std::vector<SimplexId> manifoldSize_{};
55 void clear() {
56 *this = {};
57 };
58 };
59
62 struct {
64 std::vector<float> points_{};
65 std::vector<char> smoothingMask_{};
66 std::vector<char> cellDimensions_{};
67 std::vector<ttk::SimplexId> cellIds_{};
68 } pt{}; // point data arrays
69 struct {
71 std::vector<ttk::SimplexId> connectivity_{};
72 std::vector<ttk::SimplexId> sourceIds_{};
73 std::vector<ttk::SimplexId> destinationIds_{};
74 std::vector<ttk::SimplexId> separatrixIds_{};
75 std::vector<char> separatrixTypes_{};
76 std::vector<char> isOnBoundary_{};
77 std::vector<char> isOrbit_{};
78 } cl{}; // cell data arrays
79 void clear() {
80 *this = {};
81 };
82 };
83
86 struct {
88 std::vector<float> points_{};
89 } pt{}; // point data arrays
90 struct {
92 std::vector<ttk::SimplexId> offsets_{};
93 std::vector<ttk::SimplexId> connectivity_{};
94 std::vector<ttk::SimplexId> sourceIds_{};
95 std::vector<ttk::SimplexId> separatrixIds_{};
96 std::vector<char> separatrixTypes_{};
97 std::vector<char> isOnBoundary_{};
98 } cl{}; // cell data arrays
99 void clear() {
100 *this = {};
101 };
102 };
103
110
117 template <typename dataType, typename triangulationType>
118 inline int execute(OutputCriticalPoints &outCP,
119 Output1Separatrices &outSeps1,
120 Output2Separatrices &outSeps2,
121 OutputManifold &outManifold,
122 const dataType *const vectors,
123 const size_t vectorsMTime,
124 const triangulationType &triangulation);
125
130 inline void setComputeCriticalPoints(const bool state) {
131 this->ComputeCriticalPoints = state;
132 }
133
137 inline void setComputeSeparatrices1(const bool doAscending,
138 const bool doDescending,
139 const bool doSaddleConnectors) {
140 this->ComputeAscendingSeparatrices1 = doAscending;
141 this->ComputeDescendingSeparatrices1 = doDescending;
142 this->ComputeSaddleConnectors = doSaddleConnectors;
143 }
144
148 inline void setComputeCycles1(const bool doAttracting,
149 const bool doRepelling) {
150 this->ComputeAttractingCycles1 = doAttracting;
151 this->ComputeRepellingCycles1 = doRepelling;
152 }
153
157 inline void setComputeSeparatrices2(const bool doAscending,
158 const bool doDescending) {
159 this->ComputeAscendingSeparatrices2 = doAscending;
160 this->ComputeDescendingSeparatrices2 = doDescending;
161 }
162
166 inline void setComputeSegmentation(const bool doAscending,
167 const bool doDescending,
168 const bool doMorseSmale) {
169 this->ComputeAscendingSegmentation = doAscending;
170 this->ComputeDescendingSegmentation = doDescending;
171 this->ComputeFinalSegmentation = doMorseSmale;
172 }
173
179 inline void setRunSimplification(const bool state) {
180 RunSimplification = state;
181 }
182
183 inline void setFullOrbits(const bool fullOrbits) {
184 this->simplifierField_.setFullOrbitSimplification(fullOrbits);
185 }
186
192 inline void setSimplificationThreshold(const double threshold) {
193 SimplificationThreshold = threshold;
194 }
195
201 this->simplifierField_.preconditionTriangulation(data);
202 data->preconditionCellEdges();
204 }
205
206 protected:
211 struct Separatrix {
221 std::vector<dcg::Cell> geometry_;
222 };
223
228 template <typename dataType, typename triangulationType>
229 int
230 getDescendingSeparatrices1(const std::vector<SimplexId> &saddles,
231 std::vector<Separatrix> &separatrices,
232 const triangulationType &triangulation) const;
233
238 template <typename dataType, typename triangulationType>
239 int getAscendingSeparatrices1(const std::vector<SimplexId> &saddles,
240 std::vector<Separatrix> &separatrices,
241 const triangulationType &triangulation) const;
242
247 template <typename triangulationType>
248 int getSaddleConnectors(const std::vector<SimplexId> &saddles2,
249 std::vector<Separatrix> &separatrices,
250 const triangulationType &triangulation) const;
251
256 template <typename triangulationType>
257 int getAttractingCycles1(std::vector<Separatrix> &separatrices,
258 const triangulationType &triangulation) const;
259
264 template <typename triangulationType>
265 int getRepellingCycles1(std::vector<Separatrix> &separatrices,
266 const triangulationType &triangulation) const;
267
271 template <typename dataType, typename triangulationType>
273 const std::vector<Separatrix> &separatrices,
274 const triangulationType &triangulation) const;
275
280 template <typename triangulationType>
282 const std::vector<SimplexId> &saddles2,
283 std::vector<Separatrix> &separatrices,
284 std::vector<std::vector<SimplexId>> &separatricesSaddles,
285 const triangulationType &triangulation) const;
286
291 template <typename triangulationType>
293 Output2Separatrices &outSeps2,
294 const std::vector<Separatrix> &separatrices,
295 const std::vector<std::vector<SimplexId>> &separatricesSaddles,
296 const triangulationType &triangulation) const;
297
303 template <typename triangulationType>
304 int getDualPolygon(const SimplexId edgeId,
305 SimplexId *const polygon,
306 const size_t polSize,
307 const triangulationType &triangulation) const;
308
312 template <typename triangulationType>
313 int sortDualPolygonVertices(SimplexId *const polygon,
314 const size_t polSize,
315 const triangulationType &triangulation) const;
316
321 template <typename triangulationType>
323 const std::vector<SimplexId> &saddles1,
324 std::vector<Separatrix> &separatrices,
325 std::vector<std::vector<SimplexId>> &separatricesSaddles,
326 const triangulationType &triangulation) const;
327
332 template <typename triangulationType>
334 Output2Separatrices &outSeps2,
335 const std::vector<Separatrix> &separatrices,
336 const std::vector<std::vector<SimplexId>> &separatricesSaddles,
337 const triangulationType &triangulation) const;
338
343 std::vector<std::vector<Separatrix>> &separatrices) const;
344
349 template <typename triangulationType>
350 int setAscendingSegmentation(const std::vector<SimplexId> &maxima,
351 std::vector<Separatrix> &repellingOrbits,
352 SimplexId *const morseSmaleManifold,
353 const triangulationType &triangulation) const;
354
359 template <typename triangulationType>
360 int setDescendingSegmentation(const std::vector<SimplexId> &minima,
361 std::vector<Separatrix> &attractingOrbits,
362 SimplexId *const morseSmaleManifold,
363 const triangulationType &triangulation) const;
364
369 template <typename triangulationType>
370 int setFinalSegmentation(const SimplexId numberOfMaxima,
371 const SimplexId *const ascendingManifold,
372 const SimplexId *const descendingManifold,
373 SimplexId *const morseSmaleManifold,
374 const triangulationType &triangulation) const;
375
377
389
390 bool RunSimplification{false};
393 };
394} // namespace ttk
395
396// ---------------- //
397// Execute method //
398// ---------------- //
399
400template <typename dataType, typename triangulationType>
402 Output1Separatrices &outSeps1,
403 Output2Separatrices &outSeps2,
404 OutputManifold &outManifold,
405 const dataType *const vectors,
406 const size_t vectorsMTime,
407 const triangulationType &triangulation) {
408#ifndef TTK_ENABLE_KAMIKAZE
409 if(vectors == nullptr) {
410 this->printErr("Input vector field pointer is null.");
411 return -1;
412 }
413#endif
414 TTK_FORCE_USE(outManifold); // Remove unused param error.
415 Timer t;
416
417 outCP.clear();
418 outSeps1.clear();
419 outSeps2.clear();
420 const auto dim = triangulation.getDimensionality();
421
422 this->simplifierField_.setThreadNumber(threadNumber_);
423 this->simplifierField_.setDebugLevel(debugLevel_);
424 this->simplifierField_.buildField<dataType, triangulationType>(
425 vectors, vectorsMTime, triangulation);
426 // First simplify the discrete field as desired
427 if(this->RunSimplification) {
428 int persistenceThreshold = static_cast<int>(this->SimplificationThreshold);
429 std::vector<ttk::VectorSimplification::PlotPoint> emptyPlot;
430 this->simplifierField_.performSimplification<dataType, triangulationType>(
431 persistenceThreshold, false, emptyPlot, triangulation);
432 }
433
434 std::array<std::vector<SimplexId>, 4> criticalPoints{};
436 Timer tm{};
437 this->simplifierField_.dcvf_.getCriticalPoints(
438 criticalPoints, triangulation);
439 this->printMsg(" Critical points extracted", 1.0, tm.getElapsedTime(),
440 this->threadNumber_, debug::LineMode::NEW,
442 }
443
444 std::vector<std::vector<Separatrix>> separatrices1{};
445
446 // 1-separatrices
447 Timer tm1sep{};
448
449 if(dim > 1 && ComputeDescendingSeparatrices1) {
450 Timer tmp;
451 separatrices1.emplace_back();
453 criticalPoints[1], separatrices1.back(), triangulation);
454
455 this->printMsg(" Descending 1-separatrices computed", 1.0,
456 tmp.getElapsedTime(), this->threadNumber_,
458 }
459
460 if(dim > 1 && ComputeAscendingSeparatrices1) {
461 Timer tmp;
462 separatrices1.emplace_back();
463
465 criticalPoints[dim - 1], separatrices1.back(), triangulation);
466
467 this->printMsg(" Ascending 1-separatrices computed", 1.0,
468 tmp.getElapsedTime(), this->threadNumber_,
470 }
471
472 std::vector<Separatrix> attractingCycles{};
473
475 Timer tmp;
476 // separatrices1.emplace_back();
477
478 getAttractingCycles1(attractingCycles, triangulation);
480 separatrices1.emplace_back(attractingCycles);
481 this->printMsg(" Attracting 1-cycles computed", 1.0, tmp.getElapsedTime(),
482 this->threadNumber_, debug::LineMode::NEW,
484 }
485
486 std::vector<Separatrix> repellingCycles{};
487
489 Timer tmp;
490 // separatrices1.emplace_back();
491
492 getRepellingCycles1(repellingCycles, triangulation);
494 separatrices1.emplace_back(repellingCycles);
495 this->printMsg(" Repelling 1-cycles computed", 1.0, tmp.getElapsedTime(),
496 this->threadNumber_, debug::LineMode::NEW,
498 }
499
500 // saddle-connectors
501 if(dim == 3 && ComputeSaddleConnectors) {
502 Timer tmp;
503 separatrices1.emplace_back();
504
505 getSaddleConnectors(criticalPoints[2], separatrices1.back(), triangulation);
506
507 this->printMsg(" Saddle connectors computed", 1.0, tmp.getElapsedTime(),
508 this->threadNumber_, debug::LineMode::NEW,
510 }
511
512 if(dim > 1
516 Timer tmp{};
517 if(separatrices1.size() > 0) {
518 this->flattenSeparatricesVectors(separatrices1);
520 outSeps1, separatrices1[0], triangulation);
521 }
522
523 this->printMsg(" 1-separatrices set", 1.0, tmp.getElapsedTime(),
524 this->threadNumber_, debug::LineMode::NEW,
526
527 this->printMsg("1-separatrices computed", 1.0, tm1sep.getElapsedTime(),
528 this->threadNumber_);
529 }
530
531 // 2-separatrices
532 Timer tm2sep{};
533
534 if(dim == 3 && ComputeDescendingSeparatrices2) {
535 Timer tmp;
536 std::vector<Separatrix> separatrices;
537 std::vector<std::vector<SimplexId>> separatricesSaddles;
539 criticalPoints[2], separatrices, separatricesSaddles, triangulation);
541 outSeps2, separatrices, separatricesSaddles, triangulation);
542
543 this->printMsg(" Descending 2-separatrices computed", 1.0,
544 tmp.getElapsedTime(), this->threadNumber_,
546 }
547
548 if(dim == 3 && ComputeAscendingSeparatrices2) {
549 Timer tmp;
550 std::vector<Separatrix> separatrices;
551 std::vector<std::vector<SimplexId>> separatricesSaddles;
553 criticalPoints[1], separatrices, separatricesSaddles, triangulation);
555 outSeps2, separatrices, separatricesSaddles, triangulation);
556
557 this->printMsg(" Ascending 2-separatrices computed", 1.0,
558 tmp.getElapsedTime(), this->threadNumber_,
560 }
561
564 this->printMsg("2-separatrices computed", 1.0, tm2sep.getElapsedTime(),
565 this->threadNumber_);
566 }
567
569 Timer tmp;
570
571 if(ComputeAscendingSegmentation && criticalPoints[dim].size() > 0) {
572 setAscendingSegmentation(criticalPoints[dim], repellingCycles,
573 outManifold.ascending_, triangulation);
574 }
575 if(ComputeDescendingSegmentation && criticalPoints[0].size() > 0) {
576 setDescendingSegmentation(criticalPoints[0], attractingCycles,
577 outManifold.descending_, triangulation);
578 }
580 && ComputeFinalSegmentation && criticalPoints[dim].size() > 0
581 && criticalPoints[0].size() > 0) {
582 SimplexId numSources = static_cast<SimplexId>(criticalPoints[dim].size()
583 + repellingCycles.size());
584 setFinalSegmentation(numSources, outManifold.ascending_,
585 outManifold.descending_, outManifold.morseSmale_,
586 triangulation);
587 }
588
589 this->printMsg(
590 "Segmentation computed", 1.0, tmp.getElapsedTime(), this->threadNumber_);
591 }
592
594 this->simplifierField_.dcvf_.setCriticalPoints<dataType, triangulationType>(
595 criticalPoints, outCP.points_, outCP.cellDimensions_, outCP.cellIds_,
596 outCP.isOnBoundary_, outCP.PLVertexIdentifiers_, triangulation);
597
599 this->simplifierField_.dcvf_.setManifoldSize(
600 criticalPoints, outManifold.ascending_, outManifold.descending_,
601 outCP.manifoldSize_);
602 }
603 }
604
605 this->printMsg("Data-set ("
606 + std::to_string(triangulation.getNumberOfVertices())
607 + " points) processed",
608 1.0, t.getElapsedTime(), this->threadNumber_);
609
610 return 0;
611}
612
613// ---------------- //
614// 1-Separatrices //
615// ---------------- //
616
617template <typename dataType, typename triangulationType>
619 const std::vector<SimplexId> &saddles,
620 std::vector<Separatrix> &separatrices,
621 const triangulationType &triangulation) const {
622
623 const SimplexId numberOfSaddles = saddles.size();
624
625 // only 2 descending separatrices per 1-saddle
626 const SimplexId numberOfSeparatrices = 2 * numberOfSaddles;
627 separatrices.resize(numberOfSeparatrices);
628
629 // apriori: by default construction, the separatrices are not valid
630#ifdef TTK_ENABLE_OPENMP
631#pragma omp parallel for num_threads(threadNumber_)
632#endif
633 for(SimplexId i = 0; i < numberOfSaddles; ++i) {
634 const Cell saddle{1, saddles[i]};
635
636 // add descending vpaths
637 {
638 const Cell &saddle1 = saddle;
639
640 for(int j = 0; j < 2; ++j) {
641 SimplexId vertexId;
642 triangulation.getEdgeVertex(saddle1.id_, j, vertexId);
643
644 std::vector<Cell> vpath;
645 vpath.emplace_back(saddle1);
646 simplifierField_.dcvf_.getDescendingPath<dataType, triangulationType>(
647 Cell(0, vertexId), vpath, triangulation, true);
648
649 const Cell &lastCell = vpath.back();
650 if(lastCell.dim_ == 0
651 and simplifierField_.dcvf_.isCellCritical(lastCell)) {
652 separatrices[2 * i + j].source_ = saddle;
653 separatrices[2 * i + j].destination_ = lastCell;
654 separatrices[2 * i + j].geometry_ = std::move(vpath);
655 } else { // A cycle
656 separatrices[2 * i + j].source_ = saddle;
657 separatrices[2 * i + j].destination_ = Cell(0, -1); // Denote a cycle
658 separatrices[2 * i + j].geometry_ = std::move(vpath);
659 }
660 }
661 }
662 }
663
664 return 0;
665}
666
667template <typename dataType, typename triangulationType>
669 const std::vector<SimplexId> &saddles,
670 std::vector<Separatrix> &separatrices,
671 const triangulationType &triangulation) const {
672
673 const auto dim{triangulation.getDimensionality()};
674
675 // Triangulation method pointers for 3D
676 auto getFaceStarNumber = &triangulationType::getTriangleStarNumber;
677 auto getFaceStar = &triangulationType::getTriangleStar;
678 if(dim == 2) {
679 // Triangulation method pointers for 2D
680 getFaceStarNumber = &triangulationType::getEdgeStarNumber;
681 getFaceStar = &triangulationType::getEdgeStar;
682 }
683
684 const SimplexId numberOfSaddles = saddles.size();
685
686 std::vector<std::vector<Separatrix>> sepsPerSaddle(numberOfSaddles);
687
688 // apriori: by default construction, the separatrices are not valid
689#ifdef TTK_ENABLE_OPENMP
690#pragma omp parallel for num_threads(threadNumber_)
691#endif
692 for(SimplexId i = 0; i < numberOfSaddles; ++i) {
693 const Cell saddle{dim - 1, saddles[i]};
694
695 // add ascending vpaths
696 const auto starNumber{(triangulation.*getFaceStarNumber)(saddle.id_)};
697 for(SimplexId j = 0; j < starNumber; ++j) {
698
699 SimplexId sId{};
700 (triangulation.*getFaceStar)(saddle.id_, j, sId);
701
702 std::vector<Cell> vpath{saddle};
703 simplifierField_.dcvf_.getAscendingPath<dataType, triangulationType>(
704 Cell(dim, sId), vpath, triangulation, true);
705
706 const Cell &lastCell = vpath.back();
707 if(lastCell.dim_ == dim
708 and simplifierField_.dcvf_.isCellCritical(lastCell)) {
709 sepsPerSaddle[i].emplace_back();
710 sepsPerSaddle[i].back().source_ = saddle;
711 sepsPerSaddle[i].back().destination_ = lastCell;
712 sepsPerSaddle[i].back().geometry_ = std::move(vpath);
713 } else { // A Cycle or border
714 sepsPerSaddle[i].emplace_back();
715 sepsPerSaddle[i].back().source_ = saddle;
716 sepsPerSaddle[i].back().destination_ = Cell(dim, -1);
717 sepsPerSaddle[i].back().geometry_ = std::move(vpath);
718 }
719 }
720 }
721
722 if(numberOfSaddles != 0) {
723 this->flattenSeparatricesVectors(sepsPerSaddle);
724
725 separatrices = std::move(sepsPerSaddle[0]);
726 }
727 return 0;
728}
729
730template <typename triangulationType>
732 const std::vector<SimplexId> &saddles2,
733 std::vector<Separatrix> &separatrices,
734 const triangulationType &triangulation) const {
735
736 const auto nTriangles = triangulation.getNumberOfTriangles();
737 // visited triangles (one vector per thread)
738 std::vector<bool> isVisited(nTriangles, false);
739 std::vector<SimplexId> visitedTriangles{};
740
741 using Vpath = std::vector<Cell>;
742
743 const auto dim{triangulation.getDimensionality()};
744
745 std::vector<std::vector<Separatrix>> sepsByThread(saddles2.size());
746 std::vector<SimplexId> saddles1{};
747
748#ifdef TTK_ENABLE_OPENMP
749#pragma omp parallel for num_threads(threadNumber_) schedule(dynamic) \
750 firstprivate(isVisited, visitedTriangles, saddles1)
751#endif // TTK_ENABLE_OPENMP
752 for(size_t i = 0; i < saddles2.size(); ++i) {
753 const Cell s2{dim - 1, saddles2[i]};
754
755 VisitedMask mask{isVisited, visitedTriangles};
756 simplifierField_.dcvf_.getDescendingWall(
757 s2, mask, triangulation, nullptr, &saddles1);
758
759 for(const auto saddle1Id : saddles1) {
760 const Cell s1{1, saddle1Id};
761
762 Vpath vpath;
763 simplifierField_.dcvf_.getAscendingPathThroughWall(
764 s1, s2, isVisited, &vpath, triangulation);
765
766 if(vpath.empty()) {
767 // safety, should be unreachable
768 continue;
769 }
770 const auto &last = vpath.back();
771
772 if(last.dim_ == s2.dim_ && last.id_ == s2.id_) {
773 sepsByThread[i].emplace_back();
774 sepsByThread[i].back().source_ = s1;
775 sepsByThread[i].back().destination_ = s2;
776 sepsByThread[i].back().geometry_ = std::move(vpath);
777 }
778 }
779 }
780 if(saddles2.size() != 0) {
781 this->flattenSeparatricesVectors(sepsByThread);
782
783 separatrices = std::move(sepsByThread[0]);
784 }
785
786 return 0;
787}
788
789template <typename triangulationType>
791 std::vector<Separatrix> &separatrices,
792 const triangulationType &triangulation) const {
793 // Store the vpaths of cycles found by minimum vertex id
794 std::map<SimplexId, std::vector<Cell>> minToCycles;
795
796 const auto nVerts{triangulation.getNumberOfVertices()};
797
798 std::vector<SimplexId> visited{};
799 std::vector<char> isCycle;
800 isCycle.resize(nVerts, 0);
801 std::vector<char> hasChecked;
802 hasChecked.resize(nVerts, 0);
803
804#ifdef TTK_ENABLE_OPENMP
805#pragma omp parallel for num_threads(threadNumber_) \
806 firstprivate(visited, isCycle)
807#endif // TTK_ENABLE_OPENMP
808 for(SimplexId i = 0; i < nVerts; ++i) {
809 if(hasChecked[i] != 0) {
810 continue;
811 }
812 visited.clear();
813 auto curr{i};
814 while(hasChecked[curr] == 0) {
815 // Check if a cycle has occured
816 if(isCycle[curr] == 1) {
817 std::vector<Cell> cyclePath{Cell{0, curr}};
818 SimplexId currentMin{curr};
819 while(visited.back() != curr) {
820 cyclePath.emplace_back(Cell{0, visited.back()});
821
822 hasChecked[visited.back()] = 1;
823 if(visited.back() < currentMin) {
824 currentMin = visited.back();
825 }
826 visited.pop_back();
827 }
828 cyclePath.emplace_back(Cell{0, curr});
829// Critical section to safely update minToCycles
830#pragma omp critical
831 {
832 if(minToCycles[currentMin].size() == 0) { // Not assigned key
833 minToCycles[currentMin] = cyclePath;
834 }
835 }
836 break;
837 }
838 if(this->simplifierField_.dcvf_.isCellCritical(Cell{0, curr})) {
839 break;
840 }
841 // follow a V-path till an already checked vertex is reached
842 const auto pairedEdge{this->simplifierField_.dcvf_.getPairedCell(
843 Cell{0, curr}, triangulation)};
844 SimplexId next{};
845 triangulation.getEdgeVertex(pairedEdge, 0, next);
846 if(next == curr) {
847 triangulation.getEdgeVertex(pairedEdge, 1, next);
848 }
849 visited.emplace_back(curr);
850 isCycle[curr] = 1;
851 curr = next;
852 }
853 for(const auto el : visited) {
854 hasChecked[el] = 1;
855 isCycle[el] = 0;
856 }
857 } // End of Parallel for loop
858
859 // Extract VPaths(iterate over cycles)
860 for(auto it = minToCycles.begin(); it != minToCycles.end(); ++it) {
861 auto vpath = it->second;
862 auto startCell = vpath.back();
863 separatrices.emplace_back();
864 separatrices.back().source_ = startCell;
865 separatrices.back().destination_ = startCell;
866 separatrices.back().geometry_ = std::move(vpath);
867 }
868
869 return 0;
870}
871
872template <typename triangulationType>
874 std::vector<Separatrix> &separatrices,
875 const triangulationType &triangulation) const {
876 // Store the vpaths of cycles found by minimum cell id
877 std::map<SimplexId, std::vector<Cell>> minToCycles;
878
879 const auto nCells{triangulation.getNumberOfCells()};
880
881 const auto dim{triangulation.getDimensionality()};
882
883 // Triangulation method pointers for 3D
884 auto getFaceStarNumber = &triangulationType::getTriangleStarNumber;
885 auto getFaceStar = &triangulationType::getTriangleStar;
886 if(dim == 2) {
887 // Triangulation method pointers for 2D
888 getFaceStarNumber = &triangulationType::getEdgeStarNumber;
889 getFaceStar = &triangulationType::getEdgeStar;
890 } else if(dim == 1) {
891 // Triangulation method pointers for 1D
892 getFaceStarNumber = &triangulationType::getVertexStarNumber;
893 getFaceStar = &triangulationType::getVertexStar;
894 }
895
896 // cells visited during the propagation alongside one integral line
897 std::vector<SimplexId> visited{};
898 // all marked cells
899 std::vector<char> hasChecked;
900 hasChecked.resize(nCells, 0);
901 // cycle detection array
902 std::vector<char> isCycle;
903 isCycle.resize(nCells, 0);
904
905#ifdef TTK_ENABLE_OPENMP
906#pragma omp parallel for num_threads(threadNumber_) \
907 firstprivate(visited, isCycle)
908#endif // TTK_ENABLE_OPENMP
909 for(SimplexId i = 0; i < nCells; ++i) {
910 if(hasChecked[i] != 0) {
911 continue;
912 }
913 visited.clear();
914 auto curr{i};
915 while(hasChecked[curr] == 0) {
916 if(isCycle[curr] == 1) {
917 std::vector<Cell> cyclePath{Cell{dim, curr}};
918 SimplexId currentMin = curr;
919 while(visited.back() != curr) {
920 cyclePath.emplace_back(Cell{dim, visited.back()});
921 hasChecked[visited.back()] = 1;
922 if(visited.back() < currentMin) {
923 currentMin = visited.back();
924 }
925 visited.pop_back();
926 }
927 cyclePath.emplace_back(Cell{dim, curr});
928// Critical section to safely update minToCycles
929#pragma omp critical
930 {
931 if(minToCycles[currentMin].size() == 0) { // Not assigned key
932 minToCycles[currentMin] = cyclePath;
933 }
934 }
935 break;
936 }
937 // Handle critical cell
938 if(this->simplifierField_.dcvf_.isCellCritical(Cell{dim, curr})) {
939 break;
940 }
941 // follow a V-path till an already marked cell is reached
942 // Break when reaching a cycle
943 const auto paired{this->simplifierField_.dcvf_.getPairedCell(
944 Cell{dim, curr}, triangulation, true)};
945 SimplexId next{curr};
946 const auto nStars{(triangulation.*getFaceStarNumber)(paired)};
947 for(SimplexId j = 0; j < nStars; ++j) {
948 (triangulation.*getFaceStar)(paired, j, next);
949 // get the first cell != curr (what of non-manifold datasets?)
950 if(next != curr) {
951 break;
952 }
953 }
954 visited.emplace_back(curr);
955 isCycle[curr] = 1;
956 if(next == curr) {
957 // on the boundary?
958 break;
959 }
960 curr = next;
961 }
962 for(const auto el : visited) {
963 hasChecked[el] = 1;
964 isCycle[el] = 0;
965 }
966 } // End of Parallel For Loop
967
968 // Extract VPaths(iterate over cycles)
969 for(auto it = minToCycles.begin(); it != minToCycles.end(); ++it) {
970 auto vpath = it->second;
971 auto startCell = vpath.back();
972 separatrices.emplace_back();
973 separatrices.back().source_ = startCell;
974 separatrices.back().destination_ = startCell;
975 separatrices.back().geometry_ = std::move(vpath);
976 }
977
978 return 0;
979}
980
981template <typename dataType, typename triangulationType>
983 Output1Separatrices &outSeps1,
984 const std::vector<Separatrix> &separatrices,
985 const triangulationType &triangulation) const {
986
987 // max existing separatrix id + 1 or 0
988 const SimplexId separatrixId
989 = !outSeps1.cl.separatrixIds_.empty()
990 ? *std::max_element(outSeps1.cl.separatrixIds_.begin(),
991 outSeps1.cl.separatrixIds_.end())
992 + 1
993 : 0;
994
995 // total number of separatrices points
996 auto npoints{static_cast<size_t>(outSeps1.pt.numberOfPoints_)};
997 // total number of separatrices cells
998 auto ncells{static_cast<size_t>(outSeps1.cl.numberOfCells_)};
999 // points beginning id for each separatrix geometry
1000 std::vector<size_t> geomPointsBegId{npoints};
1001 // cells beginning id for each separatrix geometry
1002 std::vector<size_t> geomCellsBegId{ncells};
1003
1004 // count total number of points and cells, flatten geometryId loops
1005 for(size_t i = 0; i < separatrices.size(); ++i) {
1006 const auto &sep = separatrices[i];
1007 const auto sepSize = sep.geometry_.size();
1008 npoints += sepSize;
1009 ncells += sepSize - 1;
1010 geomPointsBegId.emplace_back(npoints);
1011 geomCellsBegId.emplace_back(ncells);
1012 }
1013
1014 const int dimensionality = triangulation.getCellVertexNumber(0) - 1;
1015
1016 // resize arrays
1017 outSeps1.pt.points_.resize(3 * npoints);
1018 auto &points = outSeps1.pt.points_;
1019 outSeps1.cl.connectivity_.resize(2 * ncells);
1020 auto &cellsConn = outSeps1.cl.connectivity_;
1021 outSeps1.pt.smoothingMask_.resize(npoints);
1022 outSeps1.pt.cellDimensions_.resize(npoints);
1023 outSeps1.pt.cellIds_.resize(npoints);
1024 outSeps1.cl.sourceIds_.resize(ncells);
1025 outSeps1.cl.destinationIds_.resize(ncells);
1026 outSeps1.cl.separatrixIds_.resize(ncells);
1027 outSeps1.cl.separatrixTypes_.resize(ncells);
1028 outSeps1.cl.isOnBoundary_.resize(ncells);
1029 outSeps1.cl.isOrbit_.resize(ncells);
1030
1031#ifdef TTK_ENABLE_OPENMP
1032#pragma omp parallel for num_threads(threadNumber_) schedule(dynamic)
1033#endif // TTK_ENABLE_OPENMP
1034 for(size_t i = 0; i < separatrices.size(); ++i) {
1035 const auto &sep = separatrices[i];
1036 const auto &sepGeom = sep.geometry_;
1037 const auto sepId = separatrixId + i;
1038 // saddle (asc/desc sep) or saddle1 (saddle connector)
1039 const dcg::Cell &src = sep.source_;
1040 // extremum/ cycle (asc/desc sep) or saddle2 (saddle connector)
1041 const dcg::Cell &dst = sep.destination_;
1042
1043 // get separatrix type
1044 const auto saddleConnector
1045 = dimensionality == 3 && src.dim_ == 1 && dst.dim_ == 2;
1046 const char sepType
1047 = saddleConnector ? 1 : std::min(dst.dim_, dimensionality - 1);
1048
1049 // get boundary condition
1050 const auto onBoundary
1051 = static_cast<char>(
1052 simplifierField_.dcvf_.isBoundary<dataType, triangulationType>(
1053 src, triangulation))
1054 + static_cast<char>(
1055 simplifierField_.dcvf_.isBoundary<dataType, triangulationType>(
1056 dst, triangulation));
1057
1058 for(size_t j = 0; j < sepGeom.size(); ++j) {
1059 const auto &cell = sepGeom[j];
1060 std::array<float, 3> pt{};
1061 triangulation.getCellIncenter(cell.id_, cell.dim_, pt.data());
1062
1063 // index of current point in point data arrays
1064 const auto k = geomPointsBegId[i] + j;
1065
1066 points[3 * k + 0] = pt[0];
1067 points[3 * k + 1] = pt[1];
1068 points[3 * k + 2] = pt[2];
1069
1070 outSeps1.pt.smoothingMask_[k]
1071 = (j == 0 || j == sepGeom.size() - 1) ? 0 : 1;
1072 outSeps1.pt.cellDimensions_[k] = cell.dim_;
1073 outSeps1.pt.cellIds_[k] = cell.id_;
1074
1075 // skip filling cell data for first geometry point
1076 if(j == 0)
1077 continue;
1078
1079 // index of current cell in cell data arrays
1080 const auto l = geomCellsBegId[i] + j - 1;
1081
1082 cellsConn[2 * l + 0] = k - 1;
1083 cellsConn[2 * l + 1] = k;
1084
1085 outSeps1.cl.sourceIds_[l] = src.id_;
1086 outSeps1.cl.destinationIds_[l] = dst.id_;
1087 outSeps1.cl.separatrixIds_[l] = sepId;
1088 outSeps1.cl.separatrixTypes_[l] = sepType;
1089 outSeps1.cl.isOnBoundary_[l] = onBoundary;
1090 outSeps1.cl.isOrbit_[l] = static_cast<char>(src.id_ == dst.id_);
1091 }
1092 }
1093
1094 // update pointers
1095 outSeps1.pt.numberOfPoints_ = npoints;
1096 outSeps1.cl.numberOfCells_ = ncells;
1097
1098 return 0;
1099}
1100
1101// ---------------- //
1102// 2-Separatrices //
1103// ---------------- //
1104
1105template <typename triangulationType>
1107 const std::vector<SimplexId> &saddles1,
1108 std::vector<Separatrix> &separatrices,
1109 std::vector<std::vector<SimplexId>> &separatricesSaddles,
1110 const triangulationType &triangulation) const {
1111 const Cell emptyCell;
1112
1113 const SimplexId numberOfSaddles = saddles1.size();
1114
1115 // estimation of the number of separatrices, apriori : numberOfWalls =
1116 // numberOfSaddles
1117 const SimplexId numberOfSeparatrices = numberOfSaddles;
1118 separatrices.resize(numberOfSeparatrices);
1119 separatricesSaddles.resize(numberOfSeparatrices);
1120
1121 const auto nEdges = triangulation.getNumberOfEdges();
1122 std::vector<bool> isVisited(nEdges, false);
1123 std::vector<SimplexId> visitedEdges{};
1124
1125 // apriori: by default construction, the separatrices are not valid
1126#ifdef TTK_ENABLE_OPENMP
1127#pragma omp parallel for num_threads(threadNumber_) schedule(dynamic) \
1128 firstprivate(isVisited, visitedEdges)
1129#endif // TTK_ENABLE_OPENMP
1130 for(SimplexId i = 0; i < numberOfSaddles; ++i) {
1131 const Cell saddle1{1, saddles1[i]};
1132
1133 std::vector<Cell> wall;
1134 VisitedMask mask{isVisited, visitedEdges};
1135 simplifierField_.dcvf_.getAscendingWall(
1136 saddle1, mask, triangulation, &wall, &separatricesSaddles[i]);
1137
1138 separatrices[i].source_ = saddle1;
1139 separatrices[i].destination_ = emptyCell;
1140 separatrices[i].geometry_ = std::move(wall);
1141 }
1142
1143 return 0;
1144}
1145
1146template <typename triangulationType>
1148 const std::vector<SimplexId> &saddles2,
1149 std::vector<Separatrix> &separatrices,
1150 std::vector<std::vector<SimplexId>> &separatricesSaddles,
1151 const triangulationType &triangulation) const {
1152 const Cell emptyCell;
1153
1154 const SimplexId numberOfSaddles = saddles2.size();
1155
1156 // estimation of the number of separatrices, apriori : numberOfWalls =
1157 // numberOfSaddles
1158 const SimplexId numberOfSeparatrices = numberOfSaddles;
1159 separatrices.resize(numberOfSeparatrices);
1160 separatricesSaddles.resize(numberOfSeparatrices);
1161
1162 const auto nTriangles = triangulation.getNumberOfTriangles();
1163 std::vector<bool> isVisited(nTriangles, false);
1164 std::vector<SimplexId> visitedTriangles{};
1165
1166 const auto dim{triangulation.getDimensionality()};
1167
1168 // apriori: by default construction, the separatrices are not valid
1169#ifdef TTK_ENABLE_OPENMP
1170#pragma omp parallel for num_threads(threadNumber_) schedule(dynamic) \
1171 firstprivate(isVisited, visitedTriangles)
1172#endif // TTK_ENABLE_OPENMP
1173 for(SimplexId i = 0; i < numberOfSaddles; ++i) {
1174 const Cell saddle2{dim - 1, saddles2[i]};
1175
1176 std::vector<Cell> wall;
1177 VisitedMask mask{isVisited, visitedTriangles};
1178 simplifierField_.dcvf_.getDescendingWall(
1179 saddle2, mask, triangulation, &wall, &separatricesSaddles[i]);
1180
1181 separatrices[i].source_ = saddle2;
1182 separatrices[i].destination_ = emptyCell;
1183 separatrices[i].geometry_ = std::move(wall);
1184 }
1185
1186 return 0;
1187}
1188
1189template <typename triangulationType>
1191 const SimplexId edgeId,
1192 SimplexId *const polygon,
1193 const size_t polSize,
1194 const triangulationType &triangulation) const {
1195
1196 for(size_t i = 0; i < polSize; ++i) {
1197 SimplexId starId;
1198 triangulation.getEdgeStar(edgeId, i, starId);
1199 polygon[i] = starId;
1200 }
1201
1202 return 0;
1203}
1204
1205template <typename triangulationType>
1207 SimplexId *const polygon,
1208 const size_t polSize,
1209 const triangulationType &triangulation) const {
1210
1211 for(size_t i = 1; i < polSize; ++i) {
1212
1213 // find polygon[i - 1] neighboring tetra in polygon[i..]
1214 bool isFound = false;
1215 size_t j = i;
1216 for(; j < polSize; ++j) {
1217 // check if current is the neighbor
1218 for(SimplexId k = 0;
1219 k < triangulation.getCellNeighborNumber(polygon[i - 1]); ++k) {
1220 SimplexId neighborId{};
1221 triangulation.getCellNeighbor(polygon[i - 1], k, neighborId);
1222 if(neighborId == polygon[j]) {
1223 isFound = true;
1224 break;
1225 }
1226 }
1227 if(isFound)
1228 break;
1229 }
1230
1231 // place polygon[j] next to polygon[i - 1]
1232 if(isFound) {
1233 std::swap(polygon[j], polygon[i]);
1234 }
1235 }
1236
1237 return 0;
1238}
1239
1240template <typename triangulationType>
1242 Output2Separatrices &outSeps2,
1243 const std::vector<Separatrix> &separatrices,
1244 const std::vector<std::vector<SimplexId>> &separatricesSaddles,
1245 const triangulationType &triangulation) const {
1246
1247 // max existing separatrix id + 1 or 0 if no previous separatrices
1248 const SimplexId separatrixId
1249 = !outSeps2.cl.separatrixIds_.empty()
1250 ? *std::max_element(outSeps2.cl.separatrixIds_.begin(),
1251 outSeps2.cl.separatrixIds_.end())
1252 + 1
1253 : 0;
1254
1255 // total number of separatrices points
1256 auto npoints{static_cast<size_t>(outSeps2.pt.numberOfPoints_)};
1257 // total number of separatrices cells
1258 auto ncells{static_cast<size_t>(outSeps2.cl.numberOfCells_)};
1259 // old number of separatrices cells
1260 const auto noldcells{ncells};
1261 // index of last vertex of last old cell + 1
1262 const auto firstCellId{outSeps2.cl.connectivity_.size()};
1263 // cells beginning id for each separatrix geometry
1264 std::vector<size_t> geomCellsBegId{ncells};
1265
1266 // count total number of cells, flatten geometryId loops
1267 for(size_t i = 0; i < separatrices.size(); ++i) {
1268 const auto &sep = separatrices[i];
1269 ncells += sep.geometry_.size();
1270 geomCellsBegId.emplace_back(ncells);
1271 }
1272
1273 // store the separatrices info (one per separatrix)
1274 std::vector<SimplexId> sepSourceIds(separatrices.size());
1275 std::vector<SimplexId> sepIds(separatrices.size());
1276 std::vector<char> sepOnBoundary(separatrices.size());
1277
1278 // store the polygonal cells tetras SimplexId
1279 std::vector<SimplexId> polygonNTetras(ncells - noldcells);
1280 std::vector<SimplexId> polygonEdgeIds(ncells - noldcells);
1281 std::vector<SimplexId> polygonSepInfosIds(ncells - noldcells);
1282
1283#ifdef TTK_ENABLE_OPENMP
1284#pragma omp parallel for num_threads(threadNumber_) schedule(dynamic)
1285#endif // TTK_ENABLE_OPENMP
1286 for(size_t i = 0; i < separatrices.size(); ++i) {
1287 const auto &sep = separatrices[i];
1288 const auto &sepGeom = sep.geometry_;
1289 const auto &sepSaddles = separatricesSaddles[i];
1290 const auto sepId = separatrixId + i;
1291 const dcg::Cell &src = sep.source_; // saddle1
1292
1293 // get boundary condition
1294 const char onBoundary
1295 = (sepSaddles.empty()
1296 ? 0
1297 : std::count_if(sepSaddles.begin(), sepSaddles.end(),
1298 [&triangulation](const SimplexId a) {
1299 return triangulation.isTriangleOnBoundary(a);
1300 }))
1301 + triangulation.isEdgeOnBoundary(src.id_);
1302
1303 sepIds[i] = sepId;
1304 sepSourceIds[i] = src.id_;
1305 sepOnBoundary[i] = onBoundary;
1306
1307 for(size_t j = 0; j < sepGeom.size(); ++j) {
1308 const auto &cell = sepGeom[j];
1309 // index of current cell in cell data arrays
1310 const auto k = geomCellsBegId[i] + j - noldcells;
1311
1312 polygonNTetras[k] = triangulation.getEdgeStarNumber(cell.id_);
1313
1314 if(polygonNTetras[k] > 2) {
1315 polygonEdgeIds[k] = cell.id_;
1316 polygonSepInfosIds[k] = i;
1317 }
1318 }
1319 }
1320
1321 // indices of valid polygon tetras
1322 std::vector<SimplexId> validTetraIds{};
1323 validTetraIds.reserve(polygonNTetras.size());
1324
1325 for(size_t i = 0; i < polygonNTetras.size(); ++i) {
1326 if(polygonNTetras[i] > 2) {
1327 validTetraIds.emplace_back(i);
1328 }
1329 }
1330
1331 // count number of valid new cells and new points
1332 size_t nnewpoints{};
1333 std::vector<SimplexId> pointsPerCell(validTetraIds.size() + 1);
1334 for(size_t i = 0; i < validTetraIds.size(); ++i) {
1335 nnewpoints += polygonNTetras[validTetraIds[i]];
1336 pointsPerCell[i + 1] = nnewpoints;
1337 }
1338
1339 // resize connectivity array
1340 outSeps2.cl.connectivity_.resize(firstCellId + nnewpoints);
1341 auto cellsConn = &outSeps2.cl.connectivity_[firstCellId];
1342 // copy of cell connectivity array (for removing duplicates vertices)
1343 std::vector<SimplexId> cellVertsIds(nnewpoints);
1344
1345#ifdef TTK_ENABLE_OPENMP
1346#pragma omp parallel for num_threads(threadNumber_)
1347#endif // TTK_ENABLE_OPENMP
1348 for(size_t i = 0; i < validTetraIds.size(); ++i) {
1349 const auto k = validTetraIds[i];
1350
1351 // get tetras in edge star
1352 getDualPolygon(polygonEdgeIds[k], &cellVertsIds[pointsPerCell[i]],
1353 polygonNTetras[k], triangulation);
1354 // sort tetras (in-place)
1356 &cellVertsIds[pointsPerCell[i]], polygonNTetras[k], triangulation);
1357
1358 for(SimplexId j = 0; j < polygonNTetras[k]; ++j) {
1359 cellsConn[pointsPerCell[i] + j] = cellVertsIds[pointsPerCell[i] + j];
1360 }
1361 }
1362
1363 TTK_PSORT(this->threadNumber_, cellVertsIds.begin(), cellVertsIds.end());
1364 const auto last = std::unique(cellVertsIds.begin(), cellVertsIds.end());
1365 cellVertsIds.erase(last, cellVertsIds.end());
1366
1367 // vertex Id to index in points array
1368 std::vector<SimplexId> vertId2PointsId(triangulation.getNumberOfCells());
1369
1370 const auto noldpoints{npoints};
1371 npoints += cellVertsIds.size();
1372 ncells = noldcells + validTetraIds.size();
1373
1374 // resize arrays
1375 outSeps2.pt.points_.resize(3 * npoints);
1376 auto points = &outSeps2.pt.points_[3 * noldpoints];
1377 outSeps2.cl.offsets_.resize(ncells + 1);
1378 outSeps2.cl.offsets_[0] = 0;
1379 auto cellsOff = &outSeps2.cl.offsets_[noldcells];
1380 outSeps2.cl.sourceIds_.resize(ncells);
1381 outSeps2.cl.separatrixIds_.resize(ncells);
1382 outSeps2.cl.separatrixTypes_.resize(ncells);
1383 outSeps2.cl.isOnBoundary_.resize(ncells);
1384
1385#ifdef TTK_ENABLE_OPENMP
1386#pragma omp parallel for num_threads(threadNumber_)
1387#endif // TTK_ENABLE_OPENMP
1388 for(size_t i = 0; i < cellVertsIds.size(); ++i) {
1389 // vertex 3D coords
1390 triangulation.getTetraIncenter(cellVertsIds[i], &points[3 * i]);
1391 // vertex index in cellVertsIds array (do not forget offset)
1392 vertId2PointsId[cellVertsIds[i]] = i + noldpoints;
1393 }
1394
1395#ifdef TTK_ENABLE_OPENMP
1396#pragma omp parallel for num_threads(threadNumber_)
1397#endif // TTK_ENABLE_OPENMP
1398 for(size_t i = 0; i < validTetraIds.size(); ++i) {
1399 const auto m = validTetraIds[i];
1400 const auto k = pointsPerCell[i];
1401 for(SimplexId j = 0; j < polygonNTetras[m]; ++j) {
1402 cellsConn[k + j] = vertId2PointsId[cellsConn[k + j]];
1403 }
1404 const auto l = i + noldcells;
1405 const auto n = polygonSepInfosIds[m];
1406 outSeps2.cl.sourceIds_[l] = sepSourceIds[n];
1407 outSeps2.cl.separatrixIds_[l] = sepIds[n];
1408 outSeps2.cl.separatrixTypes_[l] = 1;
1409 outSeps2.cl.isOnBoundary_[l] = sepOnBoundary[n];
1410 }
1411
1412 for(size_t i = 0; i < validTetraIds.size(); ++i) {
1413 // fill offsets sequentially (due to iteration dependencies)
1414 cellsOff[i + 1] = cellsOff[i] + polygonNTetras[validTetraIds[i]];
1415 }
1416
1417 outSeps2.pt.numberOfPoints_ = npoints;
1418 outSeps2.cl.numberOfCells_ = ncells;
1419
1420 return 0;
1421}
1422
1423template <typename triangulationType>
1425 Output2Separatrices &outSeps2,
1426 const std::vector<Separatrix> &separatrices,
1427 const std::vector<std::vector<SimplexId>> &separatricesSaddles,
1428 const triangulationType &triangulation) const {
1429
1430 // max existing separatrix id + 1 or 0 if no previous separatrices
1431 const SimplexId separatrixId
1432 = !outSeps2.cl.separatrixIds_.empty()
1433 ? *std::max_element(outSeps2.cl.separatrixIds_.begin(),
1434 outSeps2.cl.separatrixIds_.end())
1435 + 1
1436 : 0;
1437
1438 // total number of separatrices points
1439 auto npoints{static_cast<size_t>(outSeps2.pt.numberOfPoints_)};
1440 // total number of separatrices cells
1441 auto ncells{static_cast<size_t>(outSeps2.cl.numberOfCells_)};
1442 // old number of separatrices cells
1443 const auto noldcells{ncells};
1444 // index of last vertex of last old cell + 1
1445 const auto firstCellId{outSeps2.cl.connectivity_.size()};
1446
1447 // cells beginning id for each separatrix geometry
1448 std::vector<size_t> geomCellsBegId{ncells};
1449
1450 // count total number of cells, flatten geometryId loops
1451 for(size_t i = 0; i < separatrices.size(); ++i) {
1452 const auto &sep = separatrices[i];
1453 ncells += sep.geometry_.size();
1454 geomCellsBegId.emplace_back(ncells);
1455 }
1456
1457 // resize arrays
1458 outSeps2.cl.offsets_.resize(ncells + 1);
1459 outSeps2.cl.offsets_[0] = 0;
1460 outSeps2.cl.connectivity_.resize(
1461 firstCellId + 3 * (ncells - noldcells)); // triangles cells
1462 auto cellsOff = &outSeps2.cl.offsets_[noldcells];
1463 auto cellsConn = &outSeps2.cl.connectivity_[firstCellId];
1464 outSeps2.cl.sourceIds_.resize(ncells);
1465 outSeps2.cl.separatrixIds_.resize(ncells);
1466 outSeps2.cl.separatrixTypes_.resize(ncells);
1467 outSeps2.cl.isOnBoundary_.resize(ncells);
1468
1469 // store the cells/triangles vertices vertexId
1470 std::vector<SimplexId> cellVertsIds(3 * (ncells - noldcells));
1471
1472#ifdef TTK_ENABLE_OPENMP
1473#pragma omp parallel for num_threads(threadNumber_)
1474#endif // TTK_ENABLE_OPENMP
1475 for(size_t i = 0; i < separatrices.size(); ++i) {
1476 const auto &sep = separatrices[i];
1477 const auto &sepGeom = sep.geometry_;
1478 const auto &sepSaddles = separatricesSaddles[i];
1479 const auto sepId = separatrixId + i;
1480 const dcg::Cell &src = sep.source_; // saddle2
1481 const char sepType = 2;
1482
1483 // get boundary condition
1484 const char onBoundary
1485 = (sepSaddles.empty()
1486 ? 0
1487 : std::count_if(sepSaddles.begin(), sepSaddles.end(),
1488 [&triangulation](const SimplexId a) {
1489 return triangulation.isEdgeOnBoundary(a);
1490 }))
1491 + triangulation.isTriangleOnBoundary(src.id_);
1492
1493 for(size_t j = 0; j < sepGeom.size(); ++j) {
1494 const auto &cell = sepGeom[j];
1495
1496 // first store the SimplexId of the cell/triangle vertices
1497 SimplexId v0{}, v1{}, v2{};
1498 triangulation.getTriangleVertex(cell.id_, 0, v0);
1499 triangulation.getTriangleVertex(cell.id_, 1, v1);
1500 triangulation.getTriangleVertex(cell.id_, 2, v2);
1501
1502 // index of current cell in cell data arrays
1503 const auto l = geomCellsBegId[i] + j;
1504 // index of current cell among all new cells
1505 const auto m = l - noldcells;
1506
1507 cellsConn[3 * m + 0] = v0;
1508 cellsConn[3 * m + 1] = v1;
1509 cellsConn[3 * m + 2] = v2;
1510 cellVertsIds[3 * m + 0] = v0;
1511 cellVertsIds[3 * m + 1] = v1;
1512 cellVertsIds[3 * m + 2] = v2;
1513
1514 outSeps2.cl.sourceIds_[l] = src.id_;
1515 outSeps2.cl.separatrixIds_[l] = sepId;
1516 outSeps2.cl.separatrixTypes_[l] = sepType;
1517 outSeps2.cl.isOnBoundary_[l] = onBoundary;
1518 }
1519 }
1520
1521 // reduce the cell vertices ids
1522 // (cells are triangles sharing two vertices)
1523 TTK_PSORT(this->threadNumber_, cellVertsIds.begin(), cellVertsIds.end());
1524 const auto last = std::unique(cellVertsIds.begin(), cellVertsIds.end());
1525 cellVertsIds.erase(last, cellVertsIds.end());
1526
1527 // vertex Id to index in points array
1528 std::vector<size_t> vertId2PointsId(triangulation.getNumberOfVertices());
1529
1530 const auto noldpoints{npoints};
1531 npoints += cellVertsIds.size();
1532 outSeps2.pt.points_.resize(3 * npoints);
1533 auto points = &outSeps2.pt.points_[3 * noldpoints];
1534
1535#ifdef TTK_ENABLE_OPENMP
1536#pragma omp parallel for num_threads(threadNumber_)
1537#endif // TTK_ENABLE_OPENMP
1538 for(size_t i = 0; i < cellVertsIds.size(); ++i) {
1539 // vertex 3D coords
1540 triangulation.getVertexPoint(
1541 cellVertsIds[i], points[3 * i + 0], points[3 * i + 1], points[3 * i + 2]);
1542 // vertex index in cellVertsIds array (do not forget offset)
1543 vertId2PointsId[cellVertsIds[i]] = i + noldpoints;
1544 }
1545
1546 const auto lastOffset = noldcells == 0 ? 0 : cellsOff[-1];
1547
1548#ifdef TTK_ENABLE_OPENMP
1549#pragma omp parallel for num_threads(threadNumber_)
1550#endif // TTK_ENABLE_OPENMP
1551 for(size_t i = 0; i < ncells - noldcells; ++i) {
1552 cellsOff[i] = 3 * i + lastOffset;
1553 cellsConn[3 * i + 0] = vertId2PointsId[cellsConn[3 * i + 0]];
1554 cellsConn[3 * i + 1] = vertId2PointsId[cellsConn[3 * i + 1]];
1555 cellsConn[3 * i + 2] = vertId2PointsId[cellsConn[3 * i + 2]];
1556 }
1557
1558 cellsOff[ncells - noldcells] = cellsOff[ncells - noldcells - 1] + 3;
1559
1560 outSeps2.pt.numberOfPoints_ = npoints;
1561 outSeps2.cl.numberOfCells_ = ncells;
1562
1563 return 0;
1564}
1565
1566// ---------------- //
1567// Segmentation //
1568// ---------------- //
1569
1570template <typename triangulationType>
1572 const std::vector<SimplexId> &maxima,
1573 std::vector<Separatrix> &repellingOrbits,
1574 SimplexId *const morseSmaleManifold,
1575 const triangulationType &triangulation) const {
1576
1577 const auto thisDim{triangulation.getDimensionality()};
1578
1579 if(morseSmaleManifold == nullptr) {
1580 this->printErr("Could not compute ascending segmentation");
1581 return 1;
1582 }
1583
1584 Timer tm{};
1585
1586 const auto nVerts{triangulation.getNumberOfVertices()};
1587 std::fill(morseSmaleManifold, morseSmaleManifold + nVerts, -1);
1588 if(maxima.empty()) {
1589 // shortcut for elevation
1590 return 0;
1591 }
1592
1593 const auto nCells{triangulation.getNumberOfCells()};
1594 std::vector<SimplexId> morseSmaleManifoldOnCells(nCells, -1);
1595
1596 size_t nMax{};
1597 for(const auto &id : maxima) {
1598 // mark the maxima
1599 morseSmaleManifoldOnCells[id] = nMax++;
1600 }
1601 for(const auto &orbit : repellingOrbits) {
1602 // mark the orbits
1603 auto cycleCells = orbit.geometry_;
1604 auto cycleNumber = nMax++;
1605 for(auto &cell : cycleCells) {
1606 if(cell.dim_ == thisDim) {
1607 morseSmaleManifoldOnCells[cell.id_] = cycleNumber;
1608 }
1609 }
1610 }
1611
1612 const auto dim{triangulation.getDimensionality()};
1613
1614 // Triangulation method pointers for 3D
1615 auto getFaceStarNumber = &triangulationType::getTriangleStarNumber;
1616 auto getFaceStar = &triangulationType::getTriangleStar;
1617 if(dim == 2) {
1618 // Triangulation method pointers for 2D
1619 getFaceStarNumber = &triangulationType::getEdgeStarNumber;
1620 getFaceStar = &triangulationType::getEdgeStar;
1621 } else if(dim == 1) {
1622 // Triangulation method pointers for 1D
1623 getFaceStarNumber = &triangulationType::getVertexStarNumber;
1624 getFaceStar = &triangulationType::getVertexStar;
1625 }
1626
1627 // cells visited during the propagation alongside one integral line
1628 std::vector<SimplexId> visited{};
1629 // all marked cells
1630 std::vector<uint8_t> isMarked(nCells, 0);
1631 // cycle detection array
1632 std::vector<char> isCycle;
1633 isCycle.resize(nCells, 0);
1634
1635#ifdef TTK_ENABLE_OPENMP
1636#pragma omp parallel for num_threads(threadNumber_) \
1637 firstprivate(visited, isCycle)
1638#endif // TTK_ENABLE_OPENMP
1639 for(SimplexId i = 0; i < nCells; ++i) {
1640 if(isMarked[i] == 1) {
1641 continue;
1642 }
1643 visited.clear();
1644 auto curr{i};
1645 while(morseSmaleManifoldOnCells[curr] == -1) {
1646 if(isMarked[curr] == 1) {
1647 break;
1648 } else if(isCycle[curr] == 1) { // Unmarked cycle(shouldn't occur)
1649 morseSmaleManifoldOnCells[curr]
1650 = -2; // Backup behavior(handled like boundary)
1651 break;
1652 }
1653 // follow a V-path till an already marked cell is reached
1654 // Break when reaching a cycle
1655 const auto paired{this->simplifierField_.dcvf_.getPairedCell(
1656 Cell{dim, curr}, triangulation, true)};
1657 SimplexId next{curr};
1658 const auto nStars{(triangulation.*getFaceStarNumber)(paired)};
1659 for(SimplexId j = 0; j < nStars; ++j) {
1660 (triangulation.*getFaceStar)(paired, j, next);
1661 // get the first cell != curr (what of non-manifold datasets?)
1662 if(next != curr) {
1663 break;
1664 }
1665 }
1666 visited.emplace_back(curr);
1667 isCycle[curr] = 1;
1668 if(next == curr) {
1669 // on the boundary?
1670 break;
1671 }
1672 curr = next;
1673 }
1674 for(const auto el : visited) {
1675 morseSmaleManifoldOnCells[el] = morseSmaleManifoldOnCells[curr];
1676 isMarked[el] = 1;
1677 }
1678 }
1679
1680#ifdef TTK_ENABLE_OPENMP
1681#pragma omp parallel for num_threads(threadNumber_)
1682#endif // TTK_ENABLE_OPENMP
1683 for(SimplexId i = 0; i < nVerts; ++i) {
1684 if(triangulation.getVertexStarNumber(i) < 1) {
1685 // handle non-manifold datasets?
1686 continue;
1687 }
1688 SimplexId starId{};
1689 triangulation.getVertexStar(i, 0, starId);
1690 // put segmentation infos from cells to points
1691 morseSmaleManifold[i] = morseSmaleManifoldOnCells[starId];
1692 }
1693
1694 this->printMsg(" Ascending segmentation computed", 1.0, tm.getElapsedTime(),
1695 this->threadNumber_, debug::LineMode::NEW,
1697
1698 return 0;
1699}
1700
1701template <typename triangulationType>
1703 const std::vector<SimplexId> &minima,
1704 std::vector<Separatrix> &attractingOrbits,
1705 SimplexId *const morseSmaleManifold,
1706 const triangulationType &triangulation) const {
1707
1708 if(morseSmaleManifold == nullptr) {
1709 this->printErr("Could not compute descending segmentation");
1710 return 1;
1711 }
1712
1713 Timer tm{};
1714
1715 const auto nVerts{triangulation.getNumberOfVertices()};
1716
1717 if(minima.size() == 1) {
1718 // shortcut for elevation
1719 std::fill(morseSmaleManifold, morseSmaleManifold + nVerts, 0);
1720 return 0;
1721 }
1722
1723 std::fill(morseSmaleManifold, morseSmaleManifold + nVerts, -1);
1724
1725 size_t nMin{};
1726 for(const auto &cp : minima) {
1727 // mark the minima
1728 morseSmaleManifold[cp] = nMin++;
1729 }
1730
1731 for(const auto &orbit : attractingOrbits) {
1732 auto cycleCells = orbit.geometry_;
1733 auto cycleNumber = nMin++;
1734 for(auto &cell : cycleCells) {
1735 if(cell.dim_ == 0) {
1736 morseSmaleManifold[cell.id_] = cycleNumber;
1737 }
1738 }
1739 }
1740
1741 std::vector<SimplexId> visited{};
1742 std::vector<char> isCycle;
1743 isCycle.resize(nVerts, 0);
1744
1745#ifdef TTK_ENABLE_OPENMP
1746#pragma omp parallel for num_threads(threadNumber_) \
1747 firstprivate(visited, isCycle)
1748#endif // TTK_ENABLE_OPENMP
1749 for(SimplexId i = 0; i < nVerts; ++i) {
1750 if(morseSmaleManifold[i] != -1) {
1751 continue;
1752 }
1753 visited.clear();
1754 auto curr{i};
1755 while(morseSmaleManifold[curr] == -1) {
1756 // Check if a cycle has occured
1757 if(isCycle[curr] == 1) { // Unmarked cycle (shouldn't occur)
1758 morseSmaleManifold[curr]
1759 = -2; // Backup behavior (handled like boundary)
1760 break;
1761 }
1762 // follow a V-path till an already marked vertex is reached
1763 const auto pairedEdge{this->simplifierField_.dcvf_.getPairedCell(
1764 Cell{0, curr}, triangulation)};
1765 SimplexId next{};
1766 triangulation.getEdgeVertex(pairedEdge, 0, next);
1767 if(next == curr) {
1768 triangulation.getEdgeVertex(pairedEdge, 1, next);
1769 }
1770 visited.emplace_back(curr);
1771 isCycle[curr] = 1;
1772 curr = next;
1773 }
1774 for(const auto el : visited) {
1775 morseSmaleManifold[el] = morseSmaleManifold[curr];
1776 }
1777 }
1778
1779 this->printMsg(" Descending segmentation computed", 1.0, tm.getElapsedTime(),
1780 this->threadNumber_, debug::LineMode::NEW,
1782
1783 return 0;
1784}
1785
1786template <typename triangulationType>
1788 const SimplexId numberOfMaxima,
1789 const SimplexId *const ascendingManifold,
1790 const SimplexId *const descendingManifold,
1791 SimplexId *const morseSmaleManifold,
1792 const triangulationType &triangulation) const {
1793
1794 if(ascendingManifold == nullptr || descendingManifold == nullptr
1795 || morseSmaleManifold == nullptr) {
1796 this->printErr("Could not compute final segmentation");
1797 return 1;
1798 }
1799
1800 Timer tm{};
1801
1802 const size_t nVerts = triangulation.getNumberOfVertices();
1803
1804 // associate a unique "sparse region id" to each (ascending, descending) pair
1805
1806#ifdef TTK_ENABLE_OPENMP
1807#pragma omp parallel for num_threads(threadNumber_)
1808#endif // TTK_ENABLE_OPENMP
1809 for(size_t i = 0; i < nVerts; ++i) {
1810 const auto d = ascendingManifold[i];
1811 const auto a = descendingManifold[i];
1812 if(a == -1 || d == -1 || a == -2 || d == -2) {
1813 morseSmaleManifold[i] = -1;
1814 } else {
1815 morseSmaleManifold[i] = a * numberOfMaxima + d;
1816 }
1817 }
1818
1819 // store the "sparse region ids" by copying the morseSmaleManifold output
1820 std::vector<SimplexId> sparseRegionIds(
1821 morseSmaleManifold, morseSmaleManifold + nVerts);
1822
1823 // get unique "sparse region ids"
1824 TTK_PSORT(
1825 this->threadNumber_, sparseRegionIds.begin(), sparseRegionIds.end());
1826 const auto last = std::unique(sparseRegionIds.begin(), sparseRegionIds.end());
1827 sparseRegionIds.erase(last, sparseRegionIds.end());
1828
1829 // "sparse region id" -> "dense region id"
1830 std::map<SimplexId, size_t> sparseToDenseRegionId{};
1831
1832 for(size_t i = 0; i < sparseRegionIds.size(); ++i) {
1833 sparseToDenseRegionId[sparseRegionIds[i]] = i;
1834 }
1835
1836 // update region id on all vertices: "sparse id" -> "dense id"
1837
1838#ifdef TTK_ENABLE_OPENMP
1839#pragma omp parallel for num_threads(threadNumber_)
1840#endif // TTK_ENABLE_OPENMP
1841 for(size_t i = 0; i < nVerts; ++i) {
1842 morseSmaleManifold[i] = sparseToDenseRegionId[morseSmaleManifold[i]];
1843 }
1844
1845 this->printMsg(" Final segmentation computed", 1.0, tm.getElapsedTime(),
1846 this->threadNumber_, debug::LineMode::NEW,
1848
1849 return 0;
1850}
#define TTK_FORCE_USE(x)
Force the compiler to use the function/method parameter.
Definition BaseClass.h:57
#define TTK_PSORT(NTHREADS,...)
Parallel sort macro.
Definition OpenMP.h:46
AbstractTriangulation is an interface class that defines an interface for efficient traversal methods...
int debugLevel_
Definition Debug.h:379
int printErr(const std::string &msg, const debug::LineMode &lineMode=debug::LineMode::NEW, std::ostream &stream=std::cerr) const
Definition Debug.h:149
double getElapsedTime()
Definition Timer.h:15
int getAscendingSeparatrices2(const std::vector< SimplexId > &saddles1, std::vector< Separatrix > &separatrices, std::vector< std::vector< SimplexId > > &separatricesSaddles, const triangulationType &triangulation) const
int sortDualPolygonVertices(SimplexId *const polygon, const size_t polSize, const triangulationType &triangulation) const
int setAscendingSegmentation(const std::vector< SimplexId > &maxima, std::vector< Separatrix > &repellingOrbits, SimplexId *const morseSmaleManifold, const triangulationType &triangulation) const
int setFinalSegmentation(const SimplexId numberOfMaxima, const SimplexId *const ascendingManifold, const SimplexId *const descendingManifold, SimplexId *const morseSmaleManifold, const triangulationType &triangulation) const
int getAscendingSeparatrices1(const std::vector< SimplexId > &saddles, std::vector< Separatrix > &separatrices, const triangulationType &triangulation) const
int setDescendingSegmentation(const std::vector< SimplexId > &minima, std::vector< Separatrix > &attractingOrbits, SimplexId *const morseSmaleManifold, const triangulationType &triangulation) const
int getAttractingCycles1(std::vector< Separatrix > &separatrices, const triangulationType &triangulation) const
void setComputeCriticalPoints(const bool state)
int getDualPolygon(const SimplexId edgeId, SimplexId *const polygon, const size_t polSize, const triangulationType &triangulation) const
void setRunSimplification(const bool state)
int getDescendingSeparatrices1(const std::vector< SimplexId > &saddles, std::vector< Separatrix > &separatrices, const triangulationType &triangulation) const
int setDescendingSeparatrices2(Output2Separatrices &outSeps2, const std::vector< Separatrix > &separatrices, const std::vector< std::vector< SimplexId > > &separatricesSaddles, const triangulationType &triangulation) const
void setFullOrbits(const bool fullOrbits)
VectorSimplification simplifierField_
int setSeparatrices1(Output1Separatrices &outSeps1, const std::vector< Separatrix > &separatrices, const triangulationType &triangulation) const
int getDescendingSeparatrices2(const std::vector< SimplexId > &saddles2, std::vector< Separatrix > &separatrices, std::vector< std::vector< SimplexId > > &separatricesSaddles, const triangulationType &triangulation) const
void setComputeCycles1(const bool doAttracting, const bool doRepelling)
void preconditionTriangulation(AbstractTriangulation *const data)
void setComputeSegmentation(const bool doAscending, const bool doDescending, const bool doMorseSmale)
void flattenSeparatricesVectors(std::vector< std::vector< Separatrix > > &separatrices) const
Flatten the vectors of vectors into their first component.
void setComputeSeparatrices2(const bool doAscending, const bool doDescending)
int execute(OutputCriticalPoints &outCP, Output1Separatrices &outSeps1, Output2Separatrices &outSeps2, OutputManifold &outManifold, const dataType *const vectors, const size_t vectorsMTime, const triangulationType &triangulation)
void setSimplificationThreshold(const double threshold)
int setAscendingSeparatrices2(Output2Separatrices &outSeps2, const std::vector< Separatrix > &separatrices, const std::vector< std::vector< SimplexId > > &separatricesSaddles, const triangulationType &triangulation) const
int getRepellingCycles1(std::vector< Separatrix > &separatrices, const triangulationType &triangulation) const
void setComputeSeparatrices1(const bool doAscending, const bool doDescending, const bool doSaddleConnectors)
int getSaddleConnectors(const std::vector< SimplexId > &saddles2, std::vector< Separatrix > &separatrices, const triangulationType &triangulation) const
TTK VectorSimplification processing package.
TTK base package defining the standard types.
int SimplexId
Identifier type for simplices of any dimension.
Definition DataTypes.h:22
1-Separatrices point and cell data arrays
struct ttk::TopologicalSkeleton::Output1Separatrices::@022074354300050243331256337257312310012152134015 cl
struct ttk::TopologicalSkeleton::Output1Separatrices::@054324302236307005372226032341307172275306336073 pt
2-Separatrices point and cell data arrays
struct ttk::TopologicalSkeleton::Output2Separatrices::@262226051151230331113327275342331147011262135307 pt
struct ttk::TopologicalSkeleton::Output2Separatrices::@146237176251162053201273250302122352264036260035 cl
std::vector< std::array< float, 3 > > points_
Pointers to pre-allocated segmentation point data arrays.
Auto-cleaning re-usable graph propagations data structure.
Definition VisitedMask.h:27
printMsg(debug::output::BOLD+" | | | | | . \\ | | (__| | / __/| |_| / __/| (_) |"+debug::output::ENDCOLOR, debug::Priority::PERFORMANCE, debug::LineMode::NEW, stream)