TTK
Loading...
Searching...
No Matches
ripser.cpp
Go to the documentation of this file.
1
10
18
19/*
20
21Ripser: a lean C++ code for computation of Vietoris-Rips persistence barcodes
22
23MIT License
24
25Original Copyright 2015-2018 Ulrich Bauer.
26Modifications Copyright 2018 Christopher Tralie
27
28
29Permission is hereby granted, free of charge, to any person obtaining a copy
30of this software and associated documentation files (the "Software"), to deal
31in the Software without restriction, including without limitation the rights
32to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
33copies of the Software, and to permit persons to whom the Software is
34furnished to do so, subject to the following conditions:
35
36The above copyright notice and this permission notice shall be included in all
37copies or substantial portions of the Software.
38
39THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
40IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
41FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
42AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
43LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
44OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
45SOFTWARE.
46You are under no obligation whatsoever to provide any bug fixes, patches, or
47upgrades to the features, functionality or performance of the source code
48("Enhancements") to anyone; however, if you choose to make your Enhancements
49available either publicly, or directly to the author of this software, without
50imposing a separate written license agreement for such Enhancements, then you
51hereby grant the following license: a non-exclusive, royalty-free perpetual
52license to install, use, modify, prepare derivative works, incorporate into
53other computer software, distribute, and sublicense such enhancements or
54derivative works thereof, in binary and source code form.
55
56
57*/
58
59#include <algorithm>
60#include <cassert>
61#include <chrono>
62#include <cmath>
63#include <fstream>
64#include <numeric>
65#include <queue>
66#include <unordered_map>
67
68#include <ripser.h>
69
70using namespace ripser;
71using namespace ttk::rpd;
72
73#define USE_COEFFICIENTS
74
76coefficient_t get_modulo(const coefficient_t val, const coefficient_t modulus);
78std::vector<coefficient_t> multiplicative_inverse_vector(const coefficient_t m);
79
80#ifdef USE_ROBINHOOD_HASHMAP
81
82#include "robin_hood.h"
83
84template <class Key, class T, class H, class E>
85using hash_map = robin_hood::unordered_map<Key, T, H, E>;
86template <class Key>
87using hash = robin_hood::hash<Key>;
88
89#else
90
91template <class Key, class T, class H, class E>
92using hash_map = std::unordered_map<Key, T, H, E>;
93template <class Key>
94using hash = std::hash<Key>;
95
96#endif
97
98#ifdef INDICATE_PROGRESS
99static const std::chrono::milliseconds time_step(40);
100#endif
101
102static const std::string clear_line("\r\033[K");
103
104static const size_t num_coefficient_bits = 8;
105
106#if defined(TTK_ENABLE_RIPSER_128BITS_IDS) \
107 && (defined(__GNUC__) || defined(__clang__))
108static const index_t max_simplex_index
109 = (__int128(1) << (8 * sizeof(index_t) - 1 - num_coefficient_bits)) - 1;
110
111void check_overflow(index_t i) {
112 if
113#ifdef USE_COEFFICIENTS
114 (i > max_simplex_index)
115#else
116 (i < 0)
117#endif
118 throw std::overflow_error("simplex index " + std::to_string((uint64_t)i)
119 + " in filtration is larger than maximum index");
120}
121#else
122// 1L on windows is ALWAYS 32 bits, when on unix systems is pointer size
123static const index_t max_simplex_index
124 = (uintptr_t(1) << (8 * sizeof(index_t) - 1 - num_coefficient_bits)) - 1;
125
127 if
128#ifdef USE_COEFFICIENTS
129 (i > max_simplex_index)
130#else
131 (i < 0)
132#endif
133 throw std::overflow_error("simplex index " + std::to_string((uint64_t)i)
134 + " in filtration is larger than maximum index "
135 + std::to_string(max_simplex_index));
136}
137#endif
138
140 /* Using flatten matrix */
141 std::vector<index_t> B;
142 size_t offset;
143
144public:
145 binomial_coeff_table(index_t n, index_t k) : B((n + 1) * (k + 1)) {
146 offset = k + 1;
147 for(index_t i = 0; i <= n; ++i) {
148 B[i * offset] = 1;
149 for(index_t j = 1; j < std::min(i, k + 1); ++j)
150 B[i * offset + j]
151 = B[(i - 1) * offset + j - 1] + B[(i - 1) * offset + j];
152 if(i <= k)
153 B[i * offset + i] = 1;
154 check_overflow(B[i * offset + std::min(i >> 1, k)]);
155 }
156 }
157
159 assert(n < index_t(B.size() / offset) && k < index_t(offset) && n >= k - 1);
160 return B[n * offset + k];
161 }
162};
163
164/* Modulo operator is expensive, using a mask when modulus is equal 2
165 * is much less expensive and speed-ups where observed
166 */
168 return (modulus == 2) ? val & 1 : val % modulus;
169}
170
172 return n > modulus / 2 ? n - modulus : n;
173}
174
175std::vector<coefficient_t>
177 std::vector<coefficient_t> inverse(m);
178 inverse[1] = 1;
179 // m = a * (m / a) + m % a
180 // Multiplying with inverse(a) * inverse(m % a):
181 // 0 = inverse(m % a) * (m / a) + inverse(a) (mod m)
182 for(coefficient_t a = 2; a < m; ++a)
183 inverse[a] = m - (inverse[m % a] * (m / a)) % m;
184 return inverse;
185}
186
187#ifdef USE_COEFFICIENTS
188
189// https://stackoverflow.com/a/3312896/13339777
190#ifdef _MSC_VER
191#define PACK(...) __pragma(pack(push, 1)) __VA_ARGS__ __pragma(pack(pop))
192#else
193#define PACK(...) __VA_ARGS__ __attribute__((__packed__))
194#endif
195
196PACK(struct entry_t {
197 index_t index : 8 * sizeof(index_t) - num_coefficient_bits;
198 index_t coefficient : num_coefficient_bits;
199 entry_t(index_t _index, coefficient_t _coefficient)
200 : index(_index), coefficient(_coefficient) {
201 }
202 entry_t(index_t _index) : index(_index), coefficient(0) {
203 }
204 entry_t() : index(0), coefficient(0) {
205 }
206});
207
208static_assert(sizeof(entry_t) == sizeof(index_t),
209 "size of entry_t is not the same as index_t");
210
211entry_t make_entry(index_t i, coefficient_t c);
212index_t get_index(const entry_t &e);
213index_t get_coefficient(const entry_t &e);
214void set_coefficient(entry_t &e, const coefficient_t c);
215std::ostream &operator<<(std::ostream &stream, const entry_t &e);
216
218 return entry_t(i, c);
219}
220index_t get_index(const entry_t &e) {
221 return e.index;
222}
223index_t get_coefficient(const entry_t &e) {
224 return e.coefficient;
225}
226void set_coefficient(entry_t &e, const coefficient_t c) {
227 e.coefficient = c;
228}
229
230std::ostream &operator<<(std::ostream &stream, const entry_t &e) {
231 stream << get_index(e) << ":" << get_coefficient(e);
232 return stream;
233}
234
235#else
236
237using entry_t = index_t;
238index_t get_index(const entry_t &i);
239index_t get_coefficient(const entry_t & /*i*/);
240entry_t make_entry(index_t _index, coefficient_t /*_value*/);
241void set_coefficient(entry_t & /*e*/, const coefficient_t /*c*/);
242
243index_t get_index(const entry_t &i) {
244 return i;
245}
246index_t get_coefficient(const entry_t & /*i*/) {
247 return 1;
248}
249entry_t make_entry(index_t _index, coefficient_t /*_value*/) {
250 return entry_t(_index);
251}
252void set_coefficient(entry_t & /*e*/, const coefficient_t /*c*/) {
253}
254
255#endif
256
257const entry_t &get_entry(const entry_t &e);
258const entry_t &get_entry(const entry_t &e) {
259 return e;
260}
261
262using diameter_index_t = std::pair<value_t, index_t>;
266 return i.first;
267}
269 return i.second;
270}
271
272using index_diameter_t = std::pair<index_t, value_t>;
276 return i.first;
277}
279 return i.second;
280}
281
282struct diameter_entry_t : std::pair<value_t, entry_t> {
283 using std::pair<value_t, entry_t>::pair;
284 diameter_entry_t() = default;
286 index_t _index,
287 coefficient_t _coefficient)
288 : diameter_entry_t(_diameter, make_entry(_index, _coefficient)) {
289 }
290 diameter_entry_t(const diameter_index_t &_diameter_index,
291 coefficient_t _coefficient)
292 : diameter_entry_t(get_diameter(_diameter_index),
293 make_entry(get_index(_diameter_index), _coefficient)) {
294 }
295 diameter_entry_t(const index_t &_index) : diameter_entry_t(0, _index, 0) {
296 }
297};
298
299const entry_t &get_entry(const diameter_entry_t &p);
300entry_t &get_entry(diameter_entry_t &p);
303const value_t &get_diameter(const diameter_entry_t &p);
305
306const entry_t &get_entry(const diameter_entry_t &p) {
307 return p.second;
308}
310 return p.second;
311}
313 return get_index(get_entry(p));
314}
319 return p.first;
320}
324
325template <typename Entry>
327 bool operator()(const Entry &a, const Entry &b) {
328 return (get_diameter(a) > get_diameter(b))
329 || ((get_diameter(a) == get_diameter(b))
330 && (get_index(a) < get_index(b)));
331 }
332};
333
338
339template <compressed_matrix_layout Layout>
341public:
342 std::vector<value_t> distances;
343 std::vector<value_t *> rows;
344
345 compressed_distance_matrix(std::vector<value_t> &&_distances)
346 : distances(std::move(_distances)),
347 rows((1 + std::sqrt(1 + 8 * distances.size())) / 2) {
348 assert(distances.size() == size() * (size() - 1) / 2);
349 init_rows();
350 }
351
352 template <typename DistanceMatrix>
353 compressed_distance_matrix(const DistanceMatrix &mat)
354 : distances(mat.size() * (mat.size() - 1) / 2), rows(mat.size()) {
355 init_rows();
356
357 for(size_t i = 1; i < size(); ++i)
358 for(size_t j = 0; j < i; ++j)
359 rows[i][j] = mat(i, j);
360 }
361
362 value_t operator()(const index_t i, const index_t j) const;
363
364 size_t size() const {
365 return rows.size();
366 }
367 void init_rows();
368};
369
375template <>
377 value_t *pointer = &distances[0];
378 for(size_t i = 1; i < size(); ++i) {
379 rows[i] = pointer;
380 pointer += i;
381 }
382}
384template <>
386 value_t *pointer = &distances[0] - 1;
387 for(size_t i = 0; i < size() - 1; ++i) {
388 rows[i] = pointer;
389 pointer += size() - i - 2;
390 }
391}
392
393template <>
395 const index_t j) const {
396 return i == j ? 0 : i < j ? rows[j][i] : rows[i][j];
397}
398
399template <>
401 const index_t j) const {
402 return i == j ? 0 : i > j ? rows[j][i] : rows[i][j];
406 std::vector<std::vector<index_diameter_t>> neighbors;
409 mutable std::vector<std::vector<index_diameter_t>::const_reverse_iterator>
411 mutable std::vector<std::vector<index_diameter_t>::const_reverse_iterator>
413
415 std::vector<std::vector<index_diameter_t>> &&_neighbors, index_t _num_edges)
416 : neighbors(std::move(_neighbors)), num_edges(_num_edges) {
417 }
419 template <typename DistanceMatrix>
420 sparse_distance_matrix(const DistanceMatrix &mat, const value_t threshold)
421 : neighbors(mat.size()), num_edges(0) {
422 for(size_t i = 0; i < size(); ++i)
423 for(size_t j = 0; j < size(); ++j)
424 if(i != j && mat(i, j) <= threshold) {
425 ++num_edges;
426 neighbors[i].emplace_back(j, mat(i, j));
427 }
429
430 size_t size() const {
431 return neighbors.size();
432 }
433
434 // not in the original ripser: for keeping only critical edges instead of
435 // simplices
436 value_t operator()(const index_t i, const index_t j) const {
437 for(auto const &[index, diameter] : neighbors[i]) {
438 if(index == j)
439 return diameter;
440 }
441 return 0;
442 }
446 std::vector<std::vector<value_t>> points;
447
448 euclidean_distance_matrix(std::vector<std::vector<value_t>> &&_points)
449 : points(std::move(_points)) {
451
452 value_t operator()(const index_t i, const index_t j) const {
453 assert(i < index_t(points.size()));
454 assert(j < index_t(points.size()));
455 return std::sqrt(std::inner_product(
456 points[i].begin(), points[i].end(), points[j].begin(), value_t(),
457 std::plus<value_t>(),
458 [](value_t u, value_t v) { return (u - v) * (u - v); }));
460
461 size_t size() const {
462 return points.size();
463 }
465
466class union_find {
467 std::vector<index_t> parent;
468 std::vector<uint8_t> rank;
470public:
471 union_find(const index_t n) : parent(n), rank(n, 0) {
472 for(index_t i = 0; i < n; ++i)
473 parent[i] = i;
475
476 index_t find(index_t x) {
477 index_t y = x, z;
478 while((z = parent[y]) != y)
479 y = z;
480 while((z = parent[x]) != y) {
481 parent[x] = y;
482 x = z;
483 }
484 return z;
486
487 void link(index_t x, index_t y) {
488 if((x = find(x)) == (y = find(y)))
489 return;
490 if(rank[x] > rank[y])
491 parent[y] = x;
492 else {
493 parent[x] = y;
494 if(rank[x] == rank[y])
495 ++rank[y];
496 }
497 }
498};
500template <typename T>
501T begin(std::pair<T, T> &p) {
502 return p.first;
504template <typename T>
505T end(std::pair<T, T> &p) {
506 return p.second;
507}
509template <typename ValueType>
511 std::vector<size_t> bounds;
512 std::vector<ValueType> entries;
513
514 using iterator = typename std::vector<ValueType>::iterator;
515 using iterator_pair = std::pair<iterator, iterator>;
517public:
518 size_t size() const {
519 return bounds.size();
521
522 iterator_pair subrange(const index_t index) {
523 return {entries.begin() + (index == 0 ? 0 : bounds[index - 1]),
524 entries.begin() + bounds[index]};
526
527 void append_column() {
528 bounds.push_back(entries.size());
530
531 void push_back(const ValueType e) {
532 assert(0 < size());
533 entries.push_back(e);
534 ++bounds.back();
536
537 void pop_back() {
538 assert(0 < size());
539 entries.pop_back();
540 --bounds.back();
541 }
542};
544template <typename DistanceMatrix>
545class Ripser {
546 const DistanceMatrix dist;
547 index_t n, dim_max;
548 const value_t threshold;
549 const float ratio;
550 const bool critical_edges_only; // not in the original ripser
551 const bool infinite_pairs; // not in the original ripser
552 const coefficient_t modulus;
553 const binomial_coeff_table binomial_coeff;
554 const std::vector<coefficient_t> multiplicative_inverse;
555 mutable std::vector<diameter_entry_t> cofacet_entries;
556
557 struct entry_hash {
558 std::size_t operator()(const entry_t &e) const {
559#if defined(USE_ROBINHOOD_HASHMAP)
560 return robin_hood::hash<index_t>()(::get_index(e));
561#else
562 return std::hash<index_t>()(::get_index(e));
563#endif
564 }
565 };
566
567 struct equal_index {
568 bool operator()(const entry_t &e, const entry_t &f) const {
569 return ::get_index(e) == ::get_index(f);
570 }
571 };
572
575public:
576 Ripser(DistanceMatrix &&_dist,
577 index_t _dim_max,
578 value_t _threshold,
579 float _ratio,
580 bool _critical_edges_only,
581 bool _infinite_pairs,
582 coefficient_t _modulus)
583 : dist(std::move(_dist)), n(dist.size()), dim_max(_dim_max),
584 threshold(_threshold), ratio(_ratio),
585 critical_edges_only(_critical_edges_only),
586 infinite_pairs(_infinite_pairs), modulus(_modulus),
587 binomial_coeff(n, dim_max + 2),
588 multiplicative_inverse(multiplicative_inverse_vector(_modulus)) {
589 }
591 index_t
592 get_max_vertex(const index_t idx, const index_t k, const index_t n_) const {
593 auto top = n_;
594 auto bottom = k - 1;
595 if(binomial_coeff(top, k) > idx) {
596 index_t count = top - bottom;
597 index_t step;
598 index_t mid;
599 while(count > 0) {
600 step = count >> 1;
601 mid = top - step;
602 if(binomial_coeff(mid, k) > idx) {
603 top = mid - 1;
604 count -= step + 1;
605 } else
606 count = step;
607 }
608 }
609 return top;
611
612 index_t get_edge_index(const index_t i, const index_t j) const {
613 return binomial_coeff(i, 2) + j;
614 }
616 template <typename OutputIterator>
617 OutputIterator get_simplex_vertices(index_t idx,
618 const index_t dim,
619 index_t n_,
620 OutputIterator out) const {
621 --n_;
622 for(index_t k = dim + 1; k > 0; --k) {
623 n_ = get_max_vertex(idx, k, n_);
624 *out++ = n_;
625 idx -= binomial_coeff(n_, k);
626 }
627 return out;
628 }
629
630 class simplex_coboundary_enumerator;
632 void
633 assemble_columns_to_reduce(std::vector<diameter_index_t> &simplices,
634 std::vector<diameter_index_t> &columns_to_reduce,
635 entry_hash_map &pivot_column_index,
636 index_t dim) {
637#ifdef INDICATE_PROGRESS
638 std::cerr << clear_line << "assembling columns" << std::flush;
639 std::chrono::steady_clock::time_point next
640 = std::chrono::steady_clock::now() + time_step;
641#endif
642 --dim;
643 columns_to_reduce.clear();
644 std::vector<diameter_index_t> next_simplices;
645
646 for(diameter_index_t &simplex : simplices) {
648 diameter_entry_t(simplex, 1), dim, *this);
649
650 while(cofacets.has_next(false)) {
651#ifdef INDICATE_PROGRESS
652 if(std::chrono::steady_clock::now() > next) {
653 std::cerr << clear_line << "assembling " << next_simplices.size()
654 << " columns (processing "
655 << std::distance(&simplices[0], &simplex) << "/"
656 << simplices.size() << " simplices)" << std::flush;
657 next = std::chrono::steady_clock::now() + time_step;
658 }
659#endif
660 auto cofacet = cofacets.next();
661 if(get_diameter(cofacet) <= threshold) {
662 if(dim != dim_max)
663 next_simplices.emplace_back(
664 get_diameter(cofacet), get_index(cofacet));
665
666 if(pivot_column_index.find(get_entry(cofacet))
667 == pivot_column_index.end())
668 columns_to_reduce.emplace_back(
669 get_diameter(cofacet), get_index(cofacet));
670 }
671 }
672 }
673
674 simplices.swap(next_simplices);
675
676#ifdef INDICATE_PROGRESS
677 std::cerr << clear_line << "sorting " << columns_to_reduce.size()
678 << " columns" << std::flush;
679#endif
680
681 std::sort(columns_to_reduce.begin(), columns_to_reduce.end(),
683
684#ifdef INDICATE_PROGRESS
685 std::cerr << clear_line << std::flush;
686#endif
687 }
689 template <typename PersistenceType>
690 void compute_dim_0_pairs(std::vector<diameter_index_t> &edges,
691 std::vector<diameter_index_t> &columns_to_reduce,
692 PersistenceType &ph) {
693 union_find dset(n);
694
695 edges = get_edges();
696 std::sort(edges.rbegin(), edges.rend(),
698 std::vector<index_t> vertices_of_edge(2);
699 for(auto e : edges) {
700 get_simplex_vertices(get_index(e), 1, n, vertices_of_edge.rbegin());
701 const index_t u = dset.find(vertices_of_edge[0]),
702 v = dset.find(vertices_of_edge[1]);
703
704 if(u != v) {
705 dset.link(u, v);
706 if(get_diameter(e) != 0) {
707 if constexpr(std::is_same_v<PersistenceType,
709 const int merged
710 = (u == dset.find(vertices_of_edge[0]) ? vertices_of_edge[1]
711 : vertices_of_edge[0]);
712 ph[0].emplace_back(FiltratedSimplex{{merged}, 0.},
713 FiltratedSimplex{{int(vertices_of_edge[0]),
714 int(vertices_of_edge[1])},
715 get_diameter(e)});
716 } else if constexpr(std::is_same_v<PersistenceType, EdgeSets3>)
717 ph[0].emplace_back(vertices_of_edge[0], vertices_of_edge[1]);
718 }
719 } else
720 columns_to_reduce.push_back(e);
721 }
722 std::reverse(columns_to_reduce.begin(), columns_to_reduce.end());
723
724 if constexpr(std::is_same_v<PersistenceType, MultidimensionalDiagram>) {
725 for(index_t i = 0; i < n; ++i) {
726 if(dset.find(i) == i)
727 ph[0].emplace_back(
728 FiltratedSimplex{{int(i)}, 0.}, FiltratedSimplex{{-1}, inf});
729 }
730 }
731 }
733 template <typename Column>
734 diameter_entry_t pop_pivot(Column &column) {
735 diameter_entry_t pivot(-1);
736#ifdef USE_COEFFICIENTS
737 while(!column.empty()) {
738 if(get_coefficient(pivot) == 0)
739 pivot = column.top();
740 else if(get_index(column.top()) != get_index(pivot))
741 return pivot;
742 else
744 + get_coefficient(column.top())),
745 modulus));
746 column.pop();
747 }
748 return (get_coefficient(pivot) == 0) ? -1 : pivot;
749#else
750 while(!column.empty()) {
751 pivot = column.top();
752 column.pop();
753 if(column.empty() || get_index(column.top()) != get_index(pivot))
754 return pivot;
755 column.pop();
756 }
757 return -1;
758#endif
759 }
761 template <typename Column>
762 diameter_entry_t get_pivot(Column &column) {
763 diameter_entry_t result = pop_pivot(column);
764 if(get_index(result) != -1)
765 column.push(result);
766 return result;
767 }
768
769 template <typename Column>
772 Column &working_coboundary,
773 const index_t &dim,
774 entry_hash_map &pivot_column_index) {
775 bool check_for_emergent_pair = true;
776 cofacet_entries.clear();
777 simplex_coboundary_enumerator cofacets(simplex, dim, *this);
778 while(cofacets.has_next()) {
779 diameter_entry_t cofacet = cofacets.next();
780 if(get_diameter(cofacet) <= threshold) {
781 cofacet_entries.push_back(cofacet);
782 if(check_for_emergent_pair
783 && (get_diameter(simplex) == get_diameter(cofacet))) {
784 if(pivot_column_index.find(get_entry(cofacet))
785 == pivot_column_index.end())
786 return cofacet;
787 check_for_emergent_pair = false;
788 }
789 }
790 }
791 for(auto cofacet : cofacet_entries)
792 working_coboundary.push(cofacet);
793 return get_pivot(working_coboundary);
794 }
796 template <typename Column>
797 void add_simplex_coboundary(const diameter_entry_t simplex,
798 const index_t &dim,
799 Column &working_reduction_column,
800 Column &working_coboundary) {
801 working_reduction_column.push(simplex);
802 simplex_coboundary_enumerator cofacets(simplex, dim, *this);
803 while(cofacets.has_next()) {
804 diameter_entry_t cofacet = cofacets.next();
805 if(get_diameter(cofacet) <= threshold)
806 working_coboundary.push(cofacet);
807 }
808 }
809
810 template <typename Column>
811 void
813 const std::vector<diameter_index_t> &columns_to_reduce,
814 const size_t index_column_to_add,
815 const coefficient_t factor,
816 const size_t &dim,
817 Column &working_reduction_column,
818 Column &working_coboundary) {
819 diameter_entry_t column_to_add(
820 columns_to_reduce[index_column_to_add], factor);
822 column_to_add, dim, working_reduction_column, working_coboundary);
823
824 for(diameter_entry_t simplex :
825 reduction_matrix.subrange(index_column_to_add)) {
826 set_coefficient(simplex, get_coefficient(simplex) * factor % modulus);
828 simplex, dim, working_reduction_column, working_coboundary);
829 }
831
832 using working_t
833 = std::priority_queue<diameter_entry_t,
834 std::vector<diameter_entry_t>,
837 // not in the original ripser: for keeping only critical edges instead of
838 // simplices
839 Edge find_longest_edge(const Simplex &vertices) const {
840 const double l1 = dist(vertices[0], vertices[1]);
841 const double l2 = dist(vertices[1], vertices[2]);
842 const double l3 = dist(vertices[0], vertices[2]);
843 if(l1 > l2 && l1 > l3)
844 return {vertices[0], vertices[1]};
845 else if(l2 > l3)
846 return {vertices[1], vertices[2]};
847 else
848 return {vertices[0], vertices[2]};
850
851 template <typename PersistenceType>
852 void compute_pairs(std::vector<diameter_index_t> &columns_to_reduce,
853 entry_hash_map &pivot_column_index,
854 index_t dim,
855 PersistenceType &ph) {
857 size_t index_column_to_add;
858
859#ifdef INDICATE_PROGRESS
860 std::chrono::steady_clock::time_point next
861 = std::chrono::steady_clock::now() + time_step;
862#endif
863
864 for(size_t index_column_to_reduce = 0;
865 index_column_to_reduce < columns_to_reduce.size();
866 ++index_column_to_reduce) {
867 diameter_entry_t column_to_reduce(
868 columns_to_reduce[index_column_to_reduce], 1);
869 value_t diameter = get_diameter(column_to_reduce);
870
871 reduction_matrix.append_column();
872
873 working_t working_reduction_column;
874 working_t working_coboundary;
875
876 working_reduction_column.push(column_to_reduce);
877
879 column_to_reduce, working_coboundary, dim, pivot_column_index);
880
881 while(true) {
882#ifdef INDICATE_PROGRESS
883 if(std::chrono::steady_clock::now() > next) {
884 std::cerr << clear_line << "reducing column "
885 << index_column_to_reduce + 1 << "/"
886 << columns_to_reduce.size() << " (diameter " << diameter
887 << ")" << std::flush;
888 next = std::chrono::steady_clock::now() + time_step;
889 }
890#endif
891 if(get_index(pivot) != -1) {
892 auto pair = pivot_column_index.find(get_entry(pivot));
893 if(pair != pivot_column_index.end()) {
894 entry_t other_pivot = pair->first;
895 index_column_to_add = pair->second;
896 coefficient_t factor
897 = modulus
898 - get_modulo(
899 get_coefficient(pivot)
900 * multiplicative_inverse[get_coefficient(other_pivot)],
901 modulus);
902
903 add_coboundary(reduction_matrix, columns_to_reduce,
904 index_column_to_add, factor, dim,
905 working_reduction_column, working_coboundary);
906
907 pivot = get_pivot(working_coboundary);
908 } else {
909 const value_t death = get_diameter(pivot);
910 if(death > diameter * ratio) {
911 Simplex vertices_birth(dim + 1), vertices_death(dim + 2);
913 get_index(column_to_reduce), dim, n, vertices_birth.rbegin());
915 get_index(pivot), dim + 1, n, vertices_death.rbegin());
916 if constexpr(std::is_same_v<PersistenceType,
918 if(critical_edges_only) {
919 const Edge longest = find_longest_edge(vertices_death);
920 ph[dim].emplace_back(
921 FiltratedSimplex{vertices_birth, diameter},
922 FiltratedSimplex{{longest.first, longest.second}, death});
923 } else
924 ph[dim].emplace_back(
925 FiltratedSimplex{vertices_birth, diameter},
926 FiltratedSimplex{vertices_death, death});
927 } else if constexpr(std::is_same_v<PersistenceType, EdgeSets3>) {
928 ph[2 * dim - 1].emplace_back(
929 vertices_birth[0], vertices_birth[1]);
930 ph[2 * dim].emplace_back(find_longest_edge(vertices_death));
931 }
932 }
933
934 pivot_column_index.insert(
935 {get_entry(pivot), index_column_to_reduce});
936
937 pop_pivot(working_reduction_column);
938 while(true) {
939 diameter_entry_t e = pop_pivot(working_reduction_column);
940
941 if(get_index(e) == -1)
942 break;
943 assert(get_coefficient(e) > 0);
944 reduction_matrix.push_back(e);
945 }
946 break;
947 }
948 } else {
949 Simplex vertices_birth(dim + 1);
951 get_index(column_to_reduce), dim, n, vertices_birth.rbegin());
952 if constexpr(std::is_same_v<PersistenceType,
954 if(infinite_pairs)
955 ph[dim].emplace_back(FiltratedSimplex{vertices_birth, diameter},
956 FiltratedSimplex{{-1}, inf});
957 }
958 break;
959 }
960 }
961 }
962#ifdef INDICATE_PROGRESS
963 std::cerr << clear_line << std::flush;
964#endif
965 }
966
967 std::vector<diameter_index_t> get_edges();
968
969 template <typename PersistenceType>
970 void compute_barcodes(PersistenceType &ph) {
971 std::vector<diameter_index_t> simplices, columns_to_reduce;
972
973 /* prevent cases where dim_max < 0 */
974 if(dim_max < 0)
975 dim_max = 0;
976
977 compute_dim_0_pairs(simplices, columns_to_reduce, ph);
978
979 for(index_t dim = 1; dim <= dim_max; ++dim) {
980 entry_hash_map pivot_column_index;
981 pivot_column_index.reserve(columns_to_reduce.size());
982
983 compute_pairs(columns_to_reduce, pivot_column_index, dim, ph);
984
985 if(dim < dim_max)
987 simplices, columns_to_reduce, pivot_column_index, dim + 1);
988 }
989 }
990};
992template <>
994private:
995 index_t idx_below, idx_above, v, k;
996 std::vector<index_t> vertices;
997 const diameter_entry_t simplex;
998 const coefficient_t modulus;
1000 const binomial_coeff_table &binomial_coeff;
1002public:
1004 const diameter_entry_t _simplex,
1005 index_t _dim,
1007 : idx_below(get_index(_simplex)), idx_above(0), v(parent.n - 1),
1008 k(_dim + 1), vertices(_dim + 1), simplex(_simplex),
1009 modulus(parent.modulus), dist(parent.dist),
1010 binomial_coeff(parent.binomial_coeff) {
1011 parent.get_simplex_vertices(
1012 get_index(_simplex), _dim, parent.n, vertices.begin());
1014
1015 bool has_next(bool all_cofacets = true) {
1016 return (v >= k && (all_cofacets || binomial_coeff(v, k) > idx_below));
1018
1019 diameter_entry_t next() {
1020 while((binomial_coeff(v, k) <= idx_below)) {
1021 idx_below -= binomial_coeff(v, k);
1022 idx_above += binomial_coeff(v, k + 1);
1023 --v;
1024 --k;
1025 assert(k != -1);
1026 }
1027 value_t cofacet_diameter = get_diameter(simplex);
1028 for(index_t w : vertices)
1029 cofacet_diameter = std::max(cofacet_diameter, dist(v, w));
1030 index_t cofacet_index = idx_above + binomial_coeff(v--, k + 1) + idx_below;
1031 coefficient_t cofacet_coefficient
1032 = (k & 1 ? modulus - 1 : 1) * get_coefficient(simplex) % modulus;
1033 return diameter_entry_t(
1034 cofacet_diameter, cofacet_index, cofacet_coefficient);
1035 }
1036};
1037
1038template <>
1039class Ripser<sparse_distance_matrix>::simplex_coboundary_enumerator {
1040 index_t idx_below, idx_above, k;
1041 std::vector<index_t> vertices;
1042 const diameter_entry_t simplex;
1043 const coefficient_t modulus;
1044 const sparse_distance_matrix &dist;
1045 const binomial_coeff_table &binomial_coeff;
1046 std::vector<std::vector<index_diameter_t>::const_reverse_iterator>
1047 &neighbor_it;
1048 std::vector<std::vector<index_diameter_t>::const_reverse_iterator>
1049 &neighbor_end;
1050 index_diameter_t neighbor;
1052public:
1054 const index_t _dim,
1055 const Ripser<sparse_distance_matrix> &parent)
1056 : idx_below(get_index(_simplex)), idx_above(0), k(_dim + 1),
1057 vertices(_dim + 1), simplex(_simplex), modulus(parent.modulus),
1058 dist(parent.dist), binomial_coeff(parent.binomial_coeff),
1059 neighbor_it(dist.neighbor_it), neighbor_end(dist.neighbor_end) {
1060 neighbor_it.clear();
1061 neighbor_end.clear();
1062
1063 parent.get_simplex_vertices(idx_below, _dim, parent.n, vertices.rbegin());
1064
1065 for(auto v : vertices) {
1066 neighbor_it.push_back(dist.neighbors[v].rbegin());
1067 neighbor_end.push_back(dist.neighbors[v].rend());
1068 }
1070
1071 bool has_next(bool all_cofacets = true) {
1072 for(auto &it0 = neighbor_it[0], &end0 = neighbor_end[0]; it0 != end0;
1073 ++it0) {
1074 neighbor = *it0;
1075 for(size_t idx = 1; idx < neighbor_it.size(); ++idx) {
1076 auto &it = neighbor_it[idx], end = neighbor_end[idx];
1077 while(get_index(*it) > get_index(neighbor))
1078 if(++it == end)
1079 return false;
1080 if(get_index(*it) != get_index(neighbor))
1081 goto continue_outer;
1082 else
1083 neighbor = std::max(neighbor, *it);
1084 }
1085 while(k > 0 && vertices[k - 1] > get_index(neighbor)) {
1086 if(!all_cofacets)
1087 return false;
1088 idx_below -= binomial_coeff(vertices[k - 1], k);
1089 idx_above += binomial_coeff(vertices[k - 1], k + 1);
1090 --k;
1091 }
1092 return true;
1093 continue_outer:;
1094 }
1095 return false;
1097
1098 diameter_entry_t next() {
1099 ++neighbor_it[0];
1100 value_t cofacet_diameter
1101 = std::max(get_diameter(simplex), get_diameter(neighbor));
1102 index_t cofacet_index
1103 = idx_above + binomial_coeff(get_index(neighbor), k + 1) + idx_below;
1104 coefficient_t cofacet_coefficient
1105 = (k & 1 ? modulus - 1 : 1) * get_coefficient(simplex) % modulus;
1106 return diameter_entry_t(
1107 cofacet_diameter, cofacet_index, cofacet_coefficient);
1108 }
1109};
1110
1111template <>
1112std::vector<diameter_index_t>
1114 std::vector<diameter_index_t> edges;
1115 std::vector<index_t> vertices(2);
1116 for(index_t index = binomial_coeff(n, 2); index-- > 0;) {
1117 get_simplex_vertices(index, 1, dist.size(), vertices.rbegin());
1118 value_t length = dist(vertices[0], vertices[1]);
1119 if(length <= threshold)
1120 edges.emplace_back(length, index);
1121 }
1122 return edges;
1123}
1125template <>
1126std::vector<diameter_index_t> Ripser<sparse_distance_matrix>::get_edges() {
1127 std::vector<diameter_index_t> edges;
1128 for(index_t i = 0; i < n; ++i)
1129 for(auto n_ : dist.neighbors[i]) {
1130 index_t j = get_index(n_);
1131 if(i > j)
1132 edges.emplace_back(get_diameter(n_), get_edge_index(i, j));
1133 }
1134 return edges;
1135}
1137template <typename PersistenceType>
1138void ripser::ripser(std::vector<std::vector<value_t>> points,
1139 PersistenceType &ph,
1140 value_t threshold,
1141 index_t dim_max,
1142 bool distanceMatrix,
1143 bool criticalEdgesOnly,
1144 bool infinitePairs,
1145 coefficient_t modulus) {
1146 const double ratio = 1.;
1147
1148 if constexpr(std::is_same_v<PersistenceType, MultidimensionalDiagram>)
1149 ph = MultidimensionalDiagram(dim_max + 1);
1150 else if constexpr(std::is_same_v<PersistenceType, EdgeSets3>)
1151 dim_max = std::max(dim_max, static_cast<index_t>(1));
1152
1153 if(!distanceMatrix) {
1154 if(threshold < inf) {
1155 const euclidean_distance_matrix eucl_dist(std::move(points));
1156 sparse_distance_matrix dist(eucl_dist, threshold);
1157 Ripser ripser(std::move(dist), dim_max, threshold, ratio,
1158 criticalEdgesOnly, infinitePairs, modulus);
1159 ripser.compute_barcodes(ph);
1160 } else {
1162 euclidean_distance_matrix(std::move(points)));
1163 Ripser ripser(std::move(dist), dim_max, threshold, ratio,
1164 criticalEdgesOnly, infinitePairs, modulus);
1165 ripser.compute_barcodes(ph);
1166 }
1167 } else {
1168 if(threshold < inf) {
1169 const compressed_lower_distance_matrix lower_dist(std::move(points[0]));
1170 sparse_distance_matrix dist(lower_dist, threshold);
1171 Ripser ripser(std::move(dist), dim_max, threshold, ratio,
1172 criticalEdgesOnly, infinitePairs, modulus);
1173 ripser.compute_barcodes(ph);
1174 } else {
1175 compressed_lower_distance_matrix dist(std::move(points[0]));
1176 Ripser ripser(std::move(dist), dim_max, threshold, ratio,
1177 criticalEdgesOnly, infinitePairs, modulus);
1178 ripser.compute_barcodes(ph);
1179 }
1180 }
1181}
1182template void ripser::ripser(std::vector<std::vector<value_t>> points,
1184 value_t threshold,
1185 index_t dim_max,
1186 bool distanceMatrix,
1187 bool criticalEdgesOnly,
1188 bool infinitePairs,
1189 coefficient_t modulus);
1190template void ripser::ripser(std::vector<std::vector<value_t>> points,
1191 EdgeSets3 &ph,
1192 value_t threshold,
1193 index_t dim_max,
1194 bool distanceMatrix,
1195 bool criticalEdgesOnly,
1196 bool infinitePairs,
1197 coefficient_t modulus);
simplex_coboundary_enumerator(const diameter_entry_t _simplex, index_t _dim, const Ripser< compressed_lower_distance_matrix > &parent)
Definition ripser.cpp:1001
bool has_next(bool all_cofacets=true)
Definition ripser.cpp:1013
index_t get_edge_index(const index_t i, const index_t j) const
Definition ripser.cpp:610
void compute_pairs(std::vector< diameter_index_t > &columns_to_reduce, entry_hash_map &pivot_column_index, index_t dim, PersistenceType &ph)
Definition ripser.cpp:849
OutputIterator get_simplex_vertices(index_t idx, const index_t dim, index_t n_, OutputIterator out) const
Definition ripser.cpp:615
std::priority_queue< diameter_entry_t, std::vector< diameter_entry_t >, greater_diameter_or_smaller_index< diameter_entry_t > > working_t
Definition ripser.cpp:830
Edge find_longest_edge(const Simplex &vertices) const
Definition ripser.cpp:836
void compute_dim_0_pairs(std::vector< diameter_index_t > &edges, std::vector< diameter_index_t > &columns_to_reduce, PersistenceType &ph)
Definition ripser.cpp:688
void assemble_columns_to_reduce(std::vector< diameter_index_t > &simplices, std::vector< diameter_index_t > &columns_to_reduce, entry_hash_map &pivot_column_index, index_t dim)
Definition ripser.cpp:631
diameter_entry_t init_coboundary_and_get_pivot(const diameter_entry_t simplex, Column &working_coboundary, const index_t &dim, entry_hash_map &pivot_column_index)
Definition ripser.cpp:769
void compute_barcodes(PersistenceType &ph)
Definition ripser.cpp:967
Ripser(DistanceMatrix &&_dist, index_t _dim_max, value_t _threshold, float _ratio, bool _critical_edges_only, bool _infinite_pairs, coefficient_t _modulus)
Definition ripser.cpp:574
void add_coboundary(compressed_sparse_matrix< diameter_entry_t > &reduction_matrix, const std::vector< diameter_index_t > &columns_to_reduce, const size_t index_column_to_add, const coefficient_t factor, const size_t &dim, Column &working_reduction_column, Column &working_coboundary)
Definition ripser.cpp:810
diameter_entry_t get_pivot(Column &column)
Definition ripser.cpp:760
void add_simplex_coboundary(const diameter_entry_t simplex, const index_t &dim, Column &working_reduction_column, Column &working_coboundary)
Definition ripser.cpp:795
std::vector< diameter_index_t > get_edges()
index_t get_max_vertex(const index_t idx, const index_t k, const index_t n_) const
Definition ripser.cpp:590
diameter_entry_t pop_pivot(Column &column)
Definition ripser.cpp:732
binomial_coeff_table(index_t n, index_t k)
Definition ripser.cpp:145
index_t operator()(index_t n, index_t k) const
Definition ripser.cpp:158
compressed_distance_matrix(std::vector< value_t > &&_distances)
Definition ripser.cpp:345
compressed_distance_matrix(const DistanceMatrix &mat)
Definition ripser.cpp:353
value_t operator()(const index_t i, const index_t j) const
size_t size() const
Definition ripser.cpp:516
void push_back(const ValueType e)
Definition ripser.cpp:529
iterator_pair subrange(const index_t index)
Definition ripser.cpp:520
index_t find(index_t x)
Definition ripser.cpp:474
union_find(const index_t n)
Definition ripser.cpp:469
void link(index_t x, index_t y)
Definition ripser.cpp:485
Definition ripser.h:9
uint16_t coefficient_t
Definition ripser.h:18
int64_t index_t
Definition ripser.h:16
double value_t
Definition ripser.h:11
void ripser(std::vector< std::vector< value_t > > points, PersistenceType &ph, value_t threshold, index_t dim_max, bool distanceMatrix, bool criticalEdgesOnly=true, bool infinitePairs=true, coefficient_t modulus=2)
Definition ripser.cpp:1136
std::pair< id_t, id_t > Edge
constexpr value_t inf
std::vector< Diagram > MultidimensionalDiagram
std::pair< Simplex, value_t > FiltratedSimplex
std::array< EdgeSet, 3 > EdgeSets3
std::vector< id_t > Simplex
std::hash< Key > hash
Definition ripser.cpp:94
std::pair< value_t, index_t > diameter_index_t
Definition ripser.cpp:262
coefficient_t get_modulo(const coefficient_t val, const coefficient_t modulus)
Definition ripser.cpp:167
void set_coefficient(entry_t &e, const coefficient_t c)
Definition ripser.cpp:226
T end(std::pair< T, T > &p)
Definition ripser.cpp:503
compressed_matrix_layout
Definition ripser.cpp:334
@ UPPER_TRIANGULAR
Definition ripser.cpp:336
@ LOWER_TRIANGULAR
Definition ripser.cpp:335
entry_t make_entry(index_t i, coefficient_t c)
Definition ripser.cpp:217
value_t get_diameter(const diameter_index_t &i)
Definition ripser.cpp:265
coefficient_t normalize(const coefficient_t n, const coefficient_t modulus)
Definition ripser.cpp:171
compressed_distance_matrix< UPPER_TRIANGULAR > compressed_upper_distance_matrix
Definition ripser.cpp:371
compressed_distance_matrix< LOWER_TRIANGULAR > compressed_lower_distance_matrix
Definition ripser.cpp:370
std::pair< index_t, value_t > index_diameter_t
Definition ripser.cpp:272
std::vector< coefficient_t > multiplicative_inverse_vector(const coefficient_t m)
Definition ripser.cpp:176
#define PACK(...)
Definition ripser.cpp:193
index_t get_index(const entry_t &e)
Definition ripser.cpp:220
const entry_t & get_entry(const entry_t &e)
Definition ripser.cpp:258
void check_overflow(index_t i)
Definition ripser.cpp:126
index_t get_coefficient(const entry_t &e)
Definition ripser.cpp:223
std::unordered_map< Key, T, H, E > hash_map
Definition ripser.cpp:92
T begin(std::pair< T, T > &p)
Definition ripser.cpp:499
std::ostream & operator<<(std::ostream &stream, const entry_t &e)
Definition ripser.cpp:230
Definition ripser.cpp:282
diameter_entry_t(value_t _diameter, index_t _index, coefficient_t _coefficient)
Definition ripser.cpp:285
diameter_entry_t()=default
diameter_entry_t(const index_t &_index)
Definition ripser.cpp:295
diameter_entry_t(const diameter_index_t &_diameter_index, coefficient_t _coefficient)
Definition ripser.cpp:290
value_t operator()(const index_t i, const index_t j) const
Definition ripser.cpp:450
euclidean_distance_matrix(std::vector< std::vector< value_t > > &&_points)
Definition ripser.cpp:446
size_t size() const
Definition ripser.cpp:459
std::vector< std::vector< value_t > > points
Definition ripser.cpp:444
bool operator()(const Entry &a, const Entry &b)
Definition ripser.cpp:327
value_t operator()(const index_t i, const index_t j) const
Definition ripser.cpp:434
size_t size() const
Definition ripser.cpp:428
std::vector< std::vector< index_diameter_t > > neighbors
Definition ripser.cpp:404
std::vector< std::vector< index_diameter_t >::const_reverse_iterator > neighbor_end
Definition ripser.cpp:410
sparse_distance_matrix(std::vector< std::vector< index_diameter_t > > &&_neighbors, index_t _num_edges)
Definition ripser.cpp:412
std::vector< std::vector< index_diameter_t >::const_reverse_iterator > neighbor_it
Definition ripser.cpp:408