TTK
Loading...
Searching...
No Matches
MorseSmaleComplex.h
Go to the documentation of this file.
1
76
77#pragma once
78
79// base code includes
80#include <DiscreteGradient.h>
82#include <Triangulation.h>
83
84#include <queue>
85
86namespace ttk {
87 class MorseSmaleComplex : public virtual Debug {
88 public:
90
93 std::vector<std::array<float, 3>> points_{};
94 std::vector<char> cellDimensions_{};
95 std::vector<SimplexId> cellIds_{};
96 std::vector<char> isOnBoundary_{};
97 std::vector<SimplexId> PLVertexIdentifiers_{};
98 std::vector<SimplexId> manifoldSize_{};
99 void clear() {
100 *this = {};
101 };
102 };
103
106 struct {
108 std::vector<float> points_{};
109 std::vector<char> smoothingMask_{};
110 std::vector<char> cellDimensions_{};
111 std::vector<ttk::SimplexId> cellIds_{};
112 } pt{}; // point data arrays
113 struct {
115 std::vector<ttk::SimplexId> connectivity_{};
116 std::vector<ttk::SimplexId> sourceIds_{};
117 std::vector<ttk::SimplexId> destinationIds_{};
118 std::vector<ttk::SimplexId> separatrixIds_{};
119 std::vector<char> separatrixTypes_{};
120 std::vector<char> isOnBoundary_{};
121 std::vector<SimplexId> sepFuncMaxId_{};
122 std::vector<SimplexId> sepFuncMinId_{};
123 } cl{}; // cell data arrays
124 void clear() {
125 *this = {};
126 };
127 };
128
131 struct {
133 std::vector<float> points_{};
134 } pt{}; // point data arrays
135 struct {
137 std::vector<ttk::SimplexId> offsets_{};
138 std::vector<ttk::SimplexId> connectivity_{};
139 std::vector<ttk::SimplexId> sourceIds_{};
140 std::vector<ttk::SimplexId> separatrixIds_{};
141 std::vector<char> separatrixTypes_{};
142 std::vector<char> isOnBoundary_{};
143 std::vector<SimplexId> sepFuncMaxId_{};
144 std::vector<SimplexId> sepFuncMinId_{};
145 } cl{}; // cell data arrays
146 void clear() {
147 *this = {};
148 };
149 };
150
157
164 template <typename dataType, typename triangulationType>
165 inline int execute(OutputCriticalPoints &outCP,
166 Output1Separatrices &outSeps1,
167 Output2Separatrices &outSeps2,
168 OutputManifold &outManifold,
169 const dataType *const scalars,
170 const size_t scalarsMTime,
171 const SimplexId *const offsets,
172 const triangulationType &triangulation);
173
178 inline void setComputeCriticalPoints(const bool state) {
179 this->ComputeCriticalPoints = state;
180 }
185 inline void setComputeSeparatrices1(const bool doAscending,
186 const bool doDescending,
187 const bool doSaddleConnectors) {
188 this->ComputeAscendingSeparatrices1 = doAscending;
189 this->ComputeDescendingSeparatrices1 = doDescending;
190 this->ComputeSaddleConnectors = doSaddleConnectors;
191 }
196 inline void setComputeSeparatrices2(const bool doAscending,
197 const bool doDescending) {
198 this->ComputeAscendingSeparatrices2 = doAscending;
199 this->ComputeDescendingSeparatrices2 = doDescending;
200 }
205 inline void setComputeSegmentation(const bool doAscending,
206 const bool doDescending,
207 const bool doMorseSmale) {
208 this->ComputeAscendingSegmentation = doAscending;
209 this->ComputeDescendingSegmentation = doDescending;
210 this->ComputeFinalSegmentation = doMorseSmale;
211 }
212
218 inline void setReturnSaddleConnectors(const bool state) {
220 }
221
227 inline void
231
241
242 protected:
247 struct Separatrix {
257 std::vector<dcg::Cell> geometry_;
258 };
259
264 template <typename triangulationType>
265 int
266 getDescendingSeparatrices1(const std::vector<SimplexId> &saddles,
267 std::vector<Separatrix> &separatrices,
268 const triangulationType &triangulation) const;
269
274 template <typename triangulationType>
275 int getAscendingSeparatrices1(const std::vector<SimplexId> &saddles,
276 std::vector<Separatrix> &separatrices,
277 const triangulationType &triangulation) const;
278
283 template <typename triangulationType>
284 int getSaddleConnectors(const std::vector<SimplexId> &saddles2,
285 std::vector<Separatrix> &separatrices,
286 const triangulationType &triangulation) const;
287
291 template <typename triangulationType>
293 const std::vector<Separatrix> &separatrices,
294 const SimplexId *const offsets,
295 const triangulationType &triangulation) const;
296
301 template <typename triangulationType>
303 const std::vector<SimplexId> &saddles2,
304 std::vector<Separatrix> &separatrices,
305 std::vector<std::vector<SimplexId>> &separatricesSaddles,
306 const triangulationType &triangulation) const;
307
312 template <typename triangulationType>
314 Output2Separatrices &outSeps2,
315 const std::vector<Separatrix> &separatrices,
316 const std::vector<std::vector<SimplexId>> &separatricesSaddles,
317 const SimplexId *const offsets,
318 const triangulationType &triangulation) const;
319
325 template <typename triangulationType>
326 int getDualPolygon(const SimplexId edgeId,
327 SimplexId *const polygon,
328 const size_t polSize,
329 const triangulationType &triangulation) const;
330
334 template <typename triangulationType>
335 int sortDualPolygonVertices(SimplexId *const polygon,
336 const size_t polSize,
337 const triangulationType &triangulation) const;
338
343 template <typename triangulationType>
345 const std::vector<SimplexId> &saddles1,
346 std::vector<Separatrix> &separatrices,
347 std::vector<std::vector<SimplexId>> &separatricesSaddles,
348 const triangulationType &triangulation) const;
349
354 template <typename triangulationType>
356 Output2Separatrices &outSeps2,
357 const std::vector<Separatrix> &separatrices,
358 const std::vector<std::vector<SimplexId>> &separatricesSaddles,
359 const SimplexId *const offsets,
360 const triangulationType &triangulation) const;
361
366 std::vector<std::vector<Separatrix>> &separatrices) const;
367
371 template <typename triangulationType>
372 int setAscendingSegmentation(const std::vector<SimplexId> &maxima,
373 SimplexId *const morseSmaleManifold,
374 const triangulationType &triangulation) const;
375
379 template <typename triangulationType>
380 int setDescendingSegmentation(const std::vector<SimplexId> &minima,
381 SimplexId *const morseSmaleManifold,
382 const triangulationType &triangulation) const;
383
388 template <typename triangulationType>
389 int setFinalSegmentation(const SimplexId numberOfMaxima,
390 const SimplexId *const ascendingManifold,
391 const SimplexId *const descendingManifold,
392 SimplexId *const morseSmaleManifold,
393 const triangulationType &triangulation) const;
394
395 template <typename dataType, typename triangulationType>
396 int returnSaddleConnectors(const double persistenceThreshold,
397 const dataType *const scalars,
398 const SimplexId *const offsets,
399 const triangulationType &triangulation);
400
402
412
417 };
418} // namespace ttk
419
420// ---------------- //
421// Execute method //
422// ---------------- //
423
424template <typename dataType, typename triangulationType>
426 Output1Separatrices &outSeps1,
427 Output2Separatrices &outSeps2,
428 OutputManifold &outManifold,
429 const dataType *const scalars,
430 const size_t scalarsMTime,
431 const SimplexId *const offsets,
432 const triangulationType &triangulation) {
433#ifndef TTK_ENABLE_KAMIKAZE
434 if(scalars == nullptr) {
435 this->printErr("Input scalar field pointer is null.");
436 return -1;
437 }
438
439 if(offsets == nullptr) {
440 this->printErr("Input offset field pointer is null.");
441 return -1;
442 }
443#endif
444 Timer t;
445
446 outCP.clear();
447 outSeps1.clear();
448 outSeps2.clear();
449 const auto dim = triangulation.getDimensionality();
450
453 this->discreteGradient_.setInputScalarField(scalars, scalarsMTime);
454 this->discreteGradient_.setInputOffsets(offsets);
456 triangulation, this->ReturnSaddleConnectors);
457
458 if(this->ReturnSaddleConnectors) {
459 auto persistenceThreshold{this->SaddleConnectorsPersistenceThreshold};
460 if(!this->ThresholdIsAbsolute) {
461 const auto nVerts{triangulation.getNumberOfVertices()};
462 // global extrema are (generally) faster computed on offsets
463 // than on scalar field
464 const auto pair{std::minmax_element(offsets, offsets + nVerts)};
465 // global extrema vertex ids
466 const auto globmin = std::distance(offsets, pair.first);
467 const auto globmax = std::distance(offsets, pair.second);
468 persistenceThreshold *= (scalars[globmax] - scalars[globmin]);
469 this->printMsg("Absolute saddle connectors persistence threshold is "
470 + std::to_string(persistenceThreshold),
472 }
473
475 persistenceThreshold, scalars, offsets, triangulation);
476 }
477
478 std::array<std::vector<SimplexId>, 4> criticalPoints{};
479 {
480 Timer tm{};
481 discreteGradient_.getCriticalPoints(criticalPoints, triangulation);
482 this->printMsg(" Critical points extracted", 1.0, tm.getElapsedTime(),
483 this->threadNumber_, debug::LineMode::NEW,
485 }
486
487 std::vector<std::vector<Separatrix>> separatrices1{};
488
489 // 1-separatrices
490 Timer tm1sep{};
491
492 if(dim > 1 && ComputeDescendingSeparatrices1) {
493 Timer tmp;
494 separatrices1.emplace_back();
495
497 criticalPoints[1], separatrices1.back(), triangulation);
498
499 this->printMsg(" Descending 1-separatrices computed", 1.0,
500 tmp.getElapsedTime(), this->threadNumber_,
502 }
503
504 if(dim > 1 && ComputeAscendingSeparatrices1) {
505 Timer tmp;
506 separatrices1.emplace_back();
507
509 criticalPoints[dim - 1], separatrices1.back(), triangulation);
510
511 this->printMsg(" Ascending 1-separatrices computed", 1.0,
512 tmp.getElapsedTime(), this->threadNumber_,
514 }
515
516 // saddle-connectors
517 if(dim == 3 && ComputeSaddleConnectors) {
518 Timer tmp;
519 separatrices1.emplace_back();
520
521 getSaddleConnectors(criticalPoints[2], separatrices1.back(), triangulation);
522
523 this->printMsg(" Saddle connectors computed", 1.0, tmp.getElapsedTime(),
524 this->threadNumber_, debug::LineMode::NEW,
526 }
527
528 if(dim > 1
531 Timer tmp{};
532
533 this->flattenSeparatricesVectors(separatrices1);
534 setSeparatrices1(outSeps1, separatrices1[0], offsets, triangulation);
535
536 this->printMsg(" 1-separatrices set", 1.0, tmp.getElapsedTime(),
537 this->threadNumber_, debug::LineMode::NEW,
539
540 this->printMsg("1-separatrices computed", 1.0, tm1sep.getElapsedTime(),
541 this->threadNumber_);
542 }
543
544 // 2-separatrices
545 Timer tm2sep{};
546
547 if(dim == 3 && ComputeDescendingSeparatrices2) {
548 Timer tmp;
549 std::vector<Separatrix> separatrices;
550 std::vector<std::vector<SimplexId>> separatricesSaddles;
552 criticalPoints[2], separatrices, separatricesSaddles, triangulation);
554 outSeps2, separatrices, separatricesSaddles, offsets, triangulation);
555
556 this->printMsg(" Descending 2-separatrices computed", 1.0,
557 tmp.getElapsedTime(), this->threadNumber_,
559 }
560
561 if(dim == 3 && ComputeAscendingSeparatrices2) {
562 Timer tmp;
563 std::vector<Separatrix> separatrices;
564 std::vector<std::vector<SimplexId>> separatricesSaddles;
566 criticalPoints[1], separatrices, separatricesSaddles, triangulation);
568 outSeps2, separatrices, separatricesSaddles, offsets, triangulation);
569
570 this->printMsg(" Ascending 2-separatrices computed", 1.0,
571 tmp.getElapsedTime(), this->threadNumber_,
573 }
574
577 this->printMsg("2-separatrices computed", 1.0, tm2sep.getElapsedTime(),
578 this->threadNumber_);
579 }
580
582 Timer tmp;
583
586 criticalPoints[dim], outManifold.ascending_, triangulation);
587 }
590 criticalPoints[0], outManifold.descending_, triangulation);
591 }
594 setFinalSegmentation(criticalPoints[dim].size(), outManifold.ascending_,
595 outManifold.descending_, outManifold.morseSmale_,
596 triangulation);
597 }
598
599 this->printMsg(
600 "Segmentation computed", 1.0, tmp.getElapsedTime(), this->threadNumber_);
601 }
602
605 criticalPoints, outCP.points_, outCP.cellDimensions_, outCP.cellIds_,
606 outCP.isOnBoundary_, outCP.PLVertexIdentifiers_, triangulation);
607
609 discreteGradient_.setManifoldSize(criticalPoints, outManifold.ascending_,
610 outManifold.descending_,
611 outCP.manifoldSize_);
612 }
613 }
614
615 this->printMsg("Data-set ("
616 + std::to_string(triangulation.getNumberOfVertices())
617 + " points) processed",
618 1.0, t.getElapsedTime(), this->threadNumber_);
619
620 return 0;
621}
622
623// ---------------- //
624// 1-Separatrices //
625// ---------------- //
626
627template <typename triangulationType>
629 const std::vector<SimplexId> &saddles,
630 std::vector<Separatrix> &separatrices,
631 const triangulationType &triangulation) const {
632
633 const SimplexId numberOfSaddles = saddles.size();
634
635 // only 2 descending separatrices per 1-saddle
636 const SimplexId numberOfSeparatrices = 2 * numberOfSaddles;
637 separatrices.resize(numberOfSeparatrices);
638
639 // apriori: by default construction, the separatrices are not valid
640#ifdef TTK_ENABLE_OPENMP
641#pragma omp parallel for num_threads(threadNumber_)
642#endif
643 for(SimplexId i = 0; i < numberOfSaddles; ++i) {
644 const Cell saddle{1, saddles[i]};
645
646 // add descending vpaths
647 {
648 const Cell &saddle1 = saddle;
649
650 for(int j = 0; j < 2; ++j) {
651 SimplexId vertexId;
652 triangulation.getEdgeVertex(saddle1.id_, j, vertexId);
653
654 std::vector<Cell> vpath;
655 vpath.push_back(saddle1);
656 discreteGradient_.getDescendingPath(
657 Cell(0, vertexId), vpath, triangulation);
658
659 const Cell &lastCell = vpath.back();
660 if(lastCell.dim_ == 0 and discreteGradient_.isCellCritical(lastCell)) {
661 separatrices[2 * i + j].source_ = saddle;
662 separatrices[2 * i + j].destination_ = lastCell;
663 separatrices[2 * i + j].geometry_ = std::move(vpath);
664 }
665 }
666 }
667 }
668
669 return 0;
670}
671
672template <typename triangulationType>
674 const std::vector<SimplexId> &saddles,
675 std::vector<Separatrix> &separatrices,
676 const triangulationType &triangulation) const {
677
678 if(saddles.empty())
679 return 0;
680
681 const auto dim{triangulation.getDimensionality()};
682
683 // Triangulation method pointers for 3D
684 auto getFaceStarNumber = &triangulationType::getTriangleStarNumber;
685 auto getFaceStar = &triangulationType::getTriangleStar;
686 if(dim == 2) {
687 // Triangulation method pointers for 2D
688 getFaceStarNumber = &triangulationType::getEdgeStarNumber;
689 getFaceStar = &triangulationType::getEdgeStar;
690 }
691
692 const SimplexId numberOfSaddles = saddles.size();
693
694 std::vector<std::vector<Separatrix>> sepsPerSaddle(numberOfSaddles);
695
696 // apriori: by default construction, the separatrices are not valid
697#ifdef TTK_ENABLE_OPENMP
698#pragma omp parallel for num_threads(threadNumber_)
699#endif
700 for(SimplexId i = 0; i < numberOfSaddles; ++i) {
701 const Cell saddle{dim - 1, saddles[i]};
702
703 // add ascending vpaths
704 const auto starNumber{(triangulation.*getFaceStarNumber)(saddle.id_)};
705 for(SimplexId j = 0; j < starNumber; ++j) {
706
707 SimplexId sId{};
708 (triangulation.*getFaceStar)(saddle.id_, j, sId);
709
710 std::vector<Cell> vpath{saddle};
711 discreteGradient_.getAscendingPath(Cell(dim, sId), vpath, triangulation);
712
713 const Cell &lastCell = vpath.back();
714 if(lastCell.dim_ == dim and discreteGradient_.isCellCritical(lastCell)) {
715 sepsPerSaddle[i].emplace_back();
716 sepsPerSaddle[i].back().source_ = saddle;
717 sepsPerSaddle[i].back().destination_ = lastCell;
718 sepsPerSaddle[i].back().geometry_ = std::move(vpath);
719 }
720 }
721 }
722
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 if(saddles2.empty())
737 return 0;
738
739 const auto nTriangles = triangulation.getNumberOfTriangles();
740 // visited triangles (one vector per thread)
741 std::vector<bool> isVisited(nTriangles, false);
742 std::vector<SimplexId> visitedTriangles{};
743
744 using Vpath = std::vector<Cell>;
745
746 const auto dim{triangulation.getDimensionality()};
747
748 std::vector<std::vector<Separatrix>> sepsByThread(saddles2.size());
749 std::vector<SimplexId> saddles1{};
750
751#ifdef TTK_ENABLE_OPENMP
752#pragma omp parallel for num_threads(threadNumber_) schedule(dynamic) \
753 firstprivate(isVisited, visitedTriangles, saddles1)
754#endif // TTK_ENABLE_OPENMP
755 for(size_t i = 0; i < saddles2.size(); ++i) {
756 const Cell s2{dim - 1, saddles2[i]};
757
758 VisitedMask mask{isVisited, visitedTriangles};
759 discreteGradient_.getDescendingWall(
760 s2, mask, triangulation, nullptr, &saddles1);
761
762 for(const auto saddle1Id : saddles1) {
763 const Cell s1{1, saddle1Id};
764
765 Vpath vpath;
766 const bool isMultiConnected
767 = discreteGradient_.getAscendingPathThroughWall(
768 s1, s2, isVisited, &vpath, triangulation);
769
770 if(vpath.empty()) {
771 // safety, should be unreachable
772 continue;
773 }
774 const auto &last = vpath.back();
775
776 if(!isMultiConnected && last.dim_ == s2.dim_ && last.id_ == s2.id_) {
777 sepsByThread[i].emplace_back();
778 sepsByThread[i].back().source_ = s1;
779 sepsByThread[i].back().destination_ = s2;
780 sepsByThread[i].back().geometry_ = std::move(vpath);
781 }
782 }
783 }
784
785 this->flattenSeparatricesVectors(sepsByThread);
786
787 separatrices = std::move(sepsByThread[0]);
788
789 return 0;
790}
791
792template <typename triangulationType>
794 Output1Separatrices &outSeps1,
795 const std::vector<Separatrix> &separatrices,
796 const SimplexId *const offsets,
797 const triangulationType &triangulation) const {
798
799 auto &separatrixFunctionMaxima = outSeps1.cl.sepFuncMaxId_;
800 auto &separatrixFunctionMinima = outSeps1.cl.sepFuncMinId_;
801
802 // max existing separatrix id + 1 or 0
803 const SimplexId separatrixId
804 = !outSeps1.cl.separatrixIds_.empty()
805 ? *std::max_element(outSeps1.cl.separatrixIds_.begin(),
806 outSeps1.cl.separatrixIds_.end())
807 + 1
808 : 0;
809
810 // total number of separatrices points
811 auto npoints{static_cast<size_t>(outSeps1.pt.numberOfPoints_)};
812 // total number of separatrices cells
813 auto ncells{static_cast<size_t>(outSeps1.cl.numberOfCells_)};
814 // points beginning id for each separatrix geometry
815 std::vector<size_t> geomPointsBegId{npoints};
816 // cells beginning id for each separatrix geometry
817 std::vector<size_t> geomCellsBegId{ncells};
818
819 // count total number of points and cells, flatten geometryId loops
820 for(size_t i = 0; i < separatrices.size(); ++i) {
821 const auto &sep = separatrices[i];
822 const auto sepSize = sep.geometry_.size();
823 npoints += sepSize;
824 ncells += sepSize - 1;
825 geomPointsBegId.emplace_back(npoints);
826 geomCellsBegId.emplace_back(ncells);
827 }
828
829 const int dimensionality = triangulation.getCellVertexNumber(0) - 1;
830
831 // resize arrays
832 outSeps1.pt.points_.resize(3 * npoints);
833 auto &points = outSeps1.pt.points_;
834 outSeps1.cl.connectivity_.resize(2 * ncells);
835 auto &cellsConn = outSeps1.cl.connectivity_;
836 outSeps1.pt.smoothingMask_.resize(npoints);
837 outSeps1.pt.cellDimensions_.resize(npoints);
838 outSeps1.pt.cellIds_.resize(npoints);
839 outSeps1.cl.sourceIds_.resize(ncells);
840 outSeps1.cl.destinationIds_.resize(ncells);
841 outSeps1.cl.separatrixIds_.resize(ncells);
842 outSeps1.cl.separatrixTypes_.resize(ncells);
843 separatrixFunctionMaxima.resize(separatrixId + separatrices.size());
844 separatrixFunctionMinima.resize(separatrixId + separatrices.size());
845 outSeps1.cl.isOnBoundary_.resize(ncells);
846
847#ifdef TTK_ENABLE_OPENMP
848#pragma omp parallel for num_threads(threadNumber_) schedule(dynamic)
849#endif // TTK_ENABLE_OPENMP
850 for(size_t i = 0; i < separatrices.size(); ++i) {
851 const auto &sep = separatrices[i];
852 const auto &sepGeom = sep.geometry_;
853 const auto sepId = separatrixId + i;
854 // saddle (asc/desc sep) or saddle1 (saddle connector)
855 const dcg::Cell &src = sep.source_;
856 // extremum (asc/desc sep) or saddle2 (saddle connector)
857 const dcg::Cell &dst = sep.destination_;
858
859 // get separatrix type
860 const auto saddleConnector
861 = dimensionality == 3 && src.dim_ == 1 && dst.dim_ == 2;
862 const char sepType
863 = saddleConnector ? 1 : std::min(dst.dim_, dimensionality - 1);
864
865 // compute separatrix function diff
866 const auto vertsOrder = [offsets](const SimplexId a, const SimplexId b) {
867 return offsets[a] < offsets[b];
868 };
869 const std::array<SimplexId, 2> gVerts{
870 discreteGradient_.getCellGreaterVertex(src, triangulation),
871 discreteGradient_.getCellGreaterVertex(dst, triangulation)};
872 const auto sepFuncMax
873 = *std::max_element(gVerts.begin(), gVerts.end(), vertsOrder);
874 const std::array<SimplexId, 2> lVerts{
875 discreteGradient_.getCellLowerVertex(src, triangulation),
876 discreteGradient_.getCellLowerVertex(dst, triangulation)};
877 const auto sepFuncMin
878 = *std::min_element(lVerts.begin(), lVerts.end(), vertsOrder);
879 separatrixFunctionMaxima[sepId] = sepFuncMax;
880 separatrixFunctionMinima[sepId] = sepFuncMin;
881
882 // get boundary condition
883 const auto onBoundary
884 = static_cast<char>(discreteGradient_.isBoundary(src, triangulation))
885 + static_cast<char>(discreteGradient_.isBoundary(dst, triangulation));
886
887 for(size_t j = 0; j < sepGeom.size(); ++j) {
888 const auto &cell = sepGeom[j];
889 std::array<float, 3> pt{};
890 triangulation.getCellIncenter(cell.id_, cell.dim_, pt.data());
891
892 // index of current point in point data arrays
893 const auto k = geomPointsBegId[i] + j;
894
895 points[3 * k + 0] = pt[0];
896 points[3 * k + 1] = pt[1];
897 points[3 * k + 2] = pt[2];
898
899 outSeps1.pt.smoothingMask_[k]
900 = (j == 0 || j == sepGeom.size() - 1) ? 0 : 1;
901 outSeps1.pt.cellDimensions_[k] = cell.dim_;
902 outSeps1.pt.cellIds_[k] = cell.id_;
903
904 // skip filling cell data for first geometry point
905 if(j == 0)
906 continue;
907
908 // index of current cell in cell data arrays
909 const auto l = geomCellsBegId[i] + j - 1;
910
911 cellsConn[2 * l + 0] = k - 1;
912 cellsConn[2 * l + 1] = k;
913
914 outSeps1.cl.sourceIds_[l] = src.id_;
915 outSeps1.cl.destinationIds_[l] = dst.id_;
916 outSeps1.cl.separatrixIds_[l] = sepId;
917 outSeps1.cl.separatrixTypes_[l] = sepType;
918 outSeps1.cl.isOnBoundary_[l] = onBoundary;
919 }
920 }
921
922 // update pointers
923 outSeps1.pt.numberOfPoints_ = npoints;
924 outSeps1.cl.numberOfCells_ = ncells;
925
926 return 0;
927}
928
929// ---------------- //
930// 2-Separatrices //
931// ---------------- //
932
933template <typename triangulationType>
935 const std::vector<SimplexId> &saddles1,
936 std::vector<Separatrix> &separatrices,
937 std::vector<std::vector<SimplexId>> &separatricesSaddles,
938 const triangulationType &triangulation) const {
939 const Cell emptyCell;
940
941 const SimplexId numberOfSaddles = saddles1.size();
942
943 // estimation of the number of separatrices, apriori : numberOfWalls =
944 // numberOfSaddles
945 const SimplexId numberOfSeparatrices = numberOfSaddles;
946 separatrices.resize(numberOfSeparatrices);
947 separatricesSaddles.resize(numberOfSeparatrices);
948
949 const auto nEdges = triangulation.getNumberOfEdges();
950 std::vector<bool> isVisited(nEdges, false);
951 std::vector<SimplexId> visitedEdges{};
952
953 // apriori: by default construction, the separatrices are not valid
954#ifdef TTK_ENABLE_OPENMP
955#pragma omp parallel for num_threads(threadNumber_) schedule(dynamic) \
956 firstprivate(isVisited, visitedEdges)
957#endif // TTK_ENABLE_OPENMP
958 for(SimplexId i = 0; i < numberOfSaddles; ++i) {
959 const Cell saddle1{1, saddles1[i]};
960
961 std::vector<Cell> wall;
962 VisitedMask mask{isVisited, visitedEdges};
963 discreteGradient_.getAscendingWall(
964 saddle1, mask, triangulation, &wall, &separatricesSaddles[i]);
965
966 separatrices[i].source_ = saddle1;
967 separatrices[i].destination_ = emptyCell;
968 separatrices[i].geometry_ = std::move(wall);
969 }
970
971 return 0;
972}
973
974template <typename triangulationType>
976 const std::vector<SimplexId> &saddles2,
977 std::vector<Separatrix> &separatrices,
978 std::vector<std::vector<SimplexId>> &separatricesSaddles,
979 const triangulationType &triangulation) const {
980 const Cell emptyCell;
981
982 const SimplexId numberOfSaddles = saddles2.size();
983
984 // estimation of the number of separatrices, apriori : numberOfWalls =
985 // numberOfSaddles
986 const SimplexId numberOfSeparatrices = numberOfSaddles;
987 separatrices.resize(numberOfSeparatrices);
988 separatricesSaddles.resize(numberOfSeparatrices);
989
990 const auto nTriangles = triangulation.getNumberOfTriangles();
991 std::vector<bool> isVisited(nTriangles, false);
992 std::vector<SimplexId> visitedTriangles{};
993
994 const auto dim{triangulation.getDimensionality()};
995
996 // apriori: by default construction, the separatrices are not valid
997#ifdef TTK_ENABLE_OPENMP
998#pragma omp parallel for num_threads(threadNumber_) schedule(dynamic) \
999 firstprivate(isVisited, visitedTriangles)
1000#endif // TTK_ENABLE_OPENMP
1001 for(SimplexId i = 0; i < numberOfSaddles; ++i) {
1002 const Cell saddle2{dim - 1, saddles2[i]};
1003
1004 std::vector<Cell> wall;
1005 VisitedMask mask{isVisited, visitedTriangles};
1006 discreteGradient_.getDescendingWall(
1007 saddle2, mask, triangulation, &wall, &separatricesSaddles[i]);
1008
1009 separatrices[i].source_ = saddle2;
1010 separatrices[i].destination_ = emptyCell;
1011 separatrices[i].geometry_ = std::move(wall);
1012 }
1013
1014 return 0;
1015}
1016
1017template <typename triangulationType>
1019 const SimplexId edgeId,
1020 SimplexId *const polygon,
1021 const size_t polSize,
1022 const triangulationType &triangulation) const {
1023
1024 for(size_t i = 0; i < polSize; ++i) {
1025 SimplexId starId;
1026 triangulation.getEdgeStar(edgeId, i, starId);
1027 polygon[i] = starId;
1028 }
1029
1030 return 0;
1031}
1032
1033template <typename triangulationType>
1035 SimplexId *const polygon,
1036 const size_t polSize,
1037 const triangulationType &triangulation) const {
1038
1039 for(size_t i = 1; i < polSize; ++i) {
1040
1041 // find polygon[i - 1] neighboring tetra in polygon[i..]
1042 bool isFound = false;
1043 size_t j = i;
1044 for(; j < polSize; ++j) {
1045 // check if current is the neighbor
1046 for(SimplexId k = 0;
1047 k < triangulation.getCellNeighborNumber(polygon[i - 1]); ++k) {
1048 SimplexId neighborId{};
1049 triangulation.getCellNeighbor(polygon[i - 1], k, neighborId);
1050 if(neighborId == polygon[j]) {
1051 isFound = true;
1052 break;
1053 }
1054 }
1055 if(isFound)
1056 break;
1057 }
1058
1059 // place polygon[j] next to polygon[i - 1]
1060 if(isFound) {
1061 std::swap(polygon[j], polygon[i]);
1062 }
1063 }
1064
1065 return 0;
1066}
1067
1068template <typename triangulationType>
1070 Output2Separatrices &outSeps2,
1071 const std::vector<Separatrix> &separatrices,
1072 const std::vector<std::vector<SimplexId>> &separatricesSaddles,
1073 const SimplexId *const offsets,
1074 const triangulationType &triangulation) const {
1075
1076 auto &separatrixFunctionMaxima = outSeps2.cl.sepFuncMaxId_;
1077 auto &separatrixFunctionMinima = outSeps2.cl.sepFuncMinId_;
1078
1079 // max existing separatrix id + 1 or 0 if no previous separatrices
1080 const SimplexId separatrixId
1081 = !outSeps2.cl.separatrixIds_.empty()
1082 ? *std::max_element(outSeps2.cl.separatrixIds_.begin(),
1083 outSeps2.cl.separatrixIds_.end())
1084 + 1
1085 : 0;
1086
1087 // total number of separatrices points
1088 auto npoints{static_cast<size_t>(outSeps2.pt.numberOfPoints_)};
1089 // total number of separatrices cells
1090 auto ncells{static_cast<size_t>(outSeps2.cl.numberOfCells_)};
1091 // old number of separatrices cells
1092 const auto noldcells{ncells};
1093 // index of last vertex of last old cell + 1
1094 const auto firstCellId{outSeps2.cl.connectivity_.size()};
1095 // cells beginning id for each separatrix geometry
1096 std::vector<size_t> geomCellsBegId{ncells};
1097
1098 // count total number of cells, flatten geometryId loops
1099 for(size_t i = 0; i < separatrices.size(); ++i) {
1100 const auto &sep = separatrices[i];
1101 ncells += sep.geometry_.size();
1102 geomCellsBegId.emplace_back(ncells);
1103 }
1104
1105 // store the separatrices info (one per separatrix)
1106 std::vector<SimplexId> sepSourceIds(separatrices.size());
1107 std::vector<SimplexId> sepIds(separatrices.size());
1108 std::vector<char> sepOnBoundary(separatrices.size());
1109 separatrixFunctionMaxima.resize(separatrixId + separatrices.size());
1110 separatrixFunctionMinima.resize(separatrixId + separatrices.size());
1111 // store the polygonal cells tetras SimplexId
1112 std::vector<SimplexId> polygonNTetras(ncells - noldcells);
1113 std::vector<SimplexId> polygonEdgeIds(ncells - noldcells);
1114 std::vector<SimplexId> polygonSepInfosIds(ncells - noldcells);
1115
1116#ifdef TTK_ENABLE_OPENMP
1117#pragma omp parallel for num_threads(threadNumber_) schedule(dynamic)
1118#endif // TTK_ENABLE_OPENMP
1119 for(size_t i = 0; i < separatrices.size(); ++i) {
1120 const auto &sep = separatrices[i];
1121 const auto &sepGeom = sep.geometry_;
1122 const auto &sepSaddles = separatricesSaddles[i];
1123 const auto sepId = separatrixId + i;
1124 const dcg::Cell &src = sep.source_; // saddle1
1125
1126 // compute separatrix function diff
1127 const auto sepFuncMin
1128 = discreteGradient_.getCellLowerVertex(src, triangulation);
1129 SimplexId sepFuncMax{};
1130 if(!sepSaddles.empty()) {
1131 // find minimum vertex on the critical triangles of the 2-separatrix
1132 const auto maxId = *std::max_element(
1133 sepSaddles.begin(), sepSaddles.end(),
1134 [&triangulation, offsets, this](const SimplexId a, const SimplexId b) {
1135 return offsets[discreteGradient_.getCellGreaterVertex(
1136 Cell{2, a}, triangulation)]
1137 < offsets[discreteGradient_.getCellGreaterVertex(
1138 Cell{2, b}, triangulation)];
1139 });
1140 sepFuncMax
1141 = discreteGradient_.getCellGreaterVertex(Cell{2, maxId}, triangulation);
1142 } else {
1143 // find maximum vertex by iterating over all the edges in the
1144 // 2-separatrix
1145 const auto maxId = *std::max_element(
1146 sepGeom.begin(), sepGeom.end(),
1147 [&triangulation, offsets, this](const Cell &a, const Cell &b) {
1148 return offsets[discreteGradient_.getCellGreaterVertex(
1149 a, triangulation)]
1150 < offsets[discreteGradient_.getCellGreaterVertex(
1151 b, triangulation)];
1152 });
1153 sepFuncMax = discreteGradient_.getCellGreaterVertex(maxId, triangulation);
1154 }
1155
1156 // get boundary condition
1157 const char onBoundary
1158 = (sepSaddles.empty()
1159 ? 0
1160 : std::count_if(sepSaddles.begin(), sepSaddles.end(),
1161 [&triangulation](const SimplexId a) {
1162 return triangulation.isTriangleOnBoundary(a);
1163 }))
1164 + triangulation.isEdgeOnBoundary(src.id_);
1165
1166 sepIds[i] = sepId;
1167 sepSourceIds[i] = src.id_;
1168 separatrixFunctionMaxima[sepId] = sepFuncMax;
1169 separatrixFunctionMinima[sepId] = sepFuncMin;
1170 sepOnBoundary[i] = onBoundary;
1171
1172 for(size_t j = 0; j < sepGeom.size(); ++j) {
1173 const auto &cell = sepGeom[j];
1174 // index of current cell in cell data arrays
1175 const auto k = geomCellsBegId[i] + j - noldcells;
1176
1177 polygonNTetras[k] = triangulation.getEdgeStarNumber(cell.id_);
1178
1179 if(polygonNTetras[k] > 2) {
1180 polygonEdgeIds[k] = cell.id_;
1181 polygonSepInfosIds[k] = i;
1182 }
1183 }
1184 }
1185
1186 // indices of valid polygon tetras
1187 std::vector<SimplexId> validTetraIds{};
1188 validTetraIds.reserve(polygonNTetras.size());
1189
1190 for(size_t i = 0; i < polygonNTetras.size(); ++i) {
1191 if(polygonNTetras[i] > 2) {
1192 validTetraIds.emplace_back(i);
1193 }
1194 }
1195
1196 // count number of valid new cells and new points
1197 size_t nnewpoints{};
1198 std::vector<SimplexId> pointsPerCell(validTetraIds.size() + 1);
1199 for(size_t i = 0; i < validTetraIds.size(); ++i) {
1200 nnewpoints += polygonNTetras[validTetraIds[i]];
1201 pointsPerCell[i + 1] = nnewpoints;
1202 }
1203
1204 // resize connectivity array
1205 outSeps2.cl.connectivity_.resize(firstCellId + nnewpoints);
1206 auto cellsConn = &outSeps2.cl.connectivity_[firstCellId];
1207 // copy of cell connectivity array (for removing duplicates vertices)
1208 std::vector<SimplexId> cellVertsIds(nnewpoints);
1209
1210#ifdef TTK_ENABLE_OPENMP
1211#pragma omp parallel for num_threads(threadNumber_)
1212#endif // TTK_ENABLE_OPENMP
1213 for(size_t i = 0; i < validTetraIds.size(); ++i) {
1214 const auto k = validTetraIds[i];
1215
1216 // get tetras in edge star
1217 getDualPolygon(polygonEdgeIds[k], &cellVertsIds[pointsPerCell[i]],
1218 polygonNTetras[k], triangulation);
1219 // sort tetras (in-place)
1220 sortDualPolygonVertices(
1221 &cellVertsIds[pointsPerCell[i]], polygonNTetras[k], triangulation);
1222
1223 for(SimplexId j = 0; j < polygonNTetras[k]; ++j) {
1224 cellsConn[pointsPerCell[i] + j] = cellVertsIds[pointsPerCell[i] + j];
1225 }
1226 }
1227
1228 TTK_PSORT(this->threadNumber_, cellVertsIds.begin(), cellVertsIds.end());
1229 const auto last = std::unique(cellVertsIds.begin(), cellVertsIds.end());
1230 cellVertsIds.erase(last, cellVertsIds.end());
1231
1232 // vertex Id to index in points array
1233 std::vector<SimplexId> vertId2PointsId(triangulation.getNumberOfCells());
1234
1235 const auto noldpoints{npoints};
1236 npoints += cellVertsIds.size();
1237 ncells = noldcells + validTetraIds.size();
1238
1239 // resize arrays
1240 outSeps2.pt.points_.resize(3 * npoints);
1241 auto points = &outSeps2.pt.points_[3 * noldpoints];
1242 outSeps2.cl.offsets_.resize(ncells + 1);
1243 outSeps2.cl.offsets_[0] = 0;
1244 auto cellsOff = &outSeps2.cl.offsets_[noldcells];
1245 outSeps2.cl.sourceIds_.resize(ncells);
1246 outSeps2.cl.separatrixIds_.resize(ncells);
1247 outSeps2.cl.separatrixTypes_.resize(ncells);
1248 outSeps2.cl.isOnBoundary_.resize(ncells);
1249
1250#ifdef TTK_ENABLE_OPENMP
1251#pragma omp parallel for num_threads(threadNumber_)
1252#endif // TTK_ENABLE_OPENMP
1253 for(size_t i = 0; i < cellVertsIds.size(); ++i) {
1254 // vertex 3D coords
1255 triangulation.getTetraIncenter(cellVertsIds[i], &points[3 * i]);
1256 // vertex index in cellVertsIds array (do not forget offset)
1257 vertId2PointsId[cellVertsIds[i]] = i + noldpoints;
1258 }
1259
1260#ifdef TTK_ENABLE_OPENMP
1261#pragma omp parallel for num_threads(threadNumber_)
1262#endif // TTK_ENABLE_OPENMP
1263 for(size_t i = 0; i < validTetraIds.size(); ++i) {
1264 const auto m = validTetraIds[i];
1265 const auto k = pointsPerCell[i];
1266 for(SimplexId j = 0; j < polygonNTetras[m]; ++j) {
1267 cellsConn[k + j] = vertId2PointsId[cellsConn[k + j]];
1268 }
1269 const auto l = i + noldcells;
1270 const auto n = polygonSepInfosIds[m];
1271 outSeps2.cl.sourceIds_[l] = sepSourceIds[n];
1272 outSeps2.cl.separatrixIds_[l] = sepIds[n];
1273 outSeps2.cl.separatrixTypes_[l] = 1;
1274 outSeps2.cl.isOnBoundary_[l] = sepOnBoundary[n];
1275 }
1276
1277 for(size_t i = 0; i < validTetraIds.size(); ++i) {
1278 // fill offsets sequentially (due to iteration dependencies)
1279 cellsOff[i + 1] = cellsOff[i] + polygonNTetras[validTetraIds[i]];
1280 }
1281
1282 outSeps2.pt.numberOfPoints_ = npoints;
1283 outSeps2.cl.numberOfCells_ = ncells;
1284
1285 return 0;
1286}
1287
1288template <typename triangulationType>
1290 Output2Separatrices &outSeps2,
1291 const std::vector<Separatrix> &separatrices,
1292 const std::vector<std::vector<SimplexId>> &separatricesSaddles,
1293 const SimplexId *const offsets,
1294 const triangulationType &triangulation) const {
1295
1296 auto &separatrixFunctionMaxima = outSeps2.cl.sepFuncMaxId_;
1297 auto &separatrixFunctionMinima = outSeps2.cl.sepFuncMinId_;
1298
1299 // max existing separatrix id + 1 or 0 if no previous separatrices
1300 const SimplexId separatrixId
1301 = !outSeps2.cl.separatrixIds_.empty()
1302 ? *std::max_element(outSeps2.cl.separatrixIds_.begin(),
1303 outSeps2.cl.separatrixIds_.end())
1304 + 1
1305 : 0;
1306
1307 // total number of separatrices points
1308 auto npoints{static_cast<size_t>(outSeps2.pt.numberOfPoints_)};
1309 // total number of separatrices cells
1310 auto ncells{static_cast<size_t>(outSeps2.cl.numberOfCells_)};
1311 // old number of separatrices cells
1312 const auto noldcells{ncells};
1313 // index of last vertex of last old cell + 1
1314 const auto firstCellId{outSeps2.cl.connectivity_.size()};
1315
1316 // cells beginning id for each separatrix geometry
1317 std::vector<size_t> geomCellsBegId{ncells};
1318
1319 // count total number of cells, flatten geometryId loops
1320 for(size_t i = 0; i < separatrices.size(); ++i) {
1321 const auto &sep = separatrices[i];
1322 ncells += sep.geometry_.size();
1323 geomCellsBegId.emplace_back(ncells);
1324 }
1325
1326 // resize arrays
1327 outSeps2.cl.offsets_.resize(ncells + 1);
1328 outSeps2.cl.offsets_[0] = 0;
1329 outSeps2.cl.connectivity_.resize(
1330 firstCellId + 3 * (ncells - noldcells)); // triangles cells
1331 auto cellsOff = &outSeps2.cl.offsets_[noldcells];
1332 auto cellsConn = &outSeps2.cl.connectivity_[firstCellId];
1333 outSeps2.cl.sourceIds_.resize(ncells);
1334 outSeps2.cl.separatrixIds_.resize(ncells);
1335 outSeps2.cl.separatrixTypes_.resize(ncells);
1336 separatrixFunctionMaxima.resize(separatrixId + separatrices.size());
1337 separatrixFunctionMinima.resize(separatrixId + separatrices.size());
1338 outSeps2.cl.isOnBoundary_.resize(ncells);
1339
1340 // store the cells/triangles vertices vertexId
1341 std::vector<SimplexId> cellVertsIds(3 * (ncells - noldcells));
1342
1343#ifdef TTK_ENABLE_OPENMP
1344#pragma omp parallel for num_threads(threadNumber_)
1345#endif // TTK_ENABLE_OPENMP
1346 for(size_t i = 0; i < separatrices.size(); ++i) {
1347 const auto &sep = separatrices[i];
1348 const auto &sepGeom = sep.geometry_;
1349 const auto &sepSaddles = separatricesSaddles[i];
1350 const auto sepId = separatrixId + i;
1351 const dcg::Cell &src = sep.source_; // saddle2
1352 const char sepType = 2;
1353
1354 // compute separatrix function diff
1355 const auto sepFuncMax
1356 = discreteGradient_.getCellGreaterVertex(src, triangulation);
1357 SimplexId sepFuncMin{};
1358 if(!sepSaddles.empty()) {
1359 // find minimum vertex on the critical edges of the 2-separatrix
1360 const auto minId = *std::min_element(
1361 sepSaddles.begin(), sepSaddles.end(),
1362 [&triangulation, offsets, this](const SimplexId a, const SimplexId b) {
1363 return offsets[discreteGradient_.getCellLowerVertex(
1364 Cell{1, a}, triangulation)]
1365 < offsets[discreteGradient_.getCellLowerVertex(
1366 Cell{1, b}, triangulation)];
1367 });
1368 sepFuncMin
1369 = discreteGradient_.getCellLowerVertex(Cell{1, minId}, triangulation);
1370 } else {
1371 // find minimum vertex by iterating over all the triangles in the
1372 // 2-separatrix
1373 const auto minId = *std::min_element(
1374 sepGeom.begin(), sepGeom.end(),
1375 [&triangulation, offsets, this](const Cell &a, const Cell &b) {
1376 return offsets[discreteGradient_.getCellLowerVertex(a, triangulation)]
1377 < offsets[discreteGradient_.getCellLowerVertex(
1378 b, triangulation)];
1379 });
1380 sepFuncMin = discreteGradient_.getCellLowerVertex(minId, triangulation);
1381 }
1382 separatrixFunctionMaxima[sepId] = sepFuncMax;
1383 separatrixFunctionMinima[sepId] = sepFuncMin;
1384
1385 // get boundary condition
1386 const char onBoundary
1387 = (sepSaddles.empty()
1388 ? 0
1389 : std::count_if(sepSaddles.begin(), sepSaddles.end(),
1390 [&triangulation](const SimplexId a) {
1391 return triangulation.isEdgeOnBoundary(a);
1392 }))
1393 + triangulation.isTriangleOnBoundary(src.id_);
1394
1395 for(size_t j = 0; j < sepGeom.size(); ++j) {
1396 const auto &cell = sepGeom[j];
1397
1398 // first store the SimplexId of the cell/triangle vertices
1399 SimplexId v0{}, v1{}, v2{};
1400 triangulation.getTriangleVertex(cell.id_, 0, v0);
1401 triangulation.getTriangleVertex(cell.id_, 1, v1);
1402 triangulation.getTriangleVertex(cell.id_, 2, v2);
1403
1404 // index of current cell in cell data arrays
1405 const auto l = geomCellsBegId[i] + j;
1406 // index of current cell among all new cells
1407 const auto m = l - noldcells;
1408
1409 cellsConn[3 * m + 0] = v0;
1410 cellsConn[3 * m + 1] = v1;
1411 cellsConn[3 * m + 2] = v2;
1412 cellVertsIds[3 * m + 0] = v0;
1413 cellVertsIds[3 * m + 1] = v1;
1414 cellVertsIds[3 * m + 2] = v2;
1415
1416 outSeps2.cl.sourceIds_[l] = src.id_;
1417 outSeps2.cl.separatrixIds_[l] = sepId;
1418 outSeps2.cl.separatrixTypes_[l] = sepType;
1419 outSeps2.cl.isOnBoundary_[l] = onBoundary;
1420 }
1421 }
1422
1423 // reduce the cell vertices ids
1424 // (cells are triangles sharing two vertices)
1425 TTK_PSORT(this->threadNumber_, cellVertsIds.begin(), cellVertsIds.end());
1426 const auto last = std::unique(cellVertsIds.begin(), cellVertsIds.end());
1427 cellVertsIds.erase(last, cellVertsIds.end());
1428
1429 // vertex Id to index in points array
1430 std::vector<size_t> vertId2PointsId(triangulation.getNumberOfVertices());
1431
1432 const auto noldpoints{npoints};
1433 npoints += cellVertsIds.size();
1434 outSeps2.pt.points_.resize(3 * npoints);
1435 auto points = &outSeps2.pt.points_[3 * noldpoints];
1436
1437#ifdef TTK_ENABLE_OPENMP
1438#pragma omp parallel for num_threads(threadNumber_)
1439#endif // TTK_ENABLE_OPENMP
1440 for(size_t i = 0; i < cellVertsIds.size(); ++i) {
1441 // vertex 3D coords
1442 triangulation.getVertexPoint(
1443 cellVertsIds[i], points[3 * i + 0], points[3 * i + 1], points[3 * i + 2]);
1444 // vertex index in cellVertsIds array (do not forget offset)
1445 vertId2PointsId[cellVertsIds[i]] = i + noldpoints;
1446 }
1447
1448 const auto lastOffset = noldcells == 0 ? 0 : cellsOff[-1];
1449
1450#ifdef TTK_ENABLE_OPENMP
1451#pragma omp parallel for num_threads(threadNumber_)
1452#endif // TTK_ENABLE_OPENMP
1453 for(size_t i = 0; i < ncells - noldcells; ++i) {
1454 cellsOff[i] = 3 * i + lastOffset;
1455 cellsConn[3 * i + 0] = vertId2PointsId[cellsConn[3 * i + 0]];
1456 cellsConn[3 * i + 1] = vertId2PointsId[cellsConn[3 * i + 1]];
1457 cellsConn[3 * i + 2] = vertId2PointsId[cellsConn[3 * i + 2]];
1458 }
1459
1460 cellsOff[ncells - noldcells] = cellsOff[ncells - noldcells - 1] + 3;
1461
1462 outSeps2.pt.numberOfPoints_ = npoints;
1463 outSeps2.cl.numberOfCells_ = ncells;
1464
1465 return 0;
1466}
1467
1468// ---------------- //
1469// Segmentation //
1470// ---------------- //
1471
1472template <typename triangulationType>
1474 const std::vector<SimplexId> &maxima,
1475 SimplexId *const morseSmaleManifold,
1476 const triangulationType &triangulation) const {
1477
1478 if(morseSmaleManifold == nullptr) {
1479 this->printErr("Could not compute ascending segmentation");
1480 return 1;
1481 }
1482
1483 Timer tm{};
1484
1485 const auto nVerts{triangulation.getNumberOfVertices()};
1486 std::fill(morseSmaleManifold, morseSmaleManifold + nVerts, -1);
1487 if(maxima.empty()) {
1488 // shortcut for elevation
1489 return 0;
1490 }
1491
1492 const auto nCells{triangulation.getNumberOfCells()};
1493 std::vector<SimplexId> morseSmaleManifoldOnCells(nCells, -1);
1494
1495 size_t nMax{};
1496 for(const auto &id : maxima) {
1497 // mark the maxima
1498 morseSmaleManifoldOnCells[id] = nMax++;
1499 }
1500
1501 const auto dim{triangulation.getDimensionality()};
1502
1503 // Triangulation method pointers for 3D
1504 auto getFaceStarNumber = &triangulationType::getTriangleStarNumber;
1505 auto getFaceStar = &triangulationType::getTriangleStar;
1506 if(dim == 2) {
1507 // Triangulation method pointers for 2D
1508 getFaceStarNumber = &triangulationType::getEdgeStarNumber;
1509 getFaceStar = &triangulationType::getEdgeStar;
1510 } else if(dim == 1) {
1511 // Triangulation method pointers for 1D
1512 getFaceStarNumber = &triangulationType::getVertexStarNumber;
1513 getFaceStar = &triangulationType::getVertexStar;
1514 }
1515
1516 // cells visited during the propagation alongside one integral line
1517 std::vector<SimplexId> visited{};
1518 // all marked cells
1519 std::vector<uint8_t> isMarked(nCells, 0);
1520
1521#ifdef TTK_ENABLE_OPENMP
1522#pragma omp parallel for num_threads(threadNumber_) firstprivate(visited)
1523#endif // TTK_ENABLE_OPENMP
1524 for(SimplexId i = 0; i < nCells; ++i) {
1525 if(isMarked[i] == 1) {
1526 continue;
1527 }
1528 visited.clear();
1529 auto curr{i};
1530 while(morseSmaleManifoldOnCells[curr] == -1) {
1531 if(isMarked[curr] == 1) {
1532 break;
1533 }
1534 // follow a V-path till an already marked cell is reached
1535 const auto paired{this->discreteGradient_.getPairedCell(
1536 Cell{dim, curr}, triangulation, true)};
1537 SimplexId next{curr};
1538 const auto nStars{(triangulation.*getFaceStarNumber)(paired)};
1539 for(SimplexId j = 0; j < nStars; ++j) {
1540 (triangulation.*getFaceStar)(paired, j, next);
1541 // get the first cell != curr (what of non-manifold datasets?)
1542 if(next != curr) {
1543 break;
1544 }
1545 }
1546 visited.emplace_back(curr);
1547 if(next == curr) {
1548 // on the boundary?
1549 break;
1550 }
1551 curr = next;
1552 }
1553 for(const auto el : visited) {
1554 morseSmaleManifoldOnCells[el] = morseSmaleManifoldOnCells[curr];
1555 isMarked[el] = 1;
1556 }
1557 }
1558
1559#ifdef TTK_ENABLE_OPENMP
1560#pragma omp parallel for num_threads(threadNumber_)
1561#endif // TTK_ENABLE_OPENMP
1562 for(SimplexId i = 0; i < nVerts; ++i) {
1563 if(triangulation.getVertexStarNumber(i) < 1) {
1564 // handle non-manifold datasets?
1565 continue;
1566 }
1567 SimplexId starId{};
1568 triangulation.getVertexStar(i, 0, starId);
1569 // put segmentation infos from cells to points
1570 morseSmaleManifold[i] = morseSmaleManifoldOnCells[starId];
1571 }
1572
1573 this->printMsg(" Ascending segmentation computed", 1.0, tm.getElapsedTime(),
1574 this->threadNumber_, debug::LineMode::NEW,
1576
1577 return 0;
1578}
1579
1580template <typename triangulationType>
1582 const std::vector<SimplexId> &minima,
1583 SimplexId *const morseSmaleManifold,
1584 const triangulationType &triangulation) const {
1585
1586 if(morseSmaleManifold == nullptr) {
1587 this->printErr("Could not compute descending segmentation");
1588 return 1;
1589 }
1590
1591 Timer tm{};
1592
1593 const auto nVerts{triangulation.getNumberOfVertices()};
1594
1595 if(minima.size() == 1) {
1596 // shortcut for elevation
1597 std::fill(morseSmaleManifold, morseSmaleManifold + nVerts, 0);
1598 return 0;
1599 }
1600
1601 std::fill(morseSmaleManifold, morseSmaleManifold + nVerts, -1);
1602
1603 size_t nMin{};
1604 for(const auto &cp : minima) {
1605 // mark the minima
1606 morseSmaleManifold[cp] = nMin++;
1607 }
1608
1609 std::vector<SimplexId> visited{};
1610
1611#ifdef TTK_ENABLE_OPENMP
1612#pragma omp parallel for num_threads(threadNumber_) firstprivate(visited)
1613#endif // TTK_ENABLE_OPENMP
1614 for(SimplexId i = 0; i < nVerts; ++i) {
1615 if(morseSmaleManifold[i] != -1) {
1616 continue;
1617 }
1618 visited.clear();
1619 auto curr{i};
1620 while(morseSmaleManifold[curr] == -1) {
1621 // follow a V-path till an already marked vertex is reached
1622 const auto pairedEdge{
1623 this->discreteGradient_.getPairedCell(Cell{0, curr}, triangulation)};
1624 SimplexId next{};
1625 triangulation.getEdgeVertex(pairedEdge, 0, next);
1626 if(next == curr) {
1627 triangulation.getEdgeVertex(pairedEdge, 1, next);
1628 }
1629 visited.emplace_back(curr);
1630 curr = next;
1631 }
1632 for(const auto el : visited) {
1633 morseSmaleManifold[el] = morseSmaleManifold[curr];
1634 }
1635 }
1636
1637 this->printMsg(" Descending segmentation computed", 1.0, tm.getElapsedTime(),
1638 this->threadNumber_, debug::LineMode::NEW,
1640
1641 return 0;
1642}
1643
1644template <typename triangulationType>
1646 const SimplexId numberOfMaxima,
1647 const SimplexId *const ascendingManifold,
1648 const SimplexId *const descendingManifold,
1649 SimplexId *const morseSmaleManifold,
1650 const triangulationType &triangulation) const {
1651
1652 if(ascendingManifold == nullptr || descendingManifold == nullptr
1653 || morseSmaleManifold == nullptr) {
1654 this->printErr("Could not compute final segmentation");
1655 return 1;
1656 }
1657
1658 Timer tm{};
1659
1660 const size_t nVerts = triangulation.getNumberOfVertices();
1661
1662 // associate a unique "sparse region id" to each (ascending, descending) pair
1663
1664#ifdef TTK_ENABLE_OPENMP
1665#pragma omp parallel for num_threads(threadNumber_)
1666#endif // TTK_ENABLE_OPENMP
1667 for(size_t i = 0; i < nVerts; ++i) {
1668 const auto d = ascendingManifold[i];
1669 const auto a = descendingManifold[i];
1670 if(a == -1 || d == -1) {
1671 morseSmaleManifold[i] = -1;
1672 } else {
1673 morseSmaleManifold[i] = a * numberOfMaxima + d;
1674 }
1675 }
1676
1677 // store the "sparse region ids" by copying the morseSmaleManifold output
1678 std::vector<SimplexId> sparseRegionIds(
1679 morseSmaleManifold, morseSmaleManifold + nVerts);
1680
1681 // get unique "sparse region ids"
1682 TTK_PSORT(
1683 this->threadNumber_, sparseRegionIds.begin(), sparseRegionIds.end());
1684 const auto last = std::unique(sparseRegionIds.begin(), sparseRegionIds.end());
1685 sparseRegionIds.erase(last, sparseRegionIds.end());
1686
1687 // "sparse region id" -> "dense region id"
1688 std::map<SimplexId, size_t> sparseToDenseRegionId{};
1689
1690 for(size_t i = 0; i < sparseRegionIds.size(); ++i) {
1691 sparseToDenseRegionId[sparseRegionIds[i]] = i;
1692 }
1693
1694 // update region id on all vertices: "sparse id" -> "dense id"
1695
1696#ifdef TTK_ENABLE_OPENMP
1697#pragma omp parallel for num_threads(threadNumber_)
1698#endif // TTK_ENABLE_OPENMP
1699 for(size_t i = 0; i < nVerts; ++i) {
1700 morseSmaleManifold[i] = sparseToDenseRegionId[morseSmaleManifold[i]];
1701 }
1702
1703 this->printMsg(" Final segmentation computed", 1.0, tm.getElapsedTime(),
1704 this->threadNumber_, debug::LineMode::NEW,
1706
1707 return 0;
1708}
1709
1710template <typename dataType, typename triangulationType>
1712 const double persistenceThreshold,
1713 const dataType *const scalars,
1714 const SimplexId *const offsets,
1715 const triangulationType &triangulation) {
1716
1717 Timer tm{};
1718
1719 const auto dim{triangulation.getDimensionality()};
1720 if(dim != 3) {
1721 this->printWrn("Can't return saddle connectors without a 3D dataset");
1722 return 0;
1723 }
1724
1725 // compute saddle-saddle Persistence Pairs from DiscreteMorseSandwich
1727 dms.setThreadNumber(this->threadNumber_);
1728 dms.setDebugLevel(this->debugLevel_);
1729 dms.setGradient(std::move(this->discreteGradient_));
1730
1731 using PersPairType = DiscreteMorseSandwich::PersistencePair;
1732
1733 std::vector<PersPairType> dms_pairs{};
1734 dms.computePersistencePairs(dms_pairs, offsets, triangulation, false, true);
1735 this->discreteGradient_ = dms.getGradient();
1736 // reset gradient pointer to local storage
1737 this->discreteGradient_.setLocalGradient();
1738
1739 const auto getPersistence
1740 = [this, &triangulation, scalars](const PersPairType &p) {
1741 return this->discreteGradient_.getPersistence(
1742 Cell{2, p.death}, Cell{1, p.birth}, scalars, triangulation);
1743 };
1744
1745 // saddle-saddle pairs should be in one continuous block inside dms_pairs
1746 const auto firstSadSadPair{std::distance(
1747 dms_pairs.begin(),
1748 std::find_if(dms_pairs.begin(), dms_pairs.end(),
1749 [](const auto &pair) { return pair.type == 1; }))};
1750
1751 // DMS pairs output are already sorted by 2-saddles in ascending order
1752
1753 std::vector<bool> isVisited(triangulation.getNumberOfTriangles(), false);
1754 std::vector<SimplexId> visitedTriangles{};
1755
1756 size_t nReturned{};
1757
1758 // Sort pairs to process by persistence
1759 std::vector<std::tuple<size_t, dataType>> pairs;
1760 for(size_t i = firstSadSadPair; i < dms_pairs.size(); ++i) {
1761 const auto &pair{dms_pairs[i]};
1762 pairs.emplace_back(std::make_tuple(i, getPersistence(pair)));
1763 }
1764 const auto comparePersistence
1765 = [](const std::tuple<size_t, dataType> &pair1,
1766 const std::tuple<size_t, dataType> &pair2) {
1767 return std::get<1>(pair1) < std::get<1>(pair2);
1768 };
1769 TTK_PSORT(
1770 this->threadNumber_, pairs.begin(), pairs.end(), comparePersistence);
1771
1772 std::vector<std::tuple<dataType, SimplexId, SimplexId>> skippedPairsPers;
1773
1774 // Process pairs
1775 for(const auto &pairTup : pairs) {
1776 const auto &pairIndex = std::get<0>(pairTup);
1777 const auto &pair{dms_pairs[pairIndex]};
1778 const auto &pairPersistence = std::get<1>(pairTup);
1779
1780 if(pair.type != 1 || pairPersistence > persistenceThreshold) {
1781 continue;
1782 }
1783
1784 const Cell birth{1, pair.birth};
1785 const Cell death{2, pair.death};
1786
1787 // 1. get the 2-saddle wall
1788 VisitedMask mask{isVisited, visitedTriangles};
1789 this->discreteGradient_.getDescendingWall(death, mask, triangulation);
1790 // 2. get the saddle connector
1791 std::vector<Cell> vpath{};
1792 bool disableForkReversal = not ForceLoopFreeGradient;
1793 this->discreteGradient_.getAscendingPathThroughWall(
1794 birth, death, isVisited, &vpath, triangulation, disableForkReversal);
1795 // 3. reverse the gradient on the saddle connector path
1796 if(vpath.back() == death) {
1797 this->discreteGradient_.reverseAscendingPathOnWall(vpath, triangulation);
1798
1799 // 3.1 Detect cycle
1800 bool cycle = false;
1801 if(ForceLoopFreeGradient) {
1802 cycle
1803 = this->discreteGradient_.detectGradientCycle(birth, triangulation);
1804 } else {
1805 std::vector<bool> isVisitedUpdated(
1806 triangulation.getNumberOfTriangles(), false);
1807 std::vector<SimplexId> visitedTrianglesUpdated{};
1808 VisitedMask maskUpdated{isVisitedUpdated, visitedTrianglesUpdated};
1809 discreteGradient_.getDescendingWall(death, maskUpdated, triangulation);
1810 discreteGradient_.getAscendingPathThroughWall(
1811 birth, death, isVisitedUpdated, nullptr, triangulation, false, true,
1812 &cycle);
1813 }
1814 if(cycle) {
1815 this->discreteGradient_.reverseAscendingPathOnWall(
1816 vpath, triangulation, true);
1817 this->printMsg("Could not return saddle connector " + birth.to_string()
1818 + " -> " + death.to_string()
1819 + " without creating a cycle.",
1821 if(this->debugLevel_ == (int)debug::Priority::DETAIL)
1822 skippedPairsPers.emplace_back(
1823 std::make_tuple(pairPersistence, pair.birth, pair.death));
1824 } else
1825 nReturned++;
1826
1827 } else {
1828 this->printMsg("Could not return saddle connector " + birth.to_string()
1829 + " -> " + death.to_string(),
1831 if(this->debugLevel_ == (int)debug::Priority::DETAIL)
1832 skippedPairsPers.emplace_back(
1833 std::make_tuple(pairPersistence, pair.birth, pair.death));
1834 }
1835 }
1836
1837 if(this->debugLevel_ == (int)debug::Priority::DETAIL) {
1838 TTK_PSORT(
1839 this->threadNumber_, skippedPairsPers.begin(), skippedPairsPers.end());
1840 for(unsigned int i = 0; i < skippedPairsPers.size(); ++i)
1841 this->printMsg(std::to_string(i) + " "
1842 + std::to_string(std::get<1>(skippedPairsPers[i])) + " "
1843 + std::to_string(std::get<2>(skippedPairsPers[i])) + " "
1844 + std::to_string(std::get<0>(skippedPairsPers[i])));
1845 }
1846
1847 this->printMsg("Returned " + std::to_string(nReturned) + " saddle connectors",
1848 1.0, tm.getElapsedTime(), this->threadNumber_);
1849
1850 return 0;
1851}
#define TTK_PSORT(NTHREADS,...)
Parallel sort macro.
Definition OpenMP.h:46
AbstractTriangulation is an interface class that defines an interface for efficient traversal methods...
virtual int setThreadNumber(const int threadNumber)
Definition BaseClass.h:80
Minimalist debugging class.
Definition Debug.h:88
int debugLevel_
Definition Debug.h:379
virtual int setDebugLevel(const int &debugLevel)
Definition Debug.cpp:147
int printErr(const std::string &msg, const debug::LineMode &lineMode=debug::LineMode::NEW, std::ostream &stream=std::cerr) const
Definition Debug.h:149
TTK DiscreteMorseSandwich processing package.
TTK processing package for the computation of Morse-Smale complexes.
int setAscendingSegmentation(const std::vector< SimplexId > &maxima, 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
void flattenSeparatricesVectors(std::vector< std::vector< Separatrix > > &separatrices) const
Flatten the vectors of vectors into their first component.
int getDualPolygon(const SimplexId edgeId, SimplexId *const polygon, const size_t polSize, const triangulationType &triangulation) const
void setComputeCriticalPoints(const bool state)
int getAscendingSeparatrices2(const std::vector< SimplexId > &saddles1, std::vector< Separatrix > &separatrices, std::vector< std::vector< SimplexId > > &separatricesSaddles, const triangulationType &triangulation) const
int getSaddleConnectors(const std::vector< SimplexId > &saddles2, std::vector< Separatrix > &separatrices, const triangulationType &triangulation) const
int getDescendingSeparatrices1(const std::vector< SimplexId > &saddles, std::vector< Separatrix > &separatrices, const triangulationType &triangulation) const
dcg::DiscreteGradient discreteGradient_
int returnSaddleConnectors(const double persistenceThreshold, const dataType *const scalars, const SimplexId *const offsets, const triangulationType &triangulation)
int setAscendingSeparatrices2(Output2Separatrices &outSeps2, const std::vector< Separatrix > &separatrices, const std::vector< std::vector< SimplexId > > &separatricesSaddles, const SimplexId *const offsets, const triangulationType &triangulation) const
int setDescendingSegmentation(const std::vector< SimplexId > &minima, SimplexId *const morseSmaleManifold, const triangulationType &triangulation) const
int setSeparatrices1(Output1Separatrices &outSeps1, const std::vector< Separatrix > &separatrices, const SimplexId *const offsets, const triangulationType &triangulation) const
void setComputeSegmentation(const bool doAscending, const bool doDescending, const bool doMorseSmale)
void setSaddleConnectorsPersistenceThreshold(const double threshold)
int sortDualPolygonVertices(SimplexId *const polygon, const size_t polSize, const triangulationType &triangulation) const
void setComputeSeparatrices2(const bool doAscending, const bool doDescending)
void preconditionTriangulation(AbstractTriangulation *const data)
int setDescendingSeparatrices2(Output2Separatrices &outSeps2, const std::vector< Separatrix > &separatrices, const std::vector< std::vector< SimplexId > > &separatricesSaddles, const SimplexId *const offsets, 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 setComputeSeparatrices1(const bool doAscending, const bool doDescending, const bool doSaddleConnectors)
void setReturnSaddleConnectors(const bool state)
int execute(OutputCriticalPoints &outCP, Output1Separatrices &outSeps1, Output2Separatrices &outSeps2, OutputManifold &outManifold, const dataType *const scalars, const size_t scalarsMTime, const SimplexId *const offsets, const triangulationType &triangulation)
int getAscendingSeparatrices1(const std::vector< SimplexId > &saddles, std::vector< Separatrix > &separatrices, const triangulationType &triangulation) const
double getElapsedTime()
Definition Timer.h:15
TTK discreteGradient processing package.
void setInputOffsets(const SimplexId *const data)
int setManifoldSize(const std::array< std::vector< SimplexId >, 4 > &criticalCellsByDim, const SimplexId *const ascendingManifold, const SimplexId *const descendingManifold, std::vector< SimplexId > &manifoldSize) const
void setInputScalarField(const void *const data, const size_t mTime)
int getCriticalPoints(std::array< std::vector< SimplexId >, 4 > &criticalCellsByDim, const triangulationType &triangulation) const
void preconditionTriangulation(AbstractTriangulation *const data)
int buildGradient(const triangulationType &triangulation, bool bypassCache=false)
int setCriticalPoints(const std::array< std::vector< SimplexId >, 4 > &criticalCellsByDim, std::vector< std::array< float, 3 > > &points, std::vector< char > &cellDimensions, std::vector< SimplexId > &cellIds, std::vector< char > &isOnBoundary, std::vector< SimplexId > &PLVertexIdentifiers, const triangulationType &triangulation) const
std::string to_string(__int128)
Definition ripserpy.cpp:99
The Topology ToolKit.
int SimplexId
Identifier type for simplices of any dimension.
Definition DataTypes.h:22
Persistence pair struct as exported by DiscreteGradient.
1-Separatrices point and cell data arrays
std::vector< ttk::SimplexId > destinationIds_
std::vector< ttk::SimplexId > separatrixIds_
struct ttk::MorseSmaleComplex::Output1Separatrices::@1 cl
std::vector< ttk::SimplexId > connectivity_
struct ttk::MorseSmaleComplex::Output1Separatrices::@0 pt
2-Separatrices point and cell data arrays
struct ttk::MorseSmaleComplex::Output2Separatrices::@2 pt
struct ttk::MorseSmaleComplex::Output2Separatrices::@3 cl
std::vector< ttk::SimplexId > connectivity_
std::vector< ttk::SimplexId > separatrixIds_
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)