66#include <unordered_map>
73#define USE_COEFFICIENTS
80#ifdef USE_ROBINHOOD_HASHMAP
82#include "robin_hood.h"
84template <
class Key,
class T,
class H,
class E>
85using hash_map = robin_hood::unordered_map<Key, T, H, E>;
87using hash = robin_hood::hash<Key>;
91template <
class Key,
class T,
class H,
class E>
92using hash_map = std::unordered_map<Key, T, H, E>;
94using hash = std::hash<Key>;
98#ifdef INDICATE_PROGRESS
99static const std::chrono::milliseconds time_step(40);
102static const std::string clear_line(
"\r\033[K");
104static const size_t num_coefficient_bits = 8;
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;
113#ifdef USE_COEFFICIENTS
114 (i > max_simplex_index)
118 throw std::overflow_error(
"simplex index " + std::to_string((uint64_t)i)
119 +
" in filtration is larger than maximum index");
123static const index_t max_simplex_index
124 = (uintptr_t(1) << (8 *
sizeof(
index_t) - 1 - num_coefficient_bits)) - 1;
128#ifdef USE_COEFFICIENTS
129 (i > max_simplex_index)
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));
141 std::vector<index_t> B;
147 for(
index_t i = 0; i <= n; ++i) {
149 for(
index_t j = 1; j < std::min(i, k + 1); ++j)
151 = B[(i - 1) * offset + j - 1] + B[(i - 1) * offset + j];
153 B[i * offset + i] = 1;
159 assert(n <
index_t(B.size() / offset) && k <
index_t(offset) && n >= k - 1);
160 return B[n * offset + k];
168 return (modulus == 2) ? val & 1 : val % modulus;
172 return n > modulus / 2 ? n - modulus : n;
175std::vector<coefficient_t>
177 std::vector<coefficient_t> inverse(m);
183 inverse[a] = m - (inverse[m % a] * (m / a)) % m;
187#ifdef USE_COEFFICIENTS
191#define PACK(...) __pragma(pack(push, 1)) __VA_ARGS__ __pragma(pack(pop))
193#define PACK(...) __VA_ARGS__ __attribute__((__packed__))
198 index_t coefficient : num_coefficient_bits;
200 : index(_index), coefficient(_coefficient) {
202 entry_t(
index_t _index) : index(_index), coefficient(0) {
204 entry_t() : index(0), coefficient(0) {
208static_assert(
sizeof(entry_t) ==
sizeof(
index_t),
209 "size of entry_t is not the same as index_t");
215std::ostream &
operator<<(std::ostream &stream,
const entry_t &e);
218 return entry_t(i, c);
224 return e.coefficient;
230std::ostream &
operator<<(std::ostream &stream,
const entry_t &e) {
250 return entry_t(_index);
257const entry_t &
get_entry(
const entry_t &e);
283 using std::pair<
value_t, entry_t>::pair;
325template <
typename Entry>
339template <compressed_matrix_layout Layout>
352 template <
typename DistanceMatrix>
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);
378 for(
size_t i = 1; i <
size(); ++i) {
387 for(
size_t i = 0; i <
size() - 1; ++i) {
389 pointer +=
size() - i - 2;
396 return i == j ? 0 : i < j ?
rows[j][i] :
rows[i][j];
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>
415 std::vector<std::vector<index_diameter_t>> &&_neighbors,
index_t _num_edges)
419 template <
typename DistanceMatrix>
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) {
430 size_t size()
const {
437 for(
auto const &[index, diameter] :
neighbors[i]) {
446 std::vector<std::vector<value_t>>
points;
449 :
points(std::move(_points)) {
455 return std::sqrt(std::inner_product(
457 std::plus<value_t>(),
461 size_t size()
const {
467 std::vector<index_t> parent;
468 std::vector<uint8_t> rank;
478 while((z = parent[y]) != y)
480 while((z = parent[x]) != y) {
490 if(rank[x] > rank[y])
494 if(rank[x] == rank[y])
501T
begin(std::pair<T, T> &p) {
505T
end(std::pair<T, T> &p) {
509template <
typename ValueType>
511 std::vector<size_t> bounds;
512 std::vector<ValueType> entries;
514 using iterator =
typename std::vector<ValueType>::iterator;
515 using iterator_pair = std::pair<iterator, iterator>;
518 size_t size()
const {
519 return bounds.
size();
523 return {entries.begin() + (index == 0 ? 0 : bounds[index - 1]),
524 entries.begin() + bounds[index]};
528 bounds.push_back(entries.size());
533 entries.push_back(e);
544template <
typename DistanceMatrix>
546 const DistanceMatrix dist;
550 const bool critical_edges_only;
551 const bool infinite_pairs;
554 const std::vector<coefficient_t> multiplicative_inverse;
555 mutable std::vector<diameter_entry_t> cofacet_entries;
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));
568 bool operator()(
const entry_t &e,
const entry_t &f)
const {
576 Ripser(DistanceMatrix &&_dist,
580 bool _critical_edges_only,
581 bool _infinite_pairs,
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),
595 if(binomial_coeff(top, k) > idx) {
602 if(binomial_coeff(mid, k) > idx) {
613 return binomial_coeff(i, 2) + j;
616 template <
typename OutputIterator>
620 OutputIterator out)
const {
622 for(
index_t k = dim + 1; k > 0; --k) {
625 idx -= binomial_coeff(n_, k);
630 class simplex_coboundary_enumerator;
634 std::vector<diameter_index_t> &columns_to_reduce,
635 entry_hash_map &pivot_column_index,
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;
643 columns_to_reduce.clear();
644 std::vector<diameter_index_t> next_simplices;
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;
660 auto cofacet = cofacets.
next();
663 next_simplices.emplace_back(
666 if(pivot_column_index.find(
get_entry(cofacet))
667 == pivot_column_index.end())
668 columns_to_reduce.emplace_back(
674 simplices.swap(next_simplices);
676#ifdef INDICATE_PROGRESS
677 std::cerr << clear_line <<
"sorting " << columns_to_reduce.size()
678 <<
" columns" << std::flush;
681 std::sort(columns_to_reduce.begin(), columns_to_reduce.end(),
684#ifdef INDICATE_PROGRESS
685 std::cerr << clear_line << std::flush;
689 template <
typename PersistenceType>
691 std::vector<diameter_index_t> &columns_to_reduce,
692 PersistenceType &ph) {
696 std::sort(edges.rbegin(), edges.rend(),
698 std::vector<index_t> vertices_of_edge(2);
699 for(
auto e : edges) {
702 v = dset.
find(vertices_of_edge[1]);
707 if constexpr(std::is_same_v<PersistenceType,
710 = (u == dset.
find(vertices_of_edge[0]) ? vertices_of_edge[1]
711 : vertices_of_edge[0]);
714 int(vertices_of_edge[1])},
716 }
else if constexpr(std::is_same_v<PersistenceType, EdgeSets3>)
717 ph[0].emplace_back(vertices_of_edge[0], vertices_of_edge[1]);
720 columns_to_reduce.push_back(e);
722 std::reverse(columns_to_reduce.begin(), columns_to_reduce.end());
724 if constexpr(std::is_same_v<PersistenceType, MultidimensionalDiagram>) {
725 for(
index_t i = 0; i < n; ++i) {
726 if(dset.
find(i) == i)
733 template <
typename Column>
736#ifdef USE_COEFFICIENTS
737 while(!column.empty()) {
739 pivot = column.top();
750 while(!column.empty()) {
751 pivot = column.top();
761 template <
typename Column>
769 template <
typename Column>
772 Column &working_coboundary,
774 entry_hash_map &pivot_column_index) {
775 bool check_for_emergent_pair =
true;
776 cofacet_entries.clear();
781 cofacet_entries.push_back(cofacet);
782 if(check_for_emergent_pair
784 if(pivot_column_index.find(
get_entry(cofacet))
785 == pivot_column_index.end())
787 check_for_emergent_pair =
false;
791 for(
auto cofacet : cofacet_entries)
792 working_coboundary.push(cofacet);
796 template <
typename Column>
799 Column &working_reduction_column,
800 Column &working_coboundary) {
801 working_reduction_column.push(simplex);
806 working_coboundary.push(cofacet);
810 template <
typename Column>
813 const std::vector<diameter_index_t> &columns_to_reduce,
814 const size_t index_column_to_add,
817 Column &working_reduction_column,
818 Column &working_coboundary) {
820 columns_to_reduce[index_column_to_add], factor);
822 column_to_add, dim, working_reduction_column, working_coboundary);
825 reduction_matrix.
subrange(index_column_to_add)) {
828 simplex, dim, working_reduction_column, working_coboundary);
834 std::vector<diameter_entry_t>,
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]};
846 return {vertices[1], vertices[2]};
848 return {vertices[0], vertices[2]};
851 template <
typename PersistenceType>
852 void compute_pairs(std::vector<diameter_index_t> &columns_to_reduce,
853 entry_hash_map &pivot_column_index,
855 PersistenceType &ph) {
857 size_t index_column_to_add;
859#ifdef INDICATE_PROGRESS
860 std::chrono::steady_clock::time_point next
861 = std::chrono::steady_clock::now() + time_step;
864 for(
size_t index_column_to_reduce = 0;
865 index_column_to_reduce < columns_to_reduce.size();
866 ++index_column_to_reduce) {
868 columns_to_reduce[index_column_to_reduce], 1);
876 working_reduction_column.push(column_to_reduce);
879 column_to_reduce, working_coboundary, dim, pivot_column_index);
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;
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;
904 index_column_to_add, factor, dim,
905 working_reduction_column, working_coboundary);
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) {
920 ph[dim].emplace_back(
924 ph[dim].emplace_back(
927 }
else if constexpr(std::is_same_v<PersistenceType, EdgeSets3>) {
928 ph[2 * dim - 1].emplace_back(
929 vertices_birth[0], vertices_birth[1]);
934 pivot_column_index.insert(
935 {
get_entry(pivot), index_column_to_reduce});
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,
962#ifdef INDICATE_PROGRESS
963 std::cerr << clear_line << std::flush;
969 template <
typename PersistenceType>
971 std::vector<diameter_index_t> simplices, columns_to_reduce;
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());
983 compute_pairs(columns_to_reduce, pivot_column_index, dim, ph);
987 simplices, columns_to_reduce, pivot_column_index, dim + 1);
995 index_t idx_below, idx_above, v, k;
996 std::vector<index_t> vertices;
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) {
1012 get_index(_simplex), _dim, parent.n, vertices.begin());
1015 bool has_next(
bool all_cofacets =
true) {
1016 return (v >= k && (all_cofacets || binomial_coeff(v, k) > idx_below));
1020 while((binomial_coeff(v, k) <= idx_below)) {
1021 idx_below -= binomial_coeff(v, k);
1022 idx_above += binomial_coeff(v, k + 1);
1029 cofacet_diameter = std::max(cofacet_diameter, dist(v, w));
1030 index_t cofacet_index = idx_above + binomial_coeff(v--, k + 1) + idx_below;
1034 cofacet_diameter, cofacet_index, cofacet_coefficient);
1040 index_t idx_below, idx_above, k;
1041 std::vector<index_t> vertices;
1042 const diameter_entry_t simplex;
1044 const sparse_distance_matrix &dist;
1045 const binomial_coeff_table &binomial_coeff;
1046 std::vector<std::vector<index_diameter_t>::const_reverse_iterator>
1048 std::vector<std::vector<index_diameter_t>::const_reverse_iterator>
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();
1065 for(
auto v : vertices) {
1066 neighbor_it.push_back(dist.neighbors[v].rbegin());
1067 neighbor_end.push_back(dist.neighbors[v].rend());
1071 bool has_next(
bool all_cofacets =
true) {
1072 for(
auto &it0 = neighbor_it[0], &end0 = neighbor_end[0]; it0 != end0;
1075 for(
size_t idx = 1; idx < neighbor_it.size(); ++idx) {
1076 auto &it = neighbor_it[idx],
end = neighbor_end[idx];
1081 goto continue_outer;
1083 neighbor = std::max(neighbor, *it);
1085 while(k > 0 && vertices[k - 1] >
get_index(neighbor)) {
1088 idx_below -= binomial_coeff(vertices[k - 1], k);
1089 idx_above += binomial_coeff(vertices[k - 1], k + 1);
1103 = idx_above + binomial_coeff(
get_index(neighbor), k + 1) + idx_below;
1107 cofacet_diameter, cofacet_index, cofacet_coefficient);
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;) {
1118 value_t length = dist(vertices[0], vertices[1]);
1119 if(length <= threshold)
1120 edges.emplace_back(length, index);
1127 std::vector<diameter_index_t> edges;
1128 for(
index_t i = 0; i < n; ++i)
1129 for(
auto n_ : dist.neighbors[i]) {
1137template <
typename PersistenceType>
1139 PersistenceType &ph,
1142 bool distanceMatrix,
1143 bool criticalEdgesOnly,
1146 const double ratio = 1.;
1148 if constexpr(std::is_same_v<PersistenceType, MultidimensionalDiagram>)
1150 else if constexpr(std::is_same_v<PersistenceType, EdgeSets3>)
1151 dim_max = std::max(dim_max,
static_cast<index_t>(1));
1153 if(!distanceMatrix) {
1154 if(threshold <
inf) {
1157 Ripser ripser(std::move(dist), dim_max, threshold, ratio,
1158 criticalEdgesOnly, infinitePairs, modulus);
1159 ripser.compute_barcodes(ph);
1163 Ripser ripser(std::move(dist), dim_max, threshold, ratio,
1164 criticalEdgesOnly, infinitePairs, modulus);
1165 ripser.compute_barcodes(ph);
1168 if(threshold <
inf) {
1171 Ripser ripser(std::move(dist), dim_max, threshold, ratio,
1172 criticalEdgesOnly, infinitePairs, modulus);
1173 ripser.compute_barcodes(ph);
1176 Ripser ripser(std::move(dist), dim_max, threshold, ratio,
1177 criticalEdgesOnly, infinitePairs, modulus);
1178 ripser.compute_barcodes(ph);
1182template void ripser::ripser(std::vector<std::vector<value_t>> points,
1186 bool distanceMatrix,
1187 bool criticalEdgesOnly,
1190template void ripser::ripser(std::vector<std::vector<value_t>> points,
1194 bool distanceMatrix,
1195 bool criticalEdgesOnly,
simplex_coboundary_enumerator(const diameter_entry_t _simplex, index_t _dim, const Ripser< compressed_lower_distance_matrix > &parent)
bool has_next(bool all_cofacets=true)
index_t get_edge_index(const index_t i, const index_t j) const
void compute_pairs(std::vector< diameter_index_t > &columns_to_reduce, entry_hash_map &pivot_column_index, index_t dim, PersistenceType &ph)
OutputIterator get_simplex_vertices(index_t idx, const index_t dim, index_t n_, OutputIterator out) const
std::priority_queue< diameter_entry_t, std::vector< diameter_entry_t >, greater_diameter_or_smaller_index< diameter_entry_t > > working_t
Edge find_longest_edge(const Simplex &vertices) const
void compute_dim_0_pairs(std::vector< diameter_index_t > &edges, std::vector< diameter_index_t > &columns_to_reduce, PersistenceType &ph)
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)
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)
void compute_barcodes(PersistenceType &ph)
Ripser(DistanceMatrix &&_dist, index_t _dim_max, value_t _threshold, float _ratio, bool _critical_edges_only, bool _infinite_pairs, coefficient_t _modulus)
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)
diameter_entry_t get_pivot(Column &column)
void add_simplex_coboundary(const diameter_entry_t simplex, const index_t &dim, Column &working_reduction_column, Column &working_coboundary)
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
diameter_entry_t pop_pivot(Column &column)
binomial_coeff_table(index_t n, index_t k)
index_t operator()(index_t n, index_t k) const
std::vector< value_t > distances
compressed_distance_matrix(std::vector< value_t > &&_distances)
std::vector< value_t * > rows
compressed_distance_matrix(const DistanceMatrix &mat)
value_t operator()(const index_t i, const index_t j) const
void push_back(const ValueType e)
iterator_pair subrange(const index_t index)
union_find(const index_t n)
void link(index_t x, index_t y)
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)
std::pair< id_t, id_t > Edge
std::vector< Diagram > MultidimensionalDiagram
std::pair< Simplex, value_t > FiltratedSimplex
std::array< EdgeSet, 3 > EdgeSets3
std::vector< id_t > Simplex
std::pair< value_t, index_t > diameter_index_t
coefficient_t get_modulo(const coefficient_t val, const coefficient_t modulus)
void set_coefficient(entry_t &e, const coefficient_t c)
T end(std::pair< T, T > &p)
entry_t make_entry(index_t i, coefficient_t c)
value_t get_diameter(const diameter_index_t &i)
coefficient_t normalize(const coefficient_t n, const coefficient_t modulus)
compressed_distance_matrix< UPPER_TRIANGULAR > compressed_upper_distance_matrix
compressed_distance_matrix< LOWER_TRIANGULAR > compressed_lower_distance_matrix
std::pair< index_t, value_t > index_diameter_t
std::vector< coefficient_t > multiplicative_inverse_vector(const coefficient_t m)
index_t get_index(const entry_t &e)
const entry_t & get_entry(const entry_t &e)
void check_overflow(index_t i)
index_t get_coefficient(const entry_t &e)
std::unordered_map< Key, T, H, E > hash_map
T begin(std::pair< T, T > &p)
std::ostream & operator<<(std::ostream &stream, const entry_t &e)
diameter_entry_t(value_t _diameter, index_t _index, coefficient_t _coefficient)
diameter_entry_t()=default
diameter_entry_t(const index_t &_index)
diameter_entry_t(const diameter_index_t &_diameter_index, coefficient_t _coefficient)
value_t operator()(const index_t i, const index_t j) const
euclidean_distance_matrix(std::vector< std::vector< value_t > > &&_points)
std::vector< std::vector< value_t > > points
bool operator()(const Entry &a, const Entry &b)
value_t operator()(const index_t i, const index_t j) const
std::vector< std::vector< index_diameter_t > > neighbors
std::vector< std::vector< index_diameter_t >::const_reverse_iterator > neighbor_end
sparse_distance_matrix(std::vector< std::vector< index_diameter_t > > &&_neighbors, index_t _num_edges)
std::vector< std::vector< index_diameter_t >::const_reverse_iterator > neighbor_it