12 std::vector<MatchingType> &matchings) {
15 if(diag0.empty() || diag1.empty()) {
16 this->printErr(
"Empty input diagrams");
20 const bool fromParaView = this->PVAlgorithm >= 0;
22 switch(this->PVAlgorithm) {
24 this->
printMsg(
"Solving with the TTK approach");
25 this->computeBottleneck(diag0, diag1, matchings);
28 this->
printMsg(
"Solving with the legacy Dionysus exact approach.");
29 this->printErr(
"Not supported");
33 "Solving with the approximate Dionysus geometric approach.");
34 this->printErr(
"Not supported");
37 this->
printMsg(
"Solving with the parallel TTK approach");
38 this->printErr(
"Not supported");
42 this->printErr(
"Not supported");
45 this->printErr(
"You must specify a valid assignment algorithm.");
50 switch(
str2int(this->DistanceAlgorithm.c_str())) {
53 this->
printMsg(
"Solving with the TTK approach");
54 this->computeBottleneck(diag0, diag1, matchings);
58 this->
printMsg(
"Solving with the legacy Dionysus exact approach.");
59 this->printErr(
"Not supported");
64 "Solving with the approximate Dionysus geometric approach.");
65 this->printErr(
"Not supported");
69 this->
printMsg(
"Solving with the parallel TTK approach");
70 this->printErr(
"Not supported");
74 this->printErr(
"Not supported");
77 this->printErr(
"You must specify a valid assignment algorithm.");
87void ttk::BottleneckDistance::buildCostMatrices(
90 const double zeroThresh,
91 std::vector<std::vector<double>> &minMatrix,
92 std::vector<std::vector<double>> &maxMatrix,
93 std::vector<std::vector<double>> &sadMatrix,
94 const bool reverseMin,
95 const bool reverseMax,
96 const bool reverseSad,
97 const int wasserstein)
const {
99 int maxI = 0, minI = 0;
100 int maxJ = 0, minJ = 0;
101 int sadI = 0, sadJ = 0;
103 for(
const auto &p1 : CTDiagram1) {
104 if(std::abs(p1.persistence()) < zeroThresh)
125 for(
const auto &p2 : CTDiagram2) {
126 if(std::abs(p2.persistence()) < zeroThresh)
142 if((isMin1 && !isMin2) || (isMax1 && !isMax2) || (isSad1 && !isSad2))
145 double distance = this->distanceFunction(p1, p2, wasserstein);
146 const double diag1 = this->diagonalDistanceFunction(p1, wasserstein);
147 const double diag2 = this->diagonalDistanceFunction(p2, wasserstein);
149 if(distance > diag1 + diag2)
150 distance = std::numeric_limits<double>::max();
152 if(isMin1 && isMin2) {
157 }
else if(isMax1 && isMax2) {
162 }
else if(isSad1 && isSad2) {
170 const double distanceToDiagonal
171 = this->diagonalDistanceFunction(p1, wasserstein);
174 minMatrix[minJ++][minI] = distanceToDiagonal;
176 minMatrix[minI][minJ++] = distanceToDiagonal;
180 maxMatrix[maxJ++][maxI] = distanceToDiagonal;
182 maxMatrix[maxI][maxJ++] = distanceToDiagonal;
186 sadMatrix[sadJ++][sadI] = distanceToDiagonal;
188 sadMatrix[sadI][sadJ++] = distanceToDiagonal;
204 for(
const auto &p3 : CTDiagram2) {
205 if(std::abs(p3.persistence()) < zeroThresh)
222 const double distanceToDiagonal
223 = this->diagonalDistanceFunction(p3, wasserstein);
226 minMatrix[minJ++][minI] = distanceToDiagonal;
228 minMatrix[minI][minJ++] = distanceToDiagonal;
232 maxMatrix[maxJ++][maxI] = distanceToDiagonal;
234 maxMatrix[maxI][maxJ++] = distanceToDiagonal;
238 sadMatrix[sadJ++][sadI] = distanceToDiagonal;
240 sadMatrix[sadI][sadJ++] = distanceToDiagonal;
246 minMatrix[minJ][minI] = std::numeric_limits<double>::max();
248 minMatrix[minI][minJ] = std::numeric_limits<double>::max();
251 maxMatrix[maxJ][maxI] = std::numeric_limits<double>::max();
253 maxMatrix[maxI][maxJ] = std::numeric_limits<double>::max();
256 sadMatrix[sadJ][sadI] = std::numeric_limits<double>::max();
258 sadMatrix[sadI][sadJ] = std::numeric_limits<double>::max();
261double ttk::BottleneckDistance::computeGeometricalRange(
265 float minX, minY, minZ, maxX, maxY, maxZ;
267 std::array<float, 3> min1{
268 std::numeric_limits<float>::max(),
269 std::numeric_limits<float>::max(),
270 std::numeric_limits<float>::max(),
273 std::array<float, 3> max1{
274 std::numeric_limits<float>::lowest(),
275 std::numeric_limits<float>::lowest(),
276 std::numeric_limits<float>::lowest(),
280 for(
const auto &p : CTDiagram1) {
281 min1[0] = std::min(std::min(min1[0], p.birth.coords[0]), p.death.coords[0]);
282 min1[1] = std::min(std::min(min1[1], p.birth.coords[1]), p.death.coords[1]);
283 min1[2] = std::min(std::min(min1[2], p.birth.coords[2]), p.death.coords[2]);
284 max1[0] = std::max(std::max(max1[0], p.birth.coords[0]), p.birth.coords[0]);
285 max1[1] = std::max(std::max(max1[1], p.birth.coords[1]), p.birth.coords[1]);
286 max1[2] = std::max(std::max(max1[2], p.birth.coords[2]), p.birth.coords[2]);
289 for(
const auto &p : CTDiagram2) {
290 min2[0] = std::min(std::min(min2[0], p.birth.coords[0]), p.death.coords[0]);
291 min2[1] = std::min(std::min(min2[1], p.birth.coords[1]), p.death.coords[1]);
292 min2[2] = std::min(std::min(min2[2], p.birth.coords[2]), p.death.coords[2]);
293 max2[0] = std::max(std::max(max2[0], p.birth.coords[0]), p.birth.coords[0]);
294 max2[1] = std::max(std::max(max2[1], p.birth.coords[1]), p.birth.coords[1]);
295 max2[2] = std::max(std::max(max2[2], p.birth.coords[2]), p.birth.coords[2]);
298 minX = std::min(min1[0], min2[0]);
299 minY = std::min(min1[1], min2[1]);
300 minZ = std::min(min1[2], min2[2]);
301 maxX = std::max(max1[0], max2[0]);
302 maxY = std::max(max1[1], max2[1]);
303 maxZ = std::max(max1[2], max2[2]);
305 const auto square = [](
const double a) {
return a * a; };
307 return std::sqrt(square(maxX - minX) + square(maxY - minY)
308 + square(maxZ - minZ));
311double ttk::BottleneckDistance::computeMinimumRelevantPersistence(
315 const auto sp = this->Tolerance;
316 const double s = sp > 0.0 && sp < 100.0 ? sp / 100.0 : 0;
318 std::vector<double> toSort(CTDiagram1.size() + CTDiagram2.size());
319 for(
size_t i = 0; i < CTDiagram1.size(); ++i) {
320 const auto &t = CTDiagram1[i];
321 toSort[i] = std::abs(t.persistence());
323 for(
size_t i = 0; i < CTDiagram2.size(); ++i) {
324 const auto &t = CTDiagram2[i];
325 toSort[CTDiagram1.size() + i] = std::abs(t.persistence());
328 const auto minVal = *std::min_element(toSort.begin(), toSort.end());
329 const auto maxVal = *std::max_element(toSort.begin(), toSort.end());
330 return s * (maxVal - minVal);
333void ttk::BottleneckDistance::computeMinMaxSaddleNumberAndMapping(
338 std::vector<int> &minMap,
339 std::vector<int> &maxMap,
340 std::vector<int> &sadMap,
341 const double zeroThresh)
const {
343 for(
size_t i = 0; i < CTDiagram.size(); ++i) {
344 const auto &t = CTDiagram[i];
345 const auto nt1 = t.birth.type;
346 const auto nt2 = t.death.type;
347 const auto dt = t.persistence();
348 if(std::abs(dt) < zeroThresh)
376 void solvePWasserstein(std::vector<std::vector<double>> &matrix,
377 std::vector<ttk::MatchingType> &matchings,
381 solver.
run(matchings);
385 void solveInfinityWasserstein(
const int nbRow,
387 std::vector<std::vector<double>> &matrix,
388 std::vector<ttk::MatchingType> &matchings,
392 auto bottleneckMatrix = matrix;
395 solver.
setInput(nbRow, nbCol, &bottleneckMatrix);
396 solver.
run(matchings);
401double ttk::BottleneckDistance::buildMappings(
402 const std::vector<MatchingType> &inputMatchings,
403 const bool transposeGlobal,
404 const bool transposeLocal,
405 std::vector<MatchingType> &outputMatchings,
406 const std::vector<int> &m1,
407 const std::vector<int> &m2,
408 int wasserstein)
const {
411 const auto &map1 = transposeLocal ? m2 : m1;
412 const auto &map2 = transposeLocal ? m1 : m2;
414 double addedPersistence = 0;
415 const auto doTranspose = transposeGlobal ^ transposeLocal;
417 for(
const auto &t : inputMatchings) {
419 const auto val = std::abs(std::get<2>(t));
420 const size_t p1 = std::get<0>(t);
421 const size_t p2 = std::get<1>(t);
423 if(p1 >= map1.size() || p2 >= map2.size()) {
424 addedPersistence = (wasserstein > 0 ? addedPersistence + val
425 : std::max(val, addedPersistence));
428 outputMatchings.emplace_back(map2[p2], map1[p1], val);
430 outputMatchings.emplace_back(map1[p1], map2[p2], val);
435 return addedPersistence;
440 const int wasserstein)
const {
442 const int w = std::max(wasserstein, 1);
449 const std::array<float, 3> coordsAbsDiff{
456 = ((isMin1 && !isMax1) ? this->PE : this->PS)
457 * Geometry::
pow(std::abs(a.birth.sfValue - b.birth.sfValue), w);
459 = (isMax1 ? this->PE : this->PS)
461 const double geoDistance
468 std::abs(a.birth.coords[0] + a.death.coords[0]) / 2
469 - std::abs(b.birth.coords[0] + b.death.coords[0]) / 2,
473 std::abs(a.birth.coords[1] + a.death.coords[1]) / 2
474 - std::abs(b.birth.coords[1] + b.death.coords[1]) / 2,
478 std::abs(a.birth.coords[2] + a.death.coords[2]) / 2
479 - std::abs(b.birth.coords[2] + b.death.coords[2]) / 2,
482 const double persDistance = x + y;
486double ttk::BottleneckDistance::diagonalDistanceFunction(
489 const int w = std::max(wasserstein, 1);
492 const double infDistance = (isMin1 || isMax1 ? this->PE : this->PS)
494 const double geoDistance
505int ttk::BottleneckDistance::computeBottleneck(
508 std::vector<MatchingType> &matchings) {
510 const auto transposeOriginal = d1.size() > d2.size();
511 if(transposeOriginal) {
512 this->
printMsg(
"The first persistence diagram is larger than the second.");
513 this->
printMsg(
"Solving the transposed problem.");
515 const auto &CTDiagram1 = transposeOriginal ? d2 : d1;
516 const auto &CTDiagram2 = transposeOriginal ? d1 : d2;
519 const auto isBottleneck = this->WassersteinMetric ==
"inf";
520 const int wasserstein = isBottleneck ? -1 : stoi(this->WassersteinMetric);
521 if(wasserstein < 0 && !isBottleneck) {
522 this->printErr(
"Wrong value for the Wassertein power parameter");
527 const auto zeroThresh
528 = this->computeMinimumRelevantPersistence(CTDiagram1, CTDiagram2);
531 std::vector<MatchingType> minMatchings;
532 std::vector<MatchingType> maxMatchings;
533 std::vector<MatchingType> sadMatchings;
536 int nbRowMin = 0, nbColMin = 0;
537 int nbRowMax = 0, nbColMax = 0;
538 int nbRowSad = 0, nbColSad = 0;
541 std::array<std::vector<int>, 3> map1{};
542 std::array<std::vector<int>, 3> map2{};
544 this->computeMinMaxSaddleNumberAndMapping(CTDiagram1, nbRowMin, nbRowMax,
545 nbRowSad, map1[0], map1[2], map1[1],
547 this->computeMinMaxSaddleNumberAndMapping(CTDiagram2, nbColMin, nbColMax,
548 nbColSad, map2[0], map2[2], map2[1],
552 const auto maxRowColMin = std::max(nbRowMin + 1, nbColMin + 1);
553 const auto maxRowColMax = std::max(nbRowMax + 1, nbColMax + 1);
554 const auto maxRowColSad = std::max(nbRowSad + 1, nbColSad + 1);
556 const auto minRowColMin = std::min(nbRowMin + 1, nbColMin + 1);
557 const auto minRowColMax = std::min(nbRowMax + 1, nbColMax + 1);
558 const auto minRowColSad = std::min(nbRowSad + 1, nbColSad + 1);
560 std::vector<std::vector<double>> minMatrix(
561 minRowColMin, std::vector<double>(maxRowColMin));
562 std::vector<std::vector<double>> maxMatrix(
563 minRowColMax, std::vector<double>(maxRowColMax));
564 std::vector<std::vector<double>> sadMatrix(
565 minRowColSad, std::vector<double>(maxRowColSad));
567 const bool transposeMin = nbRowMin > nbColMin;
568 const bool transposeMax = nbRowMax > nbColMax;
569 const bool transposeSad = nbRowSad > nbColSad;
573 this->buildCostMatrices(CTDiagram1, CTDiagram2, zeroThresh, minMatrix,
574 maxMatrix, sadMatrix, transposeMin, transposeMax,
575 transposeSad, wasserstein);
579 if(nbRowMin > 0 && nbColMin > 0) {
580 AssignmentMunkres<double> solverMin;
581 this->
printMsg(
"Affecting minima...");
582 solvePWasserstein(minMatrix, minMatchings, solverMin);
585 if(nbRowMax > 0 && nbColMax > 0) {
586 AssignmentMunkres<double> solverMax;
587 this->
printMsg(
"Affecting maxima...");
588 solvePWasserstein(maxMatrix, maxMatchings, solverMax);
591 if(nbRowSad > 0 && nbColSad > 0) {
592 AssignmentMunkres<double> solverSad;
593 this->
printMsg(
"Affecting saddles...");
594 solvePWasserstein(sadMatrix, sadMatchings, solverSad);
600 if(nbRowMin > 0 && nbColMin > 0) {
601 GabowTarjan solverMin;
602 this->
printMsg(
"Affecting minima...");
603 solveInfinityWasserstein(
604 minRowColMin, maxRowColMin, minMatrix, minMatchings, solverMin);
608 if(nbRowMax > 0 && nbColMax > 0) {
609 GabowTarjan solverMax;
610 this->
printMsg(
"Affecting maxima...");
611 solveInfinityWasserstein(
612 minRowColMax, maxRowColMax, maxMatrix, maxMatchings, solverMax);
616 if(nbRowSad > 0 && nbColSad > 0) {
617 GabowTarjan solverSad;
618 this->
printMsg(
"Affecting saddles...");
619 solveInfinityWasserstein(
620 minRowColSad, maxRowColSad, sadMatrix, sadMatchings, solverSad);
624 this->
printMsg(
"TTK CORE DONE", 1, t.getElapsedTime());
628 const std::array<double, 3> addedPersistence{
629 this->buildMappings(minMatchings, transposeOriginal, transposeMin,
630 matchings, map1[0], map2[0], wasserstein),
631 this->buildMappings(sadMatchings, transposeOriginal, transposeSad,
632 matchings, map1[1], map2[1], wasserstein),
633 this->buildMappings(maxMatchings, transposeOriginal, transposeMax,
634 matchings, map1[2], map2[2], wasserstein),
639 std::array<double, 3> costs{};
640 std::vector<bool> paired1(CTDiagram1.size(),
false);
641 std::vector<bool> paired2(CTDiagram2.size(),
false);
644 double partialDistance{};
646 for(
const auto &mt : matchings) {
647 const int i = transposeOriginal ? std::get<1>(mt) : std::get<0>(mt);
648 const int j = transposeOriginal ? std::get<0>(mt) : std::get<1>(mt);
650 const auto &t1 = CTDiagram1[i];
651 const auto &t2 = CTDiagram2[j];
655 partialDistance = this->distanceFunction(t1, t2, wasserstein);
659 costs[2] += partialDistance;
661 costs[2] = std::max(costs[2], partialDistance);
665 costs[0] += partialDistance;
667 costs[0] = std::max(costs[0], partialDistance);
672 costs[1] += partialDistance;
674 costs[1] = std::max(costs[1], partialDistance);
679 const auto affectationD
680 = !isBottleneck ? costs[0] + costs[1] + costs[2] : partialDistance;
682 = addedPersistence[0] + addedPersistence[1] + addedPersistence[2];
686 : std::max(affectationD, *std::max_element(addedPersistence.
begin(),
687 addedPersistence.
end()));
689 this->
printMsg(
"Computed distance:");
690 this->
printMsg(
"diagMax(" + std::to_string(addedPersistence[2])
691 +
"), diagMin(" + std::to_string(addedPersistence[0])
692 +
"), diagSad(" + std::to_string(addedPersistence[1]) +
")");
693 this->
printMsg(
"affAll(" + std::to_string(affectationD) +
"), res("
694 + std::to_string(this->distance_) +
")");
698 costs[0] =
Geometry::pow(costs[0] + addedPersistence[0], 1.0 / wasserstein);
699 costs[1] =
Geometry::pow(costs[1] + addedPersistence[1], 1.0 / wasserstein);
700 costs[2] =
Geometry::pow(costs[2] + addedPersistence[2], 1.0 / wasserstein);
702 costs[0] = std::max(costs[0], addedPersistence[0]);
703 costs[1] = std::max(costs[1], addedPersistence[1]);
704 costs[2] = std::max(costs[2], addedPersistence[2]);
707 this->costs_ = costs;
710 const std::vector<std::vector<std::string>> rows{
711 {
" Min-saddle cost", std::to_string(this->costs_[0])},
712 {
" Saddle-saddle cost", std::to_string(this->costs_[1])},
713 {
" Saddle-max cost", std::to_string(this->costs_[2])},
714 {isBottleneck ?
"Bottleneck Distance" :
"Wasserstein Distance",
715 std::to_string(this->distance_)},
int run(std::vector< MatchingType > &matchings) override
int setInput(std::vector< std::vector< dataType > > &C_) override
virtual void clearMatrix()
int execute(const ttk::DiagramType &diag0, const ttk::DiagramType &diag1, std::vector< MatchingType > &matchings)
void setDebugMsgPrefix(const std::string &prefix)
int run(std::vector< MatchingType > &matchings)
void setInput(int rowSize_, int colSize_, std::vector< std::vector< double > > *C_)
T1 pow(const T1 val, const T2 n)
T distance(const T *p0, const T *p1, const int &dimension=3)
constexpr unsigned long long str2int(const char *str, int h=0)
std::vector< PersistencePair > DiagramType
Persistence Diagram type as a vector of Persistence pairs.
T end(std::pair< T, T > &p)
T begin(std::pair< T, T > &p)
std::array< float, 3 > coords
ttk::CriticalVertex birth
double persistence() const
Return the topological persistence of the pair.
ttk::CriticalVertex death
printMsg(debug::output::BOLD+" | | | | | . \\ | | (__| | / __/| |_| / __/|__ _|"+debug::output::ENDCOLOR, debug::Priority::PERFORMANCE, debug::LineMode::NEW, stream)