TTK
Loading...
Searching...
No Matches
BottleneckDistance.cpp
Go to the documentation of this file.
1#include <AssignmentMunkres.h>
3#include <GabowTarjan.h>
4#include <Geometry.h>
5
9
11 const ttk::DiagramType &diag1,
12 std::vector<MatchingType> &matchings) {
13 Timer t;
14
15 if(diag0.empty() || diag1.empty()) {
16 this->printErr("Empty input diagrams");
17 return -1;
18 }
19
20 const bool fromParaView = this->PVAlgorithm >= 0;
21 if(fromParaView) {
22 switch(this->PVAlgorithm) {
23 case 0:
24 this->printMsg("Solving with the TTK approach");
25 this->computeBottleneck(diag0, diag1, matchings);
26 break;
27 case 1: {
28 this->printMsg("Solving with the legacy Dionysus exact approach.");
29 this->printErr("Not supported");
30 } break;
31 case 2: {
32 this->printMsg(
33 "Solving with the approximate Dionysus geometric approach.");
34 this->printErr("Not supported");
35 } break;
36 case 3: {
37 this->printMsg("Solving with the parallel TTK approach");
38 this->printErr("Not supported");
39 } break;
40 case 4: {
41 this->printMsg("Benchmarking");
42 this->printErr("Not supported");
43 } break;
44 default: {
45 this->printErr("You must specify a valid assignment algorithm.");
46 }
47 }
48
49 } else {
50 switch(str2int(this->DistanceAlgorithm.c_str())) {
51 case str2int("0"):
52 case str2int("ttk"):
53 this->printMsg("Solving with the TTK approach");
54 this->computeBottleneck(diag0, diag1, matchings);
55 break;
56 case str2int("1"):
57 case str2int("legacy"): {
58 this->printMsg("Solving with the legacy Dionysus exact approach.");
59 this->printErr("Not supported");
60 } break;
61 case str2int("2"):
62 case str2int("geometric"): {
63 this->printMsg(
64 "Solving with the approximate Dionysus geometric approach.");
65 this->printErr("Not supported");
66 } break;
67 case str2int("3"):
68 case str2int("parallel"): {
69 this->printMsg("Solving with the parallel TTK approach");
70 this->printErr("Not supported");
71 } break;
72 case str2int("bench"): {
73 this->printMsg("Benchmarking");
74 this->printErr("Not supported");
75 } break;
76 default: {
77 this->printErr("You must specify a valid assignment algorithm.");
78 }
79 }
80 }
81
82 this->printMsg("Complete", 1, t.getElapsedTime(), threadNumber_);
83
84 return 0;
85}
86
87void ttk::BottleneckDistance::buildCostMatrices(
88 const ttk::DiagramType &CTDiagram1,
89 const ttk::DiagramType &CTDiagram2,
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 {
98
99 int maxI = 0, minI = 0;
100 int maxJ = 0, minJ = 0;
101 int sadI = 0, sadJ = 0;
102
103 for(const auto &p1 : CTDiagram1) {
104 if(std::abs(p1.persistence()) < zeroThresh)
105 continue;
106
107 bool isMin1 = (p1.birth.type == CriticalType::Local_minimum
108 || p1.death.type == CriticalType::Local_minimum);
109 bool isMax1 = (p1.birth.type == CriticalType::Local_maximum
110 || p1.death.type == CriticalType::Local_maximum);
111 const bool isSad1 = (p1.birth.type == CriticalType::Saddle1
112 && p1.death.type == CriticalType::Saddle2)
113 || (p1.birth.type == CriticalType::Saddle2
114 && p1.death.type == CriticalType::Saddle1);
115 if(p1.birth.type == CriticalType::Local_minimum
116 && p1.death.type == CriticalType::Local_maximum) {
117 isMin1 = false;
118 isMax1 = true;
119 }
120
121 minJ = 0;
122 maxJ = 0;
123 sadJ = 0;
124
125 for(const auto &p2 : CTDiagram2) {
126 if(std::abs(p2.persistence()) < zeroThresh)
127 continue;
128
129 bool isMin2 = (p2.birth.type == CriticalType::Local_minimum
130 || p2.death.type == CriticalType::Local_minimum);
131 bool isMax2 = (p2.birth.type == CriticalType::Local_maximum
132 || p2.death.type == CriticalType::Local_maximum);
133 const bool isSad2 = (p2.birth.type == CriticalType::Saddle1
134 && p2.death.type == CriticalType::Saddle2)
135 || (p2.birth.type == CriticalType::Saddle2
136 && p2.death.type == CriticalType::Saddle1);
137 if(p2.birth.type == CriticalType::Local_minimum
138 && p2.death.type == CriticalType::Local_maximum) {
139 isMin2 = false;
140 isMax2 = true;
141 }
142 if((isMin1 && !isMin2) || (isMax1 && !isMax2) || (isSad1 && !isSad2))
143 continue;
144
145 double distance = this->distanceFunction(p1, p2, wasserstein);
146 const double diag1 = this->diagonalDistanceFunction(p1, wasserstein);
147 const double diag2 = this->diagonalDistanceFunction(p2, wasserstein);
148
149 if(distance > diag1 + diag2)
150 distance = std::numeric_limits<double>::max();
151
152 if(isMin1 && isMin2) {
153 if(reverseMin)
154 minMatrix[minJ++][minI] = distance;
155 else
156 minMatrix[minI][minJ++] = distance;
157 } else if(isMax1 && isMax2) {
158 if(reverseMax)
159 maxMatrix[maxJ++][maxI] = distance;
160 else
161 maxMatrix[maxI][maxJ++] = distance;
162 } else if(isSad1 && isSad2) {
163 if(reverseSad)
164 sadMatrix[sadJ++][sadI] = distance;
165 else
166 sadMatrix[sadI][sadJ++] = distance;
167 }
168 }
169
170 const double distanceToDiagonal
171 = this->diagonalDistanceFunction(p1, wasserstein);
172 if(isMin1) {
173 if(reverseMin)
174 minMatrix[minJ++][minI] = distanceToDiagonal;
175 else
176 minMatrix[minI][minJ++] = distanceToDiagonal;
177 }
178 if(isMax1) {
179 if(reverseMax)
180 maxMatrix[maxJ++][maxI] = distanceToDiagonal;
181 else
182 maxMatrix[maxI][maxJ++] = distanceToDiagonal;
183 }
184 if(isSad1) {
185 if(reverseSad)
186 sadMatrix[sadJ++][sadI] = distanceToDiagonal;
187 else
188 sadMatrix[sadI][sadJ++] = distanceToDiagonal;
189 }
190
191 if(isMin1)
192 ++minI;
193 if(isMax1)
194 ++maxI;
195 if(isSad1)
196 ++sadI;
197 }
198
199 minJ = 0;
200 maxJ = 0;
201 sadJ = 0;
202
203 // Last row: match remaining J components with diagonal.
204 for(const auto &p3 : CTDiagram2) {
205 if(std::abs(p3.persistence()) < zeroThresh)
206 continue;
207
208 bool isMin2 = (p3.birth.type == CriticalType::Local_minimum
209 || p3.death.type == CriticalType::Local_minimum);
210 bool isMax2 = (p3.birth.type == CriticalType::Local_maximum
211 || p3.death.type == CriticalType::Local_maximum);
212 const bool isSad2 = (p3.birth.type == CriticalType::Saddle1
213 && p3.death.type == CriticalType::Saddle2)
214 || (p3.birth.type == CriticalType::Saddle2
215 && p3.death.type == CriticalType::Saddle1);
216 if(p3.birth.type == CriticalType::Local_minimum
217 && p3.death.type == CriticalType::Local_maximum) {
218 isMin2 = false;
219 isMax2 = true;
220 }
221
222 const double distanceToDiagonal
223 = this->diagonalDistanceFunction(p3, wasserstein);
224 if(isMin2) {
225 if(reverseMin)
226 minMatrix[minJ++][minI] = distanceToDiagonal;
227 else
228 minMatrix[minI][minJ++] = distanceToDiagonal;
229 }
230 if(isMax2) {
231 if(reverseMax)
232 maxMatrix[maxJ++][maxI] = distanceToDiagonal;
233 else
234 maxMatrix[maxI][maxJ++] = distanceToDiagonal;
235 }
236 if(isSad2) {
237 if(reverseSad)
238 sadMatrix[sadJ++][sadI] = distanceToDiagonal;
239 else
240 sadMatrix[sadI][sadJ++] = distanceToDiagonal;
241 }
242 }
243
244 // Last cell
245 if(reverseMin)
246 minMatrix[minJ][minI] = std::numeric_limits<double>::max();
247 else
248 minMatrix[minI][minJ] = std::numeric_limits<double>::max();
249
250 if(reverseMax)
251 maxMatrix[maxJ][maxI] = std::numeric_limits<double>::max();
252 else
253 maxMatrix[maxI][maxJ] = std::numeric_limits<double>::max();
254
255 if(reverseSad)
256 sadMatrix[sadJ][sadI] = std::numeric_limits<double>::max();
257 else
258 sadMatrix[sadI][sadJ] = std::numeric_limits<double>::max();
259}
260
261double ttk::BottleneckDistance::computeGeometricalRange(
262 const ttk::DiagramType &CTDiagram1,
263 const ttk::DiagramType &CTDiagram2) const {
264
265 float minX, minY, minZ, maxX, maxY, maxZ;
266
267 std::array<float, 3> min1{
268 std::numeric_limits<float>::max(),
269 std::numeric_limits<float>::max(),
270 std::numeric_limits<float>::max(),
271 },
272 min2 = min1;
273 std::array<float, 3> max1{
274 std::numeric_limits<float>::lowest(),
275 std::numeric_limits<float>::lowest(),
276 std::numeric_limits<float>::lowest(),
277 },
278 max2 = max1;
279
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]);
287 }
288
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]);
296 }
297
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]);
304
305 const auto square = [](const double a) { return a * a; };
306
307 return std::sqrt(square(maxX - minX) + square(maxY - minY)
308 + square(maxZ - minZ));
309}
310
311double ttk::BottleneckDistance::computeMinimumRelevantPersistence(
312 const ttk::DiagramType &CTDiagram1,
313 const ttk::DiagramType &CTDiagram2) const {
314
315 const auto sp = this->Tolerance;
316 const double s = sp > 0.0 && sp < 100.0 ? sp / 100.0 : 0;
317
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());
322 }
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());
326 }
327
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);
331}
332
333void ttk::BottleneckDistance::computeMinMaxSaddleNumberAndMapping(
334 const ttk::DiagramType &CTDiagram,
335 int &nbMin,
336 int &nbMax,
337 int &nbSaddle,
338 std::vector<int> &minMap,
339 std::vector<int> &maxMap,
340 std::vector<int> &sadMap,
341 const double zeroThresh) const {
342
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)
349 continue;
350
352 && nt2 == CriticalType::Local_maximum) {
353 nbMax++;
354 maxMap.push_back(i);
355 } else {
357 || nt2 == CriticalType::Local_maximum) {
358 nbMax++;
359 maxMap.push_back(i);
360 }
362 || nt2 == CriticalType::Local_minimum) {
363 nbMin++;
364 minMap.push_back(i);
365 }
366 if((nt1 == CriticalType::Saddle1 && nt2 == CriticalType::Saddle2)
367 || (nt1 == CriticalType::Saddle2 && nt2 == CriticalType::Saddle1)) {
368 nbSaddle++;
369 sadMap.push_back(i);
370 }
371 }
372 }
373}
374
375namespace {
376 void solvePWasserstein(std::vector<std::vector<double>> &matrix,
377 std::vector<ttk::MatchingType> &matchings,
379
380 solver.setInput(matrix);
381 solver.run(matchings);
382 solver.clearMatrix();
383 }
384
385 void solveInfinityWasserstein(const int nbRow,
386 const int nbCol,
387 std::vector<std::vector<double>> &matrix,
388 std::vector<ttk::MatchingType> &matchings,
389 ttk::GabowTarjan &solver) {
390
391 // Copy input matrix.
392 auto bottleneckMatrix = matrix;
393
394 // Solve.
395 solver.setInput(nbRow, nbCol, &bottleneckMatrix);
396 solver.run(matchings);
397 solver.clear();
398 }
399} // namespace
400
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 {
409
410 // Input map permutation (so as to ignore transposition later on)
411 const auto &map1 = transposeLocal ? m2 : m1;
412 const auto &map2 = transposeLocal ? m1 : m2;
413
414 double addedPersistence = 0;
415 const auto doTranspose = transposeGlobal ^ transposeLocal;
416
417 for(const auto &t : inputMatchings) {
418
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);
422
423 if(p1 >= map1.size() || p2 >= map2.size()) {
424 addedPersistence = (wasserstein > 0 ? addedPersistence + val
425 : std::max(val, addedPersistence));
426 } else {
427 if(doTranspose) {
428 outputMatchings.emplace_back(map2[p2], map1[p1], val);
429 } else {
430 outputMatchings.emplace_back(map1[p1], map2[p2], val);
431 }
432 }
433 }
434
435 return addedPersistence;
436}
437
438double ttk::BottleneckDistance::distanceFunction(const ttk::PersistencePair &a,
439 const ttk::PersistencePair &b,
440 const int wasserstein) const {
441
442 const int w = std::max(wasserstein, 1); // L_inf not managed.
443
444 // We don't match critical points of different index.
445 // This must be ensured before calling the distance function.
446 const bool isMin1 = a.birth.type == CriticalType::Local_minimum;
447 const bool isMax1 = a.death.type == CriticalType::Local_maximum;
448
449 const std::array<float, 3> coordsAbsDiff{
450 std::abs(a.death.coords[0] - b.death.coords[0]),
451 std::abs(a.death.coords[1] - b.death.coords[1]),
452 std::abs(a.death.coords[2] - b.death.coords[2]),
453 };
454
455 const auto x
456 = ((isMin1 && !isMax1) ? this->PE : this->PS)
457 * Geometry::pow(std::abs(a.birth.sfValue - b.birth.sfValue), w);
458 const auto y
459 = (isMax1 ? this->PE : this->PS)
460 * Geometry::pow(std::abs(a.death.sfValue - b.death.sfValue), w);
461 const double geoDistance
462 = isMax1 || isMin1
463 ? (this->PX * Geometry::pow(coordsAbsDiff[0], w)
464 + this->PY * Geometry::pow(coordsAbsDiff[1], w)
465 + this->PZ * Geometry::pow(coordsAbsDiff[2], w))
466 : (this->PX
467 * Geometry::pow(
468 std::abs(a.birth.coords[0] + a.death.coords[0]) / 2
469 - std::abs(b.birth.coords[0] + b.death.coords[0]) / 2,
470 w)
471 + this->PY
472 * Geometry::pow(
473 std::abs(a.birth.coords[1] + a.death.coords[1]) / 2
474 - std::abs(b.birth.coords[1] + b.death.coords[1]) / 2,
475 w)
476 + this->PZ
477 * Geometry::pow(
478 std::abs(a.birth.coords[2] + a.death.coords[2]) / 2
479 - std::abs(b.birth.coords[2] + b.death.coords[2]) / 2,
480 w));
481
482 const double persDistance = x + y;
483 return Geometry::pow(persDistance + geoDistance, 1.0 / w);
484}
485
486double ttk::BottleneckDistance::diagonalDistanceFunction(
487 const ttk::PersistencePair &a, const int wasserstein) const {
488
489 const int w = std::max(wasserstein, 1);
490 const bool isMin1 = a.birth.type == CriticalType::Local_minimum;
491 const bool isMax1 = a.death.type == CriticalType::Local_maximum;
492 const double infDistance = (isMin1 || isMax1 ? this->PE : this->PS)
493 * Geometry::pow(std::abs(a.persistence()), w);
494 const double geoDistance
495 = (this->PX
496 * Geometry::pow(std::abs(a.death.coords[0] - a.birth.coords[0]), w)
497 + this->PY
498 * Geometry::pow(std::abs(a.death.coords[1] - a.birth.coords[1]), w)
499 + this->PZ
500 * Geometry::pow(std::abs(a.death.coords[2] - a.birth.coords[2]), w));
501
502 return Geometry::pow(infDistance + geoDistance, 1.0 / w);
503}
504
505int ttk::BottleneckDistance::computeBottleneck(
506 const ttk::DiagramType &d1,
507 const ttk::DiagramType &d2,
508 std::vector<MatchingType> &matchings) {
509
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.");
514 }
515 const auto &CTDiagram1 = transposeOriginal ? d2 : d1;
516 const auto &CTDiagram2 = transposeOriginal ? d1 : d2;
517
518 // Check user parameters.
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");
523 return -4;
524 }
525
526 // Needed to limit computation time.
527 const auto zeroThresh
528 = this->computeMinimumRelevantPersistence(CTDiagram1, CTDiagram2);
529
530 // Initialize solvers.
531 std::vector<MatchingType> minMatchings;
532 std::vector<MatchingType> maxMatchings;
533 std::vector<MatchingType> sadMatchings;
534
535 // Initialize cost matrices.
536 int nbRowMin = 0, nbColMin = 0;
537 int nbRowMax = 0, nbColMax = 0;
538 int nbRowSad = 0, nbColSad = 0;
539
540 // Remap for matchings.
541 std::array<std::vector<int>, 3> map1{};
542 std::array<std::vector<int>, 3> map2{};
543
544 this->computeMinMaxSaddleNumberAndMapping(CTDiagram1, nbRowMin, nbRowMax,
545 nbRowSad, map1[0], map1[2], map1[1],
546 zeroThresh);
547 this->computeMinMaxSaddleNumberAndMapping(CTDiagram2, nbColMin, nbColMax,
548 nbColSad, map2[0], map2[2], map2[1],
549 zeroThresh);
550
551 // Automatically transpose if nb rows > nb cols
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);
555
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);
559
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));
566
567 const bool transposeMin = nbRowMin > nbColMin;
568 const bool transposeMax = nbRowMax > nbColMax;
569 const bool transposeSad = nbRowSad > nbColSad;
570
571 Timer t;
572
573 this->buildCostMatrices(CTDiagram1, CTDiagram2, zeroThresh, minMatrix,
574 maxMatrix, sadMatrix, transposeMin, transposeMax,
575 transposeSad, wasserstein);
576
577 if(!isBottleneck) {
578
579 if(nbRowMin > 0 && nbColMin > 0) {
580 AssignmentMunkres<double> solverMin;
581 this->printMsg("Affecting minima...");
582 solvePWasserstein(minMatrix, minMatchings, solverMin);
583 }
584
585 if(nbRowMax > 0 && nbColMax > 0) {
586 AssignmentMunkres<double> solverMax;
587 this->printMsg("Affecting maxima...");
588 solvePWasserstein(maxMatrix, maxMatchings, solverMax);
589 }
590
591 if(nbRowSad > 0 && nbColSad > 0) {
592 AssignmentMunkres<double> solverSad;
593 this->printMsg("Affecting saddles...");
594 solvePWasserstein(sadMatrix, sadMatchings, solverSad);
595 }
596
597 } else {
598
599 // Launch solving for minima.
600 if(nbRowMin > 0 && nbColMin > 0) {
601 GabowTarjan solverMin;
602 this->printMsg("Affecting minima...");
603 solveInfinityWasserstein(
604 minRowColMin, maxRowColMin, minMatrix, minMatchings, solverMin);
605 }
606
607 // Launch solving for maxima.
608 if(nbRowMax > 0 && nbColMax > 0) {
609 GabowTarjan solverMax;
610 this->printMsg("Affecting maxima...");
611 solveInfinityWasserstein(
612 minRowColMax, maxRowColMax, maxMatrix, maxMatchings, solverMax);
613 }
614
615 // Launch solving for saddles.
616 if(nbRowSad > 0 && nbColSad > 0) {
617 GabowTarjan solverSad;
618 this->printMsg("Affecting saddles...");
619 solveInfinityWasserstein(
620 minRowColSad, maxRowColSad, sadMatrix, sadMatchings, solverSad);
621 }
622 }
623
624 this->printMsg("TTK CORE DONE", 1, t.getElapsedTime());
625
626 // Rebuild mappings.
627 // Begin cost computation for unpaired vertices.
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),
635 };
636
637 // TODO [HIGH] do that for embeddings
638 // Recompute matching weights for user-friendly distance.
639 std::array<double, 3> costs{};
640 std::vector<bool> paired1(CTDiagram1.size(), false);
641 std::vector<bool> paired2(CTDiagram2.size(), false);
642
643 // store last matching distance for bottleneck
644 double partialDistance{};
645
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);
649
650 const auto &t1 = CTDiagram1[i];
651 const auto &t2 = CTDiagram2[j];
652 paired1[i] = true;
653 paired2[j] = true;
654
655 partialDistance = this->distanceFunction(t1, t2, wasserstein);
656
657 if(t1.death.type == CriticalType::Local_maximum) {
658 if(!isBottleneck) {
659 costs[2] += partialDistance;
660 } else {
661 costs[2] = std::max(costs[2], partialDistance);
662 }
663 } else if(t1.birth.type == CriticalType::Local_minimum) {
664 if(!isBottleneck) {
665 costs[0] += partialDistance;
666 } else {
667 costs[0] = std::max(costs[0], partialDistance);
668 }
669 } else if(t1.birth.type == CriticalType::Saddle1
670 && t1.death.type == CriticalType::Saddle2) {
671 if(!isBottleneck) {
672 costs[1] += partialDistance;
673 } else {
674 costs[1] = std::max(costs[1], partialDistance);
675 }
676 }
677 }
678
679 const auto affectationD
680 = !isBottleneck ? costs[0] + costs[1] + costs[2] : partialDistance;
681 const auto addedPers
682 = addedPersistence[0] + addedPersistence[1] + addedPersistence[2];
683 this->distance_
684 = !isBottleneck
685 ? Geometry::pow(affectationD + addedPers, 1.0 / wasserstein)
686 : std::max(affectationD, *std::max_element(addedPersistence.begin(),
687 addedPersistence.end()));
688
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_) + ")");
695
696 // aggregate costs per pair type
697 if(!isBottleneck) {
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);
701 } else {
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]);
705 }
706
707 this->costs_ = costs;
708
709 // display results
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_)},
716 };
717 this->printMsg(rows);
718
719 return 0;
720}
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)
Definition Debug.h:364
int run(std::vector< MatchingType > &matchings)
void setInput(int rowSize_, int colSize_, std::vector< std::vector< double > > *C_)
Definition GabowTarjan.h:40
double getElapsedTime()
Definition Timer.h:15
T1 pow(const T1 val, const T2 n)
Definition Geometry.h:456
T distance(const T *p0, const T *p1, const int &dimension=3)
Definition Geometry.cpp:362
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)
Definition ripserpy.cpp:483
T begin(std::pair< T, T > &p)
Definition ripserpy.cpp:479
std::array< float, 3 > coords
double persistence() const
Return the topological persistence of the pair.
printMsg(debug::output::BOLD+" | | | | | . \\ | | (__| | / __/| |_| / __/|__ _|"+debug::output::ENDCOLOR, debug::Priority::PERFORMANCE, debug::LineMode::NEW, stream)