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