zigzag_persistence.h
Go to the documentation of this file.
1/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT.
2 * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details.
3 * Author(s): Clément Maria
4 *
5 * Copyright (C) 2021 Inria
6 *
7 * Modification(s):
8 * - 2023/05 Hannah Schreiber: Rework of the interface, reorganization and debug
9 * - 2023/05 Hannah Schreiber: Addition of infinite bars
10 * - 2024/06 Hannah Schreiber: Separation of the zigzag algorithm from the filtration value management
11 * - YYYY/MM Author: Description of the modification
12 */
13
20#ifndef ZIGZAG_PERSISTENCE_H_
21#define ZIGZAG_PERSISTENCE_H_
22
23#include <cmath>
24#include <set>
25#include <utility>
26#include <vector>
27
28#include <boost/iterator/indirect_iterator.hpp>
29#if BOOST_VERSION >= 108100
30#include <boost/unordered/unordered_flat_map.hpp> //don't exist for lower versions of boost
31// #include <boost/unordered/unordered_map.hpp>
32#else
33#include <unordered_map>
34#endif
35
36#include <gudhi/Matrix.h>
37
38namespace Gudhi {
39namespace zigzag_persistence {
40
48template <Gudhi::persistence_matrix::Column_types column_type>
50 static const bool has_row_access = true;
51 static const bool has_column_pairings = false;
52 static const bool has_vine_update = true;
53 static const bool is_of_boundary_type = false;
54 static const bool has_map_column_container = true;
55 static const bool has_removable_columns = true;
56 static const bool has_removable_rows = true;
57};
58
65 using Internal_key = int;
66 using Dimension = int;
72};
73
74// TODO: add the possibility of something else than Z2. Which means that the possibility of vineyards without Z2
75// also needs to be implemented. The theory needs to be done first.
147template <class ZigzagOptions = Default_zigzag_options>
149{
150 public:
152 using Index = typename Options::Internal_key;
153 using Dimension = typename Options::Dimension;
155 private:
156#if BOOST_VERSION >= 108100
157 using Birth_dictionary = boost::unordered_flat_map<Index, Index>;
158 // using Birth_dictionary = boost::unordered_map<Index, Index>; /**< Dictionary type. */
159#else
160 using Birth_dictionary = std::unordered_map<Index, Index>;
161#endif
164 using Matrix_index = typename Matrix::Index;
185 struct Birth_ordering {
189 Birth_ordering() : birthToPos_(), maxBirthPos_(0), minBirthPos_(-1) {}
190
198 void add_birth_forward(Index arrowNumber) { // amortized constant time
199 birthToPos_.emplace_hint(birthToPos_.end(), arrowNumber, maxBirthPos_);
200 ++maxBirthPos_;
201 }
209 void add_birth_backward(Index arrowNumber) { // amortized constant time
210 birthToPos_.emplace_hint(birthToPos_.end(), arrowNumber, minBirthPos_);
211 --minBirthPos_;
212 }
213
221 void remove_birth(Index birth) { birthToPos_.erase(birth); }
229 bool birth_order(Index k1, Index k2) const { return birthToPos_.at(k1) < birthToPos_.at(k2); }
237 bool reverse_birth_order(Index k1, Index k2) const { return birthToPos_.at(k1) > birthToPos_.at(k2); }
238
239 private:
240 Birth_dictionary birthToPos_;
241 Index maxBirthPos_;
242 Index minBirthPos_;
243 };
244
245 public:
263 Zigzag_persistence(std::function<void(Dimension, Index, Index)> stream_interval,
264 unsigned int preallocationSize = 0)
265 : matrix_(
266 preallocationSize,
267 [this](Matrix_index columnIndex1, Matrix_index columnIndex2) -> bool {
268 if (matrix_.get_column(columnIndex1).is_paired()) {
269 return matrix_.get_pivot(columnIndex1) < matrix_.get_pivot(columnIndex2);
270 }
271 return birthOrdering_.birth_order(births_.at(columnIndex1), births_.at(columnIndex2));
272 },
273 [this](Matrix_index columnIndex1, Matrix_index columnIndex2) -> bool { return false; }),
274 numArrow_(-1),
275 stream_interval_(std::move(stream_interval)) {}
287 template <class BoundaryRange = std::initializer_list<Index> >
288 Index insert_cell(const BoundaryRange& boundary, Dimension dimension) {
289 ++numArrow_;
290 _process_forward_arrow(boundary, dimension);
291 return numArrow_;
292 }
293
300 Index remove_cell(Index arrowNumber) {
301 ++numArrow_;
302 _process_backward_arrow(arrowNumber);
303 return numArrow_;
304 }
305
312 Index apply_identity() { return ++numArrow_; }
313
322 template <typename F>
323 void get_current_infinite_intervals(F&& stream_infinite_interval) {
324 for (auto& p : births_) {
325 auto& col = matrix_.get_column(p.first);
326 stream_infinite_interval(col.get_dimension(), p.second);
327 }
328 }
329
330 private:
339 template <class BoundaryRange>
340 void _process_forward_arrow(const BoundaryRange& boundary, Dimension dim) {
341 std::vector<Matrix_index> chainsInF = matrix_.insert_boundary(numArrow_, boundary, dim);
342
343 if (!chainsInF.empty()) {
344 _apply_surjective_reflection_diamond(dim, chainsInF);
345 } else {
346 birthOrdering_.add_birth_forward(numArrow_);
347 births_[matrix_.get_column_with_pivot(numArrow_)] = numArrow_;
348 }
349 }
350
362 void _apply_surjective_reflection_diamond(Dimension dim, const std::vector<Matrix_index>& chainsInF) {
363 // fp is the largest death index for <=d
364 // Set col_fp: col_fp <- col_f1+...+col_fp (now in G); preserves lowest idx
365 auto chainFp = chainsInF[0]; // col_fp, with largest death <d index.
366
367 // chainsInF is ordered, from .begin() to end(), by decreasing lowest_idx_. The
368 // lowest_idx_ is also the death of the chain in the right suffix of the
369 // filtration (all backward arrows). Consequently, the chains in F are ordered by
370 // decreasing death for <d.
371 // Pair the col_fi, i = 1 ... p-1, according to the reflection diamond principle
372 // Order the fi by reverse birth ordering <=_b
373 auto cmp_birth = [this](Index k1, Index k2) -> bool {
374 return birthOrdering_.reverse_birth_order(k1, k2);
375 }; // true iff b(k1) >b b(k2)
376
377 // availableBirth: for all i by >d value of the d_i,
378 // contains at step i all b_j, j > i, and maybe b_i if not stolen
379 std::set<Index, decltype(cmp_birth)> availableBirth(cmp_birth);
380 // for f1 to f_{p} (i by <=d), insertion in availableBirth sorts by >=b
381 for (auto& chainF : chainsInF) {
382 availableBirth.insert(births_.at(chainF));
383 }
384
385 auto maxbIt = availableBirth.begin(); // max birth cycle
386 auto maxb = *maxbIt; // max birth value, for persistence diagram
387 availableBirth.erase(maxbIt); // remove max birth cycle (stolen)
388
389 auto lastModifiedChainIt = chainsInF.rbegin();
390
391 // consider all death indices by increasing <d order i.e., increasing lowest_idx_
392 for (auto chainFIt = chainsInF.rbegin(); // by increasing death order <d
393 *chainFIt != chainFp; ++chainFIt) // chain_fp=*begin() has max death
394 { // find which reduced col has this birth
395 auto birthIt = availableBirth.find(births_.at(*chainFIt));
396 if (birthIt == availableBirth.end()) // birth is not available. *chain_f_it
397 { // must become the sum of all chains in F with smaller death index.
398 // this gives as birth the maximal birth of all chains with strictly larger
399 // death <=> the maximal available death.
400 // Let c_1 ... c_f be the chains s.t. <[c_1+...+c_f]> is the kernel and
401 // death(c_i) >d death(c_i-1). If the birth of c_i is not available, we set
402 // c_i <- c_i + c_i-1 + ... + c_1, which is [c_i + c_i-1 + ... + c_1] on
403 // the right (of death the maximal<d death(c_i)), and is [c_i + c_i-1 + ... +
404 // c_1] + kernel = [c_f + c_f-1 + ... + c_i+1] on the left (of birth the max<b
405 // of the birth of the c_j, j>i <=> the max<b available birth).
406 // N.B. some of the c_k, k<i, have already been modified to be equal to
407 // c_k + c_k-1 + ... + c_1. The largest k with this property is maintained in
408 // last_modified_chain_it (no need to compute from scratch the full sum).
409
410 // last_modified is equal to c_k+...+c_1, all c_j, i>j>k, are indeed c_j
411 // set c_i <- c_i + (c_i-1) + ... + (c_k+1) + (c_k + ... + c_1)
412 for (auto chainPassedIt = lastModifiedChainIt; chainPassedIt != chainFIt; ++chainPassedIt) {
413 // all with smaller <d death
414 matrix_.add_to(*chainPassedIt, *chainFIt);
415 }
416 lastModifiedChainIt = chainFIt; // new cumulated c_i+...+c_1
417 // remove the max available death
418 auto maxAvailBirthIt = availableBirth.begin(); // max because order by decreasing <b
419 Index maxAvailBirth = *maxAvailBirthIt; // max available birth
420
421 births_.at(*chainFIt) = maxAvailBirth; // give new birth
422 availableBirth.erase(maxAvailBirthIt); // remove birth from availability
423 } else {
424 availableBirth.erase(birthIt);
425 } // birth not available anymore, do not
426 } // modify *chain_f_it.
427
428 birthOrdering_.remove_birth(maxb);
429 births_.erase(chainFp);
430
431 // Update persistence diagram with left interval [fil(b_max) ; fil(m))
432 stream_interval_(dim - 1, maxb, numArrow_);
433 }
434
440 void _process_backward_arrow(Index cellID) {
441 // column whose key is the one of the removed cell
442 Matrix_index currCol = matrix_.get_column_with_pivot(cellID);
443
444 // Record all columns that get affected by the transpositions, i.e., have a coeff
445 std::vector<Matrix_index> modifiedColumns;
446 const auto& row = matrix_.get_row(cellID);
447 modifiedColumns.reserve(row.size());
448 std::transform(row.begin(), row.end(), std::back_inserter(modifiedColumns),
449 [](const auto& cell) { return cell.get_column_index(); });
450 // Sort by left-to-right order in the matrix_ (no order maintained in rows)
451 std::stable_sort(modifiedColumns.begin(), modifiedColumns.end(), [this](Matrix_index i1, Matrix_index i2) {
452 return matrix_.get_pivot(i1) < matrix_.get_pivot(i2);
453 });
454
455 // Modifies curr_col, not the other one.
456 for (auto otherColIt = std::next(modifiedColumns.begin()); otherColIt != modifiedColumns.end(); ++otherColIt) {
457 currCol = matrix_.vine_swap_with_z_eq_1_case(currCol, *otherColIt);
458 }
459
460 // curr_col points to the column to remove by restriction of K to K-{\sigma}
461 auto& col = matrix_.get_column(currCol);
462 if (!col.is_paired()) { // in F
463 auto it = births_.find(currCol);
464 stream_interval_(col.get_dimension(), it->second, numArrow_);
465 birthOrdering_.remove_birth(it->second);
466 births_.erase(it);
467 } else { // in H -> paired with c_g, that now belongs to F now
468 // maintain the <=b order
469 birthOrdering_.add_birth_backward(numArrow_);
470 births_[col.get_paired_chain_index()] = numArrow_;
471 }
472
473 // cannot be in G as the removed cell is maximal
474 matrix_.remove_maximal_cell(cellID, {}); // also un-pairs c_g if in H
475 }
476
477 private:
478 Matrix matrix_;
479 Birth_dictionary births_;
480 Birth_ordering birthOrdering_;
481 Index numArrow_;
482 std::function<void(Dimension, Index, Index)> stream_interval_;
483}; // end class Zigzag_persistence
484
485} // namespace zigzag_persistence
486} // namespace Gudhi
487
488#endif // ZIGZAG_PERSISTENCE_H_
Contains Gudhi::persistence_matrix::Matrix class.
ID_index get_pivot(Index columnIndex)
Returns the row index of the pivot of the given column. Only available for non-basic matrices.
Definition: Matrix.h:1930
typename PersistenceMatrixOptions::Index Index
Definition: Matrix.h:147
Class computing the zigzag persistent homology of a zigzag sequence. Algorithm based on .
Definition: zigzag_persistence.h:149
Zigzag_persistence(std::function< void(Dimension, Index, Index)> stream_interval, unsigned int preallocationSize=0)
Constructor of the Zigzag_persistence class.
Definition: zigzag_persistence.h:263
Index remove_cell(Index arrowNumber)
Updates the zigzag persistence diagram after the removal of the given cell.
Definition: zigzag_persistence.h:300
Index insert_cell(const BoundaryRange &boundary, Dimension dimension)
Updates the zigzag persistence diagram after the insertion of the given cell.
Definition: zigzag_persistence.h:288
Index apply_identity()
To use when a cell is neither inserted nor removed, but the filtration moves along the identity opera...
Definition: zigzag_persistence.h:312
typename Options::Dimension Dimension
Definition: zigzag_persistence.h:153
typename Options::Internal_key Index
Definition: zigzag_persistence.h:152
void get_current_infinite_intervals(F &&stream_infinite_interval)
Outputs through the given callback method all birth indices which are currently not paired with a dea...
Definition: zigzag_persistence.h:323
Column_types
List of column types.
Definition: persistence_matrix_options.h:30
Gudhi namespace.
Definition: SimplicialComplexForAlpha.h:14
Default option structure for Matrix class. See the PersistenceMatrixOptions concept for a more detail...
Definition: persistence_matrix_options.h:79
Default options for Zigzag_persistence.
Definition: zigzag_persistence.h:64
int Internal_key
Definition: zigzag_persistence.h:65
int Dimension
Definition: zigzag_persistence.h:66
static const Gudhi::persistence_matrix::Column_types column_type
Column type use by the internal matrix.
Definition: zigzag_persistence.h:70
unspecified Dimension
Type for the dimension values.
Definition: ZigzagOptions.h:47
unspecified Internal_key
Numerical type for the cell IDs used internally and other indexations. It must be signed.
Definition: ZigzagOptions.h:31
Options for the internal matrix of Zigzag_persistence.
Definition: zigzag_persistence.h:49
List of options used for the zigzag persistence computation.
Definition: ZigzagOptions.h:60