diff --git a/CMakeLists.txt b/CMakeLists.txt index c1a51312f0..54a9bda0d9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,7 @@ add_gudhi_module(Tangential_complex) add_gudhi_module(Toplex_map) add_gudhi_module(Witness_complex) add_gudhi_module(Nerve_GIC) +add_gudhi_module(Persistence_matrix) # Include module CMake subdirectories # GUDHI_SUB_DIRECTORIES is managed in CMAKE_MODULE_PATH/GUDHI_modules.cmake diff --git a/biblio/bibliography.bib b/biblio/bibliography.bib index 472e1fa01e..12c1412b06 100644 --- a/biblio/bibliography.bib +++ b/biblio/bibliography.bib @@ -722,6 +722,19 @@ @article{de2004topological % V %----------------------------- +@inproceedings{vineyards, + author = {David Cohen-Steiner and + Herbert Edelsbrunner and + Dmitriy Morozov}, + title = {Vines and vineyards by updating persistence in linear time}, + booktitle = {Symposium on Computational Geometry}, + year = {2006}, + pages = {119-126}, + ee = {http://doi.acm.org/10.1145/1137856.1137877}, + bibsource = {DBLP, http://dblp.uni-trier.de}, + url = {https://doi.org/10.1145/1137856.1137877} +} + @inproceedings{cavanna15visualizing, author = {Nicholas J. Cavanna and Mahmoodreza Jahanseir and Donald R. Sheehy}, booktitle = {Proceedings of the 31st International Symposium on Computational Geometry}, @@ -768,6 +781,19 @@ @article{DBLP:journals/focm/CarlssonS10 url = {https://doi.org/10.1007/s10208-010-9066-0} } +@inproceedings{zigzag, + author = {Cl{\'{e}}ment Maria and + Steve Y. Oudot}, + title = {Zigzag Persistence via Reflections and Transpositions}, + booktitle = {Proceedings of the Twenty-Sixth Annual {ACM-SIAM} Symposium on Discrete + Algorithms, {SODA} 2015, San Diego, CA, USA, January 4-6, 2015}, + pages = {181--199}, + publisher = {{SIAM}}, + year = {2015}, + url = {https://doi.org/10.1137/1.9781611973730.14}, + doi = {10.1137/1.9781611973730.14} +} + %----------------------------- % UNUSED %----------------------------- @@ -1279,18 +1305,6 @@ @article{rips2012 pdf = {http://hal.archives-ouvertes.fr/hal-00785072/PDF/2012-cgta-Rips.pdf} } -@inproceedings{DBLP:conf/compgeom/Cohen-SteinerEM06, - author = {David Cohen-Steiner and - Herbert Edelsbrunner and - Dmitriy Morozov}, - title = {Vines and vineyards by updating persistence in linear time}, - booktitle = {Symposium on Computational Geometry}, - year = {2006}, - pages = {119-126}, - ee = {http://doi.acm.org/10.1145/1137856.1137877}, - bibsource = {DBLP, http://dblp.uni-trier.de} -} - @inproceedings{DBLP:conf/compgeom/CarlssonSM09, author = {Gunnar E. Carlsson and Vin de Silva and diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 92e964c4bd..52c21166ba 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -44,6 +44,7 @@ add_gudhi_module(Tangential_complex) add_gudhi_module(Toplex_map) add_gudhi_module(Witness_complex) add_gudhi_module(Nerve_GIC) +add_gudhi_module(Persistence_matrix) # For "make doxygen" - Requires GUDHI_USER_VERSION_DIR to be set set(GUDHI_USER_VERSION_DIR ${CMAKE_SOURCE_DIR}) diff --git a/src/Persistence_matrix/concept/FieldOperators.h b/src/Persistence_matrix/concept/FieldOperators.h new file mode 100644 index 0000000000..a2155c7027 --- /dev/null +++ b/src/Persistence_matrix/concept/FieldOperators.h @@ -0,0 +1,140 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2024 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file FieldOperators.h + * @author Hannah Schreiber + * @brief Contains the concept for the matrix field operators. + */ + +namespace Gudhi { +namespace persistence_matrix { + +/** + * @ingroup persistence_matrix + * + * @brief Concept of the field operator classes needed for the class @ref Matrix. + * + * Implementations of this concept are @ref Gudhi::persistence_fields::Zp_field_operators, + * @ref Gudhi::persistence_fields::Z2_field_operators, + * @ref Gudhi::persistence_fields::Multi_field_operators and + * @ref Gudhi::persistence_fields::Multi_field_operators_with_small_characteristics. + */ +class FieldOperators +{ + public: + using element_type = unspecified; /**< Type for the elements in the field. */ + using characteristic_type = unspecified; /**< Type for the field characteristic. */ + + /** + * @brief Default constructor. If a non-zero characteristic is given, initializes the field with it. + * The characteristic can later be changed again or initialized with @ref set_characteristic. + * + * @param characteristic Prime number corresponding to the desired characteristic of the field. + */ + FieldOperators(characteristic_type characteristic = 0); + + /** + * @brief Sets the characteristic of the field. Can eventually be omitted if the characteristic of the class + * is fixed. + * + * @param characteristic Prime number corresponding to the desired characteristic of the field. + */ + void set_characteristic(const characteristic_type& characteristic); + /** + * @brief Returns the current characteristic. + * + * @return The value of the current characteristic. + */ + const characteristic_type& get_characteristic() const; + + /** + * @brief Returns the value of an integer in the field. + * That is the positive value of the integer modulo the current characteristic. + * + * @tparam Integer_type A native integer type: int, unsigned int, long int, bool, etc. + * @param e Integer to return the value from. + * @return @p e modulo the current characteristic, such that the result is positive. + */ + template + element_type get_value(Integer_type e) const; + + // void get_value(element_type& e) const; + + /** + * @brief Stores in the first element the sum of two given elements in the field, that is + * `(e1 + e2) % characteristic`, such that the result is positive. + * + * @param e1 First element. + * @param e2 Second element. + */ + void add_inplace(element_type& e1, const element_type& e2) const; + + /** + * @brief Stores in the first element the multiplication of two given elements in the field, + * that is `(e1 * e2) % characteristic`, such that the result is positive. + * + * @param e1 First element. + * @param e2 Second element. + */ + void multiply_inplace(element_type& e1, const element_type& e2) const; + + /** + * @brief Multiplies the first element with the second one and adds the third one, that is + * `(e * m + a) % characteristic`, such that the result is positive. Stores the result in the first element. + * + * @param e First element. + * @param m Second element. + * @param a Third element. + */ + void multiply_and_add_inplace_front(element_type& e, const element_type& m, const element_type& a) const; + /** + * @brief Multiplies the first element with the second one and adds the third one, that is + * `(e * m + a) % characteristic`, such that the result is positive. Stores the result in the third element. + * + * @param e First element. + * @param m Second element. + * @param a Third element. + */ + void multiply_and_add_inplace_back(const element_type& e, const element_type& m, element_type& a) const; + + /** + * @brief Returns the inverse of the given element in the field. + * + * @param e Element to get the inverse from. + * @return Inverse in the current field of `e % characteristic`. + */ + element_type get_inverse(const element_type& e) const; + + /** + * @brief Returns the additive identity of the field. + * + * @return The additive identity of the field. + */ + static const element_type& get_additive_identity(); + /** + * @brief Returns the multiplicative identity of the field. + * + * @return The multiplicative identity of the field. + */ + static const element_type& get_multiplicative_identity(); + + /** + * @brief Assign operator. + */ + FieldOperators& operator=(FieldOperators other); + /** + * @brief Swap operator. + */ + friend void swap(FieldOperators& f1, FieldOperators& f2); +}; + +} // namespace persistence_matrix +} // namespace Gudhi diff --git a/src/Persistence_matrix/concept/PersistenceMatrixColumn.h b/src/Persistence_matrix/concept/PersistenceMatrixColumn.h new file mode 100644 index 0000000000..8906495bdc --- /dev/null +++ b/src/Persistence_matrix/concept/PersistenceMatrixColumn.h @@ -0,0 +1,458 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2024 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file PersistenceMatrixColumn.h + * @author Hannah Schreiber + * @brief Contains the concept for the matrix columns. + */ + +namespace Gudhi { +namespace persistence_matrix { + +/** + * @ingroup persistence_matrix + * + * @brief If PersistenceMatrixOptions::has_row_access is true, then @ref Row_access. Otherwise @ref Dummy_row_access. + * Can eventually be removed if the structure of the column does not allow row access (as for @ref Heap_column), but + * then it needs to be notified in the documentation of @ref Column_types and as static_assert in + * @ref Matrix::_assert_options. + */ +using Row_access_option = Row_access; +/** + * @ingroup persistence_matrix + * + * @brief If @ref PersistenceMatrixOptions::has_column_pairings or @ref PersistenceMatrixOptions::has_vine_update or + * @ref PersistenceMatrixOptions::can_retrieve_representative_cycles is true, then @ref Column_dimension_holder. + * Otherwise @ref Dummy_dimension_holder. + */ +using Column_dimension_option = Column_dimension_holder; +/** + * @ingroup persistence_matrix + * + * @brief If @ref PersistenceMatrixOptions::is_of_boundary_type is false, and, + * @ref PersistenceMatrixOptions::has_column_pairings or @ref PersistenceMatrixOptions::has_vine_update or + * @ref PersistenceMatrixOptions::can_retrieve_representative_cycles is true, then @ref Chain_column_extra_properties. + * Otherwise @ref Dummy_chain_properties. + */ +using Chain_column_option = Chain_column_extra_properties; + +/** + * @ingroup persistence_matrix + * + * @brief Concept of the column classes used by the @ref Matrix class. The classes the columns inheritates from + * are either real or dummy classes, see @ref Row_access_option, @ref Column_dimension_option, @ref Chain_column_option. + * If used with column compression, the column type has to have its `std::hash` method. + * + * Implementations of this concept are @ref Heap_column, @ref List_column, @ref Vector_column, @ref Naive_vector_column + * @ref Set_column, @ref Unordered_set_column, @ref Intrusive_list_column and @ref Intrusive_set_column. + */ +class PersistenceMatrixColumn : + public Row_access_option, + public Column_dimension_option, + public Chain_column_option +{ + public: + using Master = unspecified; /**< Master matrix, that is a templated @ref Matrix. */ + using index = unspecified; /**< Type of @ref MatIdx index. */ + using id_index = unspecified; /**< Type of @ref IDIdx index. */ + using dimension_type = unspecified; /**< Type for dimension value. */ + using Field_element_type = unspecified; /**< Type of a field element. */ + using Cell = unspecified; /**< @ref Cell. */ + using Column_settings = unspecified; /**< Structure giving access to external classes eventually necessary, + like a cell pool for example. */ + using iterator = unspecified; /**< Column iterator type. */ + using const_iterator = unspecified; /**< Column const_iterator type. */ + using reverse_iterator = unspecified; /**< Column reverse_iterator type. */ + using const_reverse_iterator = unspecified; /**< Column const_reverse_iterator type. */ + + /** + * @brief Constructs an empty column. If @p cellConstructor is not specified or is set to `nullptr`, the column + * can only be used as a dummy, i.e., no modifying method should be used or there will be a segmentation fault. + * Same goes for @p operators if @ref PersistenceMatrixOptions::is_z2 is false. + * + * @param colSettings Pointer to a setting structure or `nullptr`. The structure should contain all the necessary + * classes specific to the column type, such as custom allocators. The specificities are this way hidden behind + * a commun interface for all column types. If @p colSettings is not specified or is equal to `nullptr`, the column + * should still be constructable eventhough not necessarily "usable". + */ + PersistenceMatrixColumn(Column_settings* colSettings = nullptr); + /** + * @brief Constructs a column from the given range of @ref Matrix::cell_rep_type. If the dimension is stored, + * the face is assumed to be simplicial and its dimension to be `nonZeroRowIndices length - 1` or `0`. + * Otherwise, the dimension should be specified with another constructor. + * + * @tparam Container_type Range of @ref Matrix::cell_rep_type. Assumed to have a %begin(), %end() and %size() method. + * @param nonZeroRowIndices Range of @ref Matrix::cell_rep_type representing all rows with non zero values. + * @param colSettings Pointer to an existing setting structure. The structure should contain all the necessary + * classes specific to the column type, such as custom allocators. The specificities are this way hidden behind + * a commun interface for all column types. + */ + template + PersistenceMatrixColumn(const Container_type& nonZeroRowIndices, + Column_settings* colSettings); + /** + * @brief Constructs a column from the given range of @ref Matrix::cell_rep_type such that the rows can be accessed. + * Each new cell in the column is also inserted in a row using @ref Row_access::insert_cell. + * If the dimension is stored, the face is assumed to be simplicial and its dimension to be + * `nonZeroRowIndices length - 1` or `0`. Otherwise, the dimension should be specified with another constructor. + * + * @tparam Container_type Range of @ref Matrix::cell_rep_type. Assumed to have a %begin(), %end() and %size() method. + * @tparam Row_container_type Either std::map if @ref PersistenceMatrixOptions::has_removable_rows is true or + * std::vector. + * @param columnIndex @ref MatIdx column index that should be specified to the cells. + * @param nonZeroRowIndices Range of @ref Matrix::cell_rep_type representing all rows with non zero values. + * @param rowContainer Pointer to the row container that will be forwarded to @ref Row_access at construction. + * @param colSettings Pointer to an existing setting structure. The structure should contain all the necessary + * classes specific to the column type, such as custom allocators. The specificities are this way hidden behind + * a commun interface for all column types. + */ + template + PersistenceMatrixColumn(index columnIndex, + const Container_type& nonZeroRowIndices, + Row_container_type* rowContainer, + Column_settings* colSettings); + /** + * @brief Constructs a column from the given range of @ref Matrix::cell_rep_type and stores the given dimension + * if @ref Column_dimension_option is not a dummy. + * + * @tparam Container_type Range of @ref Matrix::cell_rep_type. Assumed to have a %begin(), %end() and %size() method. + * @param nonZeroChainRowIndices Range of @ref Matrix::cell_rep_type representing all rows with non zero values. + * @param dimension Dimension of the column. Is ignored if the dimension is not stored. + * @param colSettings Pointer to an existing setting structure. The structure should contain all the necessary + * classes specific to the column type, such as custom allocators. The specificities are this way hidden behind + * a commun interface for all column types. + */ + template + PersistenceMatrixColumn(const Container_type& nonZeroChainRowIndices, + dimension_type dimension, + Column_settings* colSettings); + /** + * @brief Constructs a column from the given range of @ref Matrix::cell_rep_type such that the rows can be accessed. + * Each new cell in the column is also inserted in a row using @ref Row_access::insert_cell. + * Stores the given dimension if @ref Column_dimension_option is not a dummy. + * + * @tparam Container_type Range of @ref Matrix::cell_rep_type. Assumed to have a %begin(), %end() and %size() method. + * @tparam Row_container_type Either std::map if @ref PersistenceMatrixOptions::has_removable_rows is true or + * std::vector. + * @param columnIndex @ref MatIdx column index that should be specified to the cells. + * @param nonZeroRowIndices Range of @ref Matrix::cell_rep_type representing all rows with non zero values. + * @param dimension Dimension of the column. Is ignored if the dimension is not stored. + * @param rowContainer Pointer to the row container that will be forwarded to @ref Row_access at construction. + * @param colSettings Pointer to an existing setting structure. The structure should contain all the necessary + * classes specific to the column type, such as custom allocators. The specificities are this way hidden behind + * a commun interface for all column types. + */ + template + PersistenceMatrixColumn(index columnIndex, + const Container_type& nonZeroChainRowIndices, + dimension_type dimension, + Row_container_type* rowContainer, + Column_settings* colSettings); + /** + * @brief Copy constructor. If @p operators or @p cellConstructor is not a null pointer, its value is kept + * instead of the one in the copied column. + * + * @param column Column to copy. + * @param colSettings Pointer to a setting structure or `nullptr`. The structure should contain all the necessary + * classes specific to the column type, such as custom allocators. The specificities are this way hidden behind + * a commun interface for all column types. If @p colSettings is not specified or is equal to `nullptr`, the structure + * stored in @p column is used instead. + */ + PersistenceMatrixColumn(const PersistenceMatrixColumn& column, + Column_settings* colSettings = nullptr); + /** + * @brief Copy constructor with row access. + * If @p operators or @p cellConstructor is not a null pointer, its value is kept + * instead of the one in the copied column. + * + * @tparam Row_container_type Either std::map if @ref PersistenceMatrixOptions::has_removable_rows is true or + * std::vector. + * @param column Column to copy. + * @param columnIndex @ref MatIdx column index of the new column once copied. + * @param rowContainer Pointer to the row container that will be forwarded to @ref Row_access. + * @param colSettings Pointer to a setting structure or `nullptr`. The structure should contain all the necessary + * classes specific to the column type, such as custom allocators. The specificities are this way hidden behind + * a commun interface for all column types. If @p colSettings is not specified or is equal to `nullptr`, the structure + * stored in @p column is used instead. + */ + template + PersistenceMatrixColumn(const PersistenceMatrixColumn& column, + index columnIndex, + Row_container_type* rowContainer, + Column_settings* colSettings = nullptr); + /** + * @brief Move constructor. + * + * @param column Column to move. + */ + PersistenceMatrixColumn(PersistenceMatrixColumn&& column) noexcept; + /** + * @brief Destructor. + */ + ~PersistenceMatrixColumn(); + + /** + * @brief Returns the values of the column, zero values included. + * + * @param columnLength Number of rows to be returned. If -1, the number of rows is fixed at the biggest + * row index with non zero value. Default value: -1. + * @return Vector of @ref Field_element_type. At element \f$ i \f$ of the vector will be stored the value + * at row \f$ i \f$ of the column. + */ + std::vector get_content(int columnLength = -1) const; + /** + * @brief Indicates if the cell at given row index has value zero. + * + * @param rowIndex Row index to look at. + * @return true If the cell has value zero. + * @return false Otherwise. + */ + bool is_non_zero(id_index rowIndex) const; + /** + * @brief Indicates if the column is empty or has only zero values. + * + * @return true If the column is empty or only has zero values. + * @return false Otherwise. + */ + bool is_empty(); + /** + * @brief Returns the size of the underlying container. + * + * @warning Depending of the column type, the container does not have to contain only the non-zero cells. + * Even if for most of the types, the size of the container will correspond to the number of non-zero values + * in the column, it is not always the case. See description of the actual Column class for more details. + * + * @return Size of the underlying container. + */ + std::size_t size() const; + + /** + * @brief Reorders the column with the given map of row indices. Also changes the column index stored in the + * cells if row access is enabled and @p columnIndex is not -1. + * + * Only useful for @ref basematrix "base" and @ref boundarymatrix "boundary matrices" using lazy swaps. + * + * @tparam Map_type Map with an %at() method. + * @param valueMap Map such that `valueMap.at(i)` indicates the new row index of the cell + * at current row index `i`. + * @param columnIndex New @ref MatIdx column index of the column. If -1, the index does not change. Ignored if + * the row access is not enabled. Default value: -1. + */ + template + void reorder(const Map_type& valueMap, [[maybe_unused]] index columnIndex = -1); + /** + * @brief Zeros/empties the column. + * + * Only useful for @ref basematrix "base" and @ref boundarymatrix "boundary matrices". + * Used in @ref Matrix::zero_column and in the reduction algorithm for the persistence barcode. + */ + void clear(); + /** + * @brief Zeros the cell at given row index. + * + * Only useful for @ref basematrix "base" and @ref boundarymatrix "boundary matrices". + * Used in @ref Matrix::zero_cell and during vine swaps. + * + * @warning For @ref Vector_column, do not clear a cell that was already at zero or the results of @ref size and + * @ref is_empty will be wrong. + * + * @param rowIndex Row index of the cell to zero. + */ + void clear(id_index rowIndex); + + /** + * @brief Returns the row index of the pivot. If the column does not have a pivot, returns -1. + * + * Only useful for @ref boundarymatrix "boundary" and @ref chainmatrix "chain matrices". + * + * @return Row index of the pivot or -1. + */ + id_index get_pivot(); + /** + * @brief Returns the value of the pivot. If the column does not have a pivot, returns 0. + * + * Has to have value 1 if \f$ Z_2 \f$ coefficients are used. + * + * Only useful for @ref boundarymatrix "boundary" and @ref chainmatrix "chain matrices". + * + * @return The value of the pivot or 0. + */ + Field_element_type get_pivot_value(); + + /** + * @brief Returns a begin @ref Cell iterator to iterate over all cells contained in the underlying container. + * + * @warning The iterators really just iterate over the underlying container. Depending of the column type, + * neither the content nor the order is garanteed. See description of the actual Column class for more details. + * + * @return @ref Cell iterator. + */ + iterator begin() noexcept; + /** + * @brief Returns a begin @ref Cell const iterator to iterate over all cells contained in the underlying container. + * + * @warning The iterators really just iterate over the underlying container. Depending of the column type, + * neither the content nor the order is garanteed. See description of the actual Column class for more details. + * + * @return @ref Cell const iterator. + */ + const_iterator begin() const noexcept; + /** + * @brief Returns a end @ref Cell iterator, iterating over all cells contained in the underlying container. + * + * @warning The iterators really just iterate over the underlying container. Depending of the column type, + * neither the content nor the order is garanteed. See description of the actual Column class for more details. + * + * @return @ref Cell iterator. + */ + iterator end() noexcept; + /** + * @brief Returns a end @ref Cell const iterator, iterating over all cells contained in the underlying container. + * + * @warning The iterators really just iterate over the underlying container. Depending of the column type, + * neither the content nor the order is garanteed. See description of the actual Column class for more details. + * + * @return @ref Cell const iterator. + */ + const_iterator end() const noexcept; + /** + * @brief Returns a begin @ref Cell reverse iterator to iterate over all cells contained in the underlying container. + * + * @warning The iterators really just iterate over the underlying container. Depending of the column type, + * neither the content nor the order is garanteed. See description of the actual Column class for more details. + * + * @return @ref Cell reverse iterator. + */ + reverse_iterator rbegin() noexcept; + /** + * @brief Returns a begin @ref Cell const reverse iterator to iterate over all cells contained in the underlying + * container. + * + * @warning The iterators really just iterate over the underlying container. Depending of the column type, + * neither the content nor the order is garanteed. See description of the actual Column class for more details. + * + * @return @ref Cell const reverse iterator. + */ + const_reverse_iterator rbegin() const noexcept; + /** + * @brief Returns a end @ref Cell reverse iterator, iterating over all cells contained in the underlying container. + * + * @warning The iterators really just iterate over the underlying container. Depending of the column type, + * neither the content nor the order is garanteed. See description of the actual Column class for more details. + * + * @return @ref Cell reverse iterator. + */ + reverse_iterator rend() noexcept; + /** + * @brief Returns a end @ref Cell const reverse iterator, iterating over all cells contained in the underlying + * container. + * + * @warning The iterators really just iterate over the underlying container. Depending of the column type, + * neither the content nor the order is garanteed. See description of the actual Column class for more details. + * + * @return @ref Cell const reverse iterator. + */ + const_reverse_iterator rend() const noexcept; + + /** + * @brief Adds the given @ref Cell range onto the column. + * + * @tparam Cell_range @ref Cell range with %begin() and %end() method. + * Has to be ordered by row index if not specified otherwise. + * @param column @ref Cell range. Only the stored row index and the stored element value + * (if @ref PersistenceMatrixOptions::is_z2 is false) are token into account for this method. + * Even if @ref PersistenceMatrixOptions::has_row_access is true, the column index does not need to be correct. + * @return Reference to this column. + */ + template + PersistenceMatrixColumn& operator+=(const Cell_range& column); + /** + * @brief Adds the given column onto this column. + * + * @param column Column to add. + * @return Reference to this column. + */ + PersistenceMatrixColumn& operator+=(PersistenceMatrixColumn& column); + + /** + * @brief Multiplies all values in the column with the given value. + * + * @param val Value to multiply. + * @return Reference to this column. + */ + PersistenceMatrixColumn& operator*=(const Field_element_type& val); + + /** + * @brief `this = val * this + column` + * + * @tparam Cell_range @ref Cell range with %begin() and %end() method. + * Has to be ordered by row index if not specified otherwise. + * @param val Value to multiply. + * @param column @ref Cell range. Only the stored row index and the stored element value + * (if @ref PersistenceMatrixOptions::is_z2 is false) are token into account for this method. + * Even if @ref PersistenceMatrixOptions::has_row_access is true, the column index does not need to be correct. + * @return Reference to this column. + */ + template + PersistenceMatrixColumn& multiply_target_and_add(const Field_element_type& val, const Cell_range& column); + + /** + * @brief `this = this + column * val` + * + * @tparam Cell_range @ref Cell range with %begin() and %end() method. + * Has to be ordered by row index if not specified otherwise. + * @param column @ref Cell range. Only the stored row index and the stored element value + * (if @ref PersistenceMatrixOptions::is_z2 is false) are token into account for this method. + * Even if @ref PersistenceMatrixOptions::has_row_access is true, the column index does not need to be correct. + * @param val Value to multiply. + * @return Reference to this column. + */ + template + PersistenceMatrixColumn& multiply_source_and_add(const Cell_range& column, const Field_element_type& val); + + /** + * @brief Equality comparator. Equal in the sense that what is "supposed" to be contained in the columns is equal, + * not what is actually stored in the underlying container. For exemple, the underlying container of + * @ref Vector_column can contain cells which were erased explicitely by @ref clear(index). Those cells should not + * be taken into account while comparing. + * + * @param c1 First column to compare. + * @param c2 Second column to compare. + * @return true If both column are equal. + * @return false Otherwise. + */ + friend bool operator==(const PersistenceMatrixColumn& c1, const PersistenceMatrixColumn& c2); + /** + * @brief "Strictly smaller than" comparator. Usually a lexicographical order, but what matters is that the + * order is total. The order should apply on what is "supposed" to be contained in the columns, + * not what is actually stored in the underlying container. For exemple, the underlying container of + * @ref Vector_column can contain cells which were erased explicitely by @ref clear(index). Those cells should not + * be taken into account while comparing. + * + * @param c1 First column to compare. + * @param c2 Second column to compare. + * @return true If the first column is strictly smaller than the second one. + * @return false Otherwise. + */ + friend bool operator<(const PersistenceMatrixColumn& c1, const PersistenceMatrixColumn& c2); + + /** + * @brief Assign operator. Should be disabled when row access is enabled. + */ + PersistenceMatrixColumn& operator=(const PersistenceMatrixColumn& other); + /** + * @brief Swap operator. + */ + friend void swap(PersistenceMatrixColumn& col1, PersistenceMatrixColumn& col2); +}; + +} // namespace persistence_matrix +} // namespace Gudhi diff --git a/src/Persistence_matrix/concept/PersistenceMatrixOptions.h b/src/Persistence_matrix/concept/PersistenceMatrixOptions.h new file mode 100644 index 0000000000..b26b362606 --- /dev/null +++ b/src/Persistence_matrix/concept/PersistenceMatrixOptions.h @@ -0,0 +1,181 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2024 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file PersistenceMatrixOptions.h + * @author Hannah Schreiber + * @brief Contains the concept for the matrix options. + */ + +/// Gudhi namespace. +namespace Gudhi { +/// Persistence matrix namespace. +namespace persistence_matrix { + +/** + * @ingroup persistence_matrix + * + * @brief Concept of the template parameter for the class @ref Matrix. + * + * An implementation of this concept is @ref Default_options. + * If you want to provide your own, it is recommended that you derive from it and override some parts instead of + * writing a class from scratch. + */ +struct PersistenceMatrixOptions +{ + /** + * @brief Field operators. Has to follow the @ref FieldOperators concept. + * The type will not be used if @ref is_z2 is set to true, so it can be set to anything. + */ + using Field_coeff_operators = unspecified; + /** + * @brief Type for the dimension. Has to be an integer type. + * If unsigned, the maximal value of the type should not be attained during a run. + */ + using dimension_type = unspecified; + /** + * @brief Type for the different indexation types and should be able to contain the maximal number of columns + * of the matrix during a run. Has to be an integer type. + * If unsigned, the maximal value of the type should not be attained during a run. + */ + using index_type = unspecified; + + /** + * @brief If true, indicates that the values contained in the matrix are in \f$ Z_2 \f$ and can therefore + * be treated like booleans. If set to false, the values are assumed to be in the field \f$ Z_p \f$ for + * some prime \f$ p \f$ given by @ref Field_coeff_operators. It is highly recommended to set the variable to true, + * if \f$ p = 2 \f$. + */ + static const bool is_z2; + /** + * @brief Specifies the desired column type. All possible column types are described in @ref Column_types. + */ + static const Column_types column_type; + /** + * @brief Specifies the desired indexation scheme to access the methods of the matrix. + * See @ref mp_indexation "matrix description" and @ref Column_indexation_types for more details about the meaning + * of the indexation types. + */ + static const Column_indexation_types column_indexation_type; + + /** + * @brief Only enabled for @ref basematrix "base matrices" (i.e., none of the following is true: + * @ref has_column_pairings, @ref has_vine_update, @ref can_retrieve_representative_cycles), is ignored otherwise. + * If set to true, two identical columns in the matrix are not explicitely stored separately but are represented + * by a same column. + * + * Note that some methods of the @ref basematrix "base matrix" are not available when true: + * - @ref Matrix::insert_column(const Container_type&, index_type) "insert_column(const Container_type&, index)", + * - @ref Matrix::zero_column(index_type) "zero_column(index)", + * - @ref Matrix::zero_cell(index_type, index_type) "zero_cell(index, id_index)", + * - @ref Matrix::swap_columns(index_type, index_type) "swap_columns(index, index)", + * - @ref Matrix::swap_rows(index_type, index_type) "swap_rows(index, index)", + * - @ref Matrix::remove_column(index_type) "remove_column(index)", + * - @ref Matrix::remove_last "remove_last()". + */ + static const bool has_column_compression; + /** + * @brief Only enabled for @ref basematrix "base matrices" or simple @ref boundarymatrix "boundary matrices", i.e., + * when both @ref has_vine_update and @ref can_retrieve_representative_cycles are false. + * If set to true, the methods @ref Matrix::swap_columns and @ref Matrix::swap_rows are enabled. + */ + static const bool has_column_and_row_swaps; + + /** + * @brief If set to true, the underlying container containing the matrix columns is an std::unordered_map. + * If set to false, the container is a std::vector. By default, it is recommended to set it to false, but some + * methods require it to be true to be enabled: + * - @ref Matrix::remove_column(index_type) "remove_column(index)" for @ref basematrix "base matrices", + * - @ref Matrix::remove_maximal_face(index_type) "remove_maximal_face(index)" for @ref chainmatrix "chain matrices", + * - @ref Matrix::remove_maximal_face(index_type, const std::vector&) + * "remove_maximal_face(id_index, const std::vector&)" for @ref chainmatrix "chain matrices", + * - @ref Matrix::remove_last "remove_last()" for @ref chainmatrix "chain matrices" if @ref has_vine_update is true. + */ + static const bool has_map_column_container; + /** + * @brief If set to true, enables the methods @ref Matrix::remove_maximal_face and @ref Matrix::remove_last, + * except for @ref basematrix "base matrices" when @ref has_column_compression is true. + */ + static const bool has_removable_columns; + + /** + * @brief If set to true, enables the method @ref Matrix::get_row. + */ + static const bool has_row_access; + /** + * @brief Only enabled if @ref has_row_access is true, ignored otherwise. + * If set to true, the underlying container representing a row is an boost::intrusive::list. + * If set to false, the container is a std::set. It is usually recommended to set it to true. + */ + static const bool has_intrusive_rows; + /** + * @brief Only enabled if @ref has_row_access is true, ignored otherwise. + * If set to true, the underlying container containing the rows is an std::map and for + * @ref chainmatrix "chain matrices", enables the method @ref Matrix::erase_empty_row (always enabled for other + * matrix types). If set to false, the container is a std::vector. + */ + static const bool has_removable_rows; + + /** + * @brief Only used, when at least one of the following is true: + * @ref has_column_pairings, @ref has_vine_update or @ref can_retrieve_representative_cycles. Is ignored otherwise. + * If set to true, the matrix is a @ref boundarymatrix "boundary matrix". If set to false, the matrix is a + * @ref chainmatrix "chain matrix". + */ + static const bool is_of_boundary_type; + + /** + * @brief Only enabled for @ref boundarymatrix "boundary" and @ref chainmatrix "chain matrices", i.e., when at least + * one of the following is true: @ref has_column_pairings, @ref has_vine_update or + * @ref can_retrieve_representative_cycles. Is ignored otherwise (the notion of dimension makes generally no + * sense then). If set to true, enables the method @ref Matrix::get_max_dimension. If set to false, the method is + * disabled except when @ref has_column_pairings is true and @ref has_vine_update and + * @ref can_retrieve_representative_cycles are both false. In this case, the method is always available. + */ + static const bool has_matrix_maximal_dimension_access; + /** + * @brief If set to true, enables the method @ref Matrix::get_current_barcode. The matrix will then either be a + * @ref boundarymatrix "boundary matrix" (if @ref is_of_boundary_type is true), or a @ref chainmatrix "chain matrix" + * (if @ref is_of_boundary_type is false). + */ + static const bool has_column_pairings; + /** + * @brief If set to true, enables the methods @ref Matrix::vine_swap and @ref Matrix::vine_swap_with_z_eq_1_case. + * The matrix will then either be a @ref boundarymatrix "boundary matrix" (if @ref is_of_boundary_type is true), + * or a @ref chainmatrix "chain matrix" (if @ref is_of_boundary_type is false). + */ + static const bool has_vine_update; + /** + * @brief If set to true, enables the methods @ref Matrix::update_representative_cycles and + * @ref Matrix::get_representative_cycles. + * The matrix will then either be a @ref boundarymatrix "boundary matrix" (if @ref is_of_boundary_type is true), + * or a @ref chainmatrix "chain matrix" (if @ref is_of_boundary_type is false). + */ + static const bool can_retrieve_representative_cycles; + + // not implemented yet + // /** + // * @brief Only enabled for boundary and @ref chainmatrix "chain matrices", i.e., when at least one of + // * the following is true: @ref has_column_pairings, @ref has_vine_update or + // * @ref can_retrieve_representative_cycles. + // * Is ignored otherwise + // * If set to true, the matrix is decomposed in several submatrices containing each all the + // * columns of same dimension. + // */ + // static const bool is_separated_by_dimension; + // /** + // * @brief If set to true, some methods will use parallel computing. + // */ + // static const bool is_parallelizable; +}; + +} // namespace persistence_matrix +} // namespace Gudhi + diff --git a/src/Persistence_matrix/doc/COPYRIGHT b/src/Persistence_matrix/doc/COPYRIGHT new file mode 100644 index 0000000000..236bfc6351 --- /dev/null +++ b/src/Persistence_matrix/doc/COPYRIGHT @@ -0,0 +1,12 @@ +The files of this directory are part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. +See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + +Author(s): Hannah Schreiber + +Copyright (C) 2024 Inria + +This gives everyone the freedoms to use openFrameworks in any context: +commercial or non-commercial, public or private, open or closed source. + +You should have received a copy of the MIT License along with this program. +If not, see https://opensource.org/licenses/MIT. diff --git a/src/Persistence_matrix/doc/Intro_field_elements_and_operators.h b/src/Persistence_matrix/doc/Intro_field_elements_and_operators.h new file mode 100644 index 0000000000..938fccc842 --- /dev/null +++ b/src/Persistence_matrix/doc/Intro_field_elements_and_operators.h @@ -0,0 +1,46 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2024 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +#ifndef DOC_PERSISTENCE_MATRIX_INTRO_FIELDS_H_ +#define DOC_PERSISTENCE_MATRIX_INTRO_FIELDS_H_ + +// needs namespace for Doxygen to link on classes +namespace Gudhi { +namespace persistence_fields { + +/** \defgroup persistence_fields Persistence Fields + * @{ + * \author Hannah Schreiber, Clément Maria + * + * Set of classes allowing addition and multiplication, as well as inverse computation, in \f$ \mathbb{F}_p \f$ fields, + * with \f$ p \f$ some prime number, or in multi-fields as defined in \cite boissonnat:hal-00922572. + * + * There are two types of classes: + * - those defining directly a field element, allowing to use them as any integer: the operators are overwritten such + * that the calculation is done in the field. For example, if \f$ e = 2 \f$ is an instanciation of an + * \f$ \mathbb{F}_3 \f$ element class, then `e + 3` returns an element instanciation of value `2`, + * - those only defining the operators of a field or multi-field. They represent a collection of methods taking + * one or two integers as input and treating them as elements of the field. For example, if \f$ op \f$ is an + * instanciation of a \f$ \mathbb{F}_3 \f$ operator class, `op.add(2, 3)` returns `2`. + * + * The field operator classes all respect the @ref persistence_matrix::FieldOperators concept. + * + * \subsection fieldsexamples Examples + * + * Here is a list of examples using the module: + * \li \gudhi_example_link{Persistence_field,example_field_operations.cpp} - A simple example to showcase how + * to use the different field element and operator classes. + * + * @} + */ +} // namespace persistence_fields +} // namespace Gudhi + +#endif // DOC_PERSISTENCE_MATRIX_INTRO_FIELDS_H_ diff --git a/src/Persistence_matrix/doc/Intro_persistence_matrix.h b/src/Persistence_matrix/doc/Intro_persistence_matrix.h new file mode 100644 index 0000000000..359001e2c2 --- /dev/null +++ b/src/Persistence_matrix/doc/Intro_persistence_matrix.h @@ -0,0 +1,54 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2024 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +#ifndef DOC_PERSISTENCE_MATRIX_INTRO_PERSISTENCE_MATRIX_H_ +#define DOC_PERSISTENCE_MATRIX_INTRO_PERSISTENCE_MATRIX_H_ + +// needs namespace for Doxygen to link on classes +namespace Gudhi { +namespace persistence_matrix { + +/** \defgroup persistence_matrix Persistence Matrix + * @{ + * \author Hannah Schreiber + * + * The module provides a data structure for matrices, in particular thought for matrices representing filtered complexes + * and used as backend for persistence algorithms, such at persistent homology, @ref persistent_cohomology, + * or zigzag @cite zigzag. + * + * The structure is entirely accessed via the class @ref Matrix and it provides several functionnalities which can + * be enabled or disabled through a template argument following the @ref PersistenceMatrixOptions concept. + * The main functionnalities are: + * @li column and row access, + * @li column addition and scalar multiplication, + * @li removal of maximal faces while maintaining a valid reduced boundary matrix or compatible chain complex base + * and a valid barcode with respect to the new filtration, + * @li computation of persistent homology (but note that if the barcode is your only necessity, using the + * @ref persistent_cohomology module is often more performant), + * @li computation of representative cycles for the cycle classes, + * @li swapping of two consecutive faces in a filtration (cf. vineyards @cite vineyards) while maintaining a valid + * reduced boundary matrix or compatible chain complex base and a valid barcode with respect to the new filtration, + * + * + * \subsection matrixexamples Examples + * + * Here is a list of examples using the module: + * \li \gudhi_example_link{Persistence_matrix,representative_cycles_from_matrix.cpp} - A simple example on how to + * use the matrix to compute representative cycles. + * + * \li \gudhi_example_link{Persistence_matrix,simplex_tree_to_matrix.cpp} - A simplex example on how to build + * a the different matrices from a simplex tree. + * + * @} + */ +} // namespace persistence_matrix +} // namespace Gudhi + +#endif // DOC_PERSISTENCE_MATRIX_INTRO_PERSISTENCE_MATRIX_H_ diff --git a/src/Persistence_matrix/example/CMakeLists.txt b/src/Persistence_matrix/example/CMakeLists.txt new file mode 100644 index 0000000000..a5c6ccbedc --- /dev/null +++ b/src/Persistence_matrix/example/CMakeLists.txt @@ -0,0 +1,19 @@ +project(Matrix_examples) + +add_executable(Matrix_examples_representative_cycles representative_cycles_from_matrix.cpp) +if(TARGET TBB::tbb) + target_link_libraries(Matrix_examples_representative_cycles TBB::tbb) +endif() +add_test(NAME Matrix_examples_representative_cycles COMMAND $) + +add_executable(Matrix_examples_simplex_tree_matrix simplex_tree_to_matrix.cpp) +if(TARGET TBB::tbb) + target_link_libraries(Matrix_examples_simplex_tree_matrix TBB::tbb) +endif() +add_test(NAME Matrix_examples_simplex_tree_matrix COMMAND $) + +add_executable(Matrix_examples_field_operations example_field_operations.cpp) +if(TARGET TBB::tbb) + target_link_libraries(Matrix_examples_field_operations TBB::tbb) +endif() +add_test(NAME Matrix_examples_field_operations COMMAND $) diff --git a/src/Persistence_matrix/example/example_field_operations.cpp b/src/Persistence_matrix/example/example_field_operations.cpp new file mode 100644 index 0000000000..d288219553 --- /dev/null +++ b/src/Persistence_matrix/example/example_field_operations.cpp @@ -0,0 +1,91 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2024 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +#include + +#include +#include +#include +#include +#include + +using Gudhi::persistence_fields::Z2_field_element; +using Gudhi::persistence_fields::Z2_field_operators; +using Gudhi::persistence_fields::Zp_field_element; +using Gudhi::persistence_fields::Shared_Zp_field_element; +using Gudhi::persistence_fields::Zp_field_operators; + +template +void field_element_example(int ini){ + Field_element e = ini; + std::cout << "Element e initialized with " << ini << "\n"; + std::cout << "Characteristic: " << e.get_characteristic() << "\n"; + std::cout << "e: " << e << "\n"; + std::cout << "e + 3: " << (e + 3) << "\n"; + std::cout << "3 + e: " << (3 + e) << "\n"; + e += 3; + std::cout << "e += 3: " << e << "\n"; + int t = 3; + t += e; //standard integer addition + std::cout << "3 += e: " << t << " //standard integer addition\n"; + std::cout << "e - 4: " << (e - 4) << "\n"; + std::cout << "4 - e: " << (4 - e) << "\n"; + e -= 4; + std::cout << "e -= 4: " << e << "\n"; + t = 4; + t -= e; //standard integer addition + std::cout << "4 -= e: " << t << " //standard integer substraction\n"; + std::cout << "e * 6: " << (e * 6) << "\n"; + std::cout << "6 * e: " << (6 * e) << "\n"; + e *= 6; + std::cout << "e *= 6: " << e << "\n"; + t = 6; + t *= e; //standard integer addition + std::cout << "6 *= e: " << t << " //standard integer multiplication\n"; + std::cout << "Inverse: " << e.get_inverse() << "\n"; +} + +template +void field_operator_example(const Field_operator& op){ + std::cout << "Characteristic: " << op.get_characteristic() << "\n"; + std::cout << "2 + 3: " << op.add(2u, 3u) << "\n"; + std::cout << "3 + 2: " << op.add(3u, 2u) << "\n"; + std::cout << "10 - 4: " << op.substract(10u, 4u) << "\n"; + std::cout << "4 - 10: " << op.substract(4u, 10u) << "\n"; + std::cout << "3 * 6: " << op.multiply(3u, 6u) << "\n"; + std::cout << "6 * 3: " << op.multiply(6u, 3u) << "\n"; + std::cout << "Value of 7: " << op.get_value(7u) << "\n"; + std::cout << "Inverse of 7: " << op.get_inverse(7u) << "\n"; +} + +int main() { + std::cout << "=== Example for Z2 field elements ===\n\n"; + field_element_example(2); + + std::cout << "\n=== Example for Z3 field elements ===\n\n"; + field_element_example >(5); + + std::cout << "\n=== Example for Z5 field elements ===\n\n"; + Shared_Zp_field_element<>::initialize(5); + field_element_example >(4); + + std::cout << "\n+++ Example for Z2 field operator +++\n\n"; + Z2_field_operators z2op; + field_operator_example(z2op); + + std::cout << "\n+++ Example for Z3 field operator +++\n\n"; + Zp_field_operators zpop; + zpop.set_characteristic(3); + field_operator_example(zpop); + + std::cout << "\n+++ Example for Z5 field operator +++\n\n"; + zpop.set_characteristic(5); + field_operator_example(zpop); +} diff --git a/src/Persistence_matrix/example/representative_cycles_from_matrix.cpp b/src/Persistence_matrix/example/representative_cycles_from_matrix.cpp new file mode 100644 index 0000000000..f5b4bcef30 --- /dev/null +++ b/src/Persistence_matrix/example/representative_cycles_from_matrix.cpp @@ -0,0 +1,84 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2024 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +#include + +#include +#include + +using Gudhi::persistence_matrix::Default_options; +using Gudhi::persistence_matrix::Column_types; + +struct RU_rep_cycles_options : Default_options +{ + static const bool can_retrieve_representative_cycles = true; +}; + +struct Chain_rep_cycles_options : Default_options +{ + static const bool can_retrieve_representative_cycles = true; + static const bool is_of_boundary_type = false; +}; + +using RU_matrix = Gudhi::persistence_matrix::Matrix; +using Chain_matrix = Gudhi::persistence_matrix::Matrix; + +template +void print_representative_cycles_example() +{ + Matrix mp({ { }, + { }, + { }, + { }, + { }, + { }, + { }, + { 2, 3 }, + { 4, 5 }, + { 0, 2 }, + { 0, 1 }, + { 1, 3 }, + { 1, 2 }, + { 7, 11, 12 }, + { 9, 10, 12 }, + { 5, 6 }, + { 2, 4 }, + { 4, 6 }, + { 8, 15, 17 }, + { 3, 6 } + }); + + auto rc = mp.get_representative_cycles(); + for (auto cycle : rc) { + // cycle[0] gives the row index of a simplex in the cycle + // because the simplices where indexed from 0 continously, the simplex represented by the row index cycle[0] is + // the same simplex represented by the column at position cycle[0] in RU + // that is why `mp.get_column_dimension(cycle[0])` gives us the dimension of the simplex for RU_matrix + // + // for the chain matrix, the row index will always represent a simplex ID. So, + // `mp.get_column_dimension(mp.get_column_with_pivot(cycle[0]))` will always work to get the dimension + // of the simplex. But in this particlar case, because of the simplex indexation and the fact that no swap + // occured, mp.get_column_with_pivot(cycle[0]) == cycle[0] and so `mp.get_column_dimension(cycle[0])` also works. + std::cout << mp.get_column_dimension(cycle[0]); + std::cout << "-cycle: "; + for (auto index : cycle) { + std::cout << index << ", "; + } + std::cout << "\n"; + } +} + +int main() { + std::cout << "RU_matrix:\n"; + print_representative_cycles_example(); + std::cout << "\n"; + std::cout << "Chain_matrix:\n"; + print_representative_cycles_example(); +} diff --git a/src/Persistence_matrix/example/simplex_tree_to_matrix.cpp b/src/Persistence_matrix/example/simplex_tree_to_matrix.cpp new file mode 100644 index 0000000000..3799d3fe15 --- /dev/null +++ b/src/Persistence_matrix/example/simplex_tree_to_matrix.cpp @@ -0,0 +1,146 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2024 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +#include + +#include +#include +#include + +using Gudhi::persistence_matrix::Default_options; +using Gudhi::persistence_matrix::Column_types; +using Gudhi::Simplex_tree; + +struct RU_options : Default_options +{ + static const bool has_column_pairings = true; + static const bool can_retrieve_representative_cycles = true; +}; + +struct Chain_options : Default_options +{ + static const bool has_column_pairings = true; + static const bool is_of_boundary_type = false; +}; + +using Base_matrix = Gudhi::persistence_matrix::Matrix<>; +using RU_matrix = Gudhi::persistence_matrix::Matrix; +using Chain_matrix = Gudhi::persistence_matrix::Matrix; + +void build_simplex_tree(Simplex_tree<>& st){ + std::vector > simplices = { + {0}, + {1}, + {2}, + {3}, + {4}, + {5}, + {6}, + {2, 3}, + {4, 5}, + {0, 2}, + {0, 1}, + {1, 3}, + {1, 2}, + {1, 2, 3}, + {0, 1, 2}, + {5, 6}, + {2, 4}, + {4, 6}, + {4, 5, 6}, + {3, 6} + }; + + //insertion in the simplex tree + for (unsigned int i = 0; i < simplices.size(); ++i){ + st.insert_simplex(simplices[i], i); + } +} + +template +void print_column(Column& col, unsigned int num, unsigned int size) { + std::cout << "col " << num << ": "; + if (num < 10) std::cout << " "; + for (const auto& e : col.get_content(size)) { + if (e == 0u) + std::cout << "- "; + else + std::cout << e << " "; + } + std::cout << "\n"; +} + +void print_matrix(Base_matrix& bm, unsigned int size){ + std::cout << "Base_matrix:\n"; + for (unsigned int i = 0; i < size; ++i) { + print_column(bm.get_column(i), i, size); + } + std::cout << "\n"; +} + +void print_matrix(const Chain_matrix& cm, unsigned int size){ + std::cout << "Chain_matrix:\n"; + // just note that if some vine swaps or removals occured, this would + // not give us the columns in the order of filtration anymore, but just + // in the order they are stored in the matrix + for (unsigned int i = 0; i < size; ++i) { + print_column(cm.get_column(i), i, size); + } + std::cout << "\n"; +} + +void print_matrix(RU_matrix& rum, unsigned int size){ + std::cout << "RU_matrix:\n"; + std::cout << "R:\n"; + for (unsigned int i = 0; i < size; ++i) { + print_column(rum.get_column(i), i, size); + } + std::cout << "\n"; + std::cout << "U:\n"; + for (unsigned int i = 0; i < size; ++i) { + print_column(rum.get_column(i, false), i, size); + } + std::cout << "\n"; +} + +int main() { + Simplex_tree<> st; + build_simplex_tree(st); //could be any other way to build a simplex tree + auto size = st.num_simplices(); + + //reserving space with `size` is not mandatory but recommended for better performances. + Base_matrix bm(size); + RU_matrix rum(size); + Chain_matrix cm(size); + + //filling the matrices with the boundary matrix computed from the simplex tree + unsigned int id = 0; + for (auto sh : st.filtration_simplex_range()){ + //identifying the simplex such that the IDs are strictly increasing in the order of filtration. + st.assign_key(sh, id++); + + //creating boundary + std::vector boundary; + for (auto b : st.boundary_simplex_range(sh)){ + boundary.push_back(st.key(b)); + } + std::sort(boundary.begin(), boundary.end()); //boundaries have to be ordered + + //insertion in the matrices, the id is continuously increasing from 0 so no need to give it as an argument. + bm.insert_boundary(boundary); + rum.insert_boundary(boundary); + cm.insert_boundary(boundary); + } + + //content of the matrices + print_matrix(bm, size); + print_matrix(rum, size); + print_matrix(cm, size); +} diff --git a/src/Persistence_matrix/include/gudhi/Fields/Multi_field.h b/src/Persistence_matrix/include/gudhi/Fields/Multi_field.h new file mode 100644 index 0000000000..d70647b94f --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Fields/Multi_field.h @@ -0,0 +1,484 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber, Clément Maria + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file Multi_field.h + * @author Hannah Schreiber, Clément Maria + * @brief Contains the @ref Multi_field_element class. + */ + +#ifndef MATRIX_FIELD_MULTI_H_ +#define MATRIX_FIELD_MULTI_H_ + +#include +#include +#include +#include + +namespace Gudhi { +namespace persistence_fields { + +/** + * @class Multi_field_element Multi_field.h gudhi/Fields/Multi_field.h + * @ingroup persistence_fields + * + * @brief Class representing an element of a multi-field. + * The characteristics will corresponds to all prime numbers in the interval given as template. + * + * @tparam minimum Interval closed lower bound. + * @tparam maximum Interval closed upper bound. + */ +template +class Multi_field_element { + public: + using element_type = mpz_class; /**< Type for the elements in the field. */ + using characteristic_type = element_type; /**< Type for the field characteristic. */ + + /** + * @brief Default constructor. Sets the element to 0. + */ + Multi_field_element(); + /** + * @brief Constructor setting the element to the given value. + * + * @param element Value of the element. + */ + Multi_field_element(const element_type& element); + /** + * @brief Copy constructor. + * + * @param toCopy Element to copy. + */ + Multi_field_element(const Multi_field_element& toCopy); + /** + * @brief Move constructor. + * + * @param toMove Element to move. + */ + Multi_field_element(Multi_field_element&& toMove) noexcept; + + /** + * @brief operator+= + */ + friend void operator+=(Multi_field_element& f1, Multi_field_element const& f2) { + f1.element_ += f2.element_; + mpz_mod(f1.element_.get_mpz_t(), f1.element_.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); + } + /** + * @brief operator+ + */ + friend Multi_field_element operator+(Multi_field_element f1, Multi_field_element const& f2) { + f1 += f2; + return f1; + } + /** + * @brief operator+= + */ + friend void operator+=(Multi_field_element& f, const element_type& v) { + f.element_ += v; + mpz_mod(f.element_.get_mpz_t(), f.element_.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); + } + /** + * @brief operator+ + */ + friend Multi_field_element operator+(Multi_field_element f, const element_type& v) { + f += v; + return f; + } + /** + * @brief operator+ + */ + friend element_type operator+(element_type v, Multi_field_element const& f) { + v += f.element_; + mpz_mod(v.get_mpz_t(), v.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); + return v; + } + + /** + * @brief operator-= + */ + friend void operator-=(Multi_field_element& f1, Multi_field_element const& f2) { + f1.element_ -= f2.element_; + mpz_mod(f1.element_.get_mpz_t(), f1.element_.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); + } + /** + * @brief operator- + */ + friend Multi_field_element operator-(Multi_field_element f1, Multi_field_element const& f2) { + f1 -= f2; + return f1; + } + /** + * @brief operator-= + */ + friend void operator-=(Multi_field_element& f, const element_type& v) { + f.element_ -= v; + mpz_mod(f.element_.get_mpz_t(), f.element_.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); + } + /** + * @brief operator- + */ + friend Multi_field_element operator-(Multi_field_element f, const element_type& v) { + f -= v; + return f; + } + /** + * @brief operator- + */ + friend element_type operator-(element_type v, Multi_field_element const& f) { + // element_type e(v); + if (v >= productOfAllCharacteristics_) + mpz_mod(v.get_mpz_t(), v.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); + if (f.element_ > v) v += productOfAllCharacteristics_; + v -= f.element_; + return v; + } + + /** + * @brief operator*= + */ + friend void operator*=(Multi_field_element& f1, Multi_field_element const& f2) { + f1.element_ *= f2.element_; + mpz_mod(f1.element_.get_mpz_t(), f1.element_.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); + } + /** + * @brief operator* + */ + friend Multi_field_element operator*(Multi_field_element f1, Multi_field_element const& f2) { + f1 *= f2; + return f1; + } + /** + * @brief operator*= + */ + friend void operator*=(Multi_field_element& f, const element_type& v) { + f.element_ *= v; + mpz_mod(f.element_.get_mpz_t(), f.element_.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); + } + /** + * @brief operator* + */ + friend Multi_field_element operator*(Multi_field_element f, const element_type& v) { + f *= v; + return f; + } + /** + * @brief operator* + */ + friend element_type operator*(element_type v, Multi_field_element const& f) { + v *= f.element_; + mpz_mod(v.get_mpz_t(), v.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); + return v; + } + + /** + * @brief operator== + */ + friend bool operator==(const Multi_field_element& f1, const Multi_field_element& f2) { + return f1.element_ == f2.element_; + } + /** + * @brief operator== + */ + friend bool operator==(const element_type& v, const Multi_field_element& f) { + if (v < productOfAllCharacteristics_) return v == f.element_; + element_type e(v); + mpz_mod(e.get_mpz_t(), e.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); + return e == f.element_; + } + /** + * @brief operator== + */ + friend bool operator==(const Multi_field_element& f, const element_type& v) { + if (v < productOfAllCharacteristics_) return v == f.element_; + element_type e(v); + mpz_mod(e.get_mpz_t(), e.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); + return e == f.element_; + } + /** + * @brief operator!= + */ + friend bool operator!=(const Multi_field_element& f1, const Multi_field_element& f2) { return !(f1 == f2); } + /** + * @brief operator!= + */ + friend bool operator!=(const element_type& v, const Multi_field_element& f) { return !(v == f); } + /** + * @brief operator!= + */ + friend bool operator!=(const Multi_field_element& f, const element_type& v) { return !(v == f); } + + /** + * @brief Assign operator. + */ + Multi_field_element& operator=(Multi_field_element other); + /** + * @brief Assign operator. + */ + Multi_field_element& operator=(const element_type& value); + /** + * @brief Swap operator. + */ + friend void swap(Multi_field_element& f1, Multi_field_element& f2) { std::swap(f1.element_, f2.element_); } + + /** + * @brief Casts the element into an unsigned int. + */ + operator unsigned int() const; + /** + * @brief Casts the element into an mpz_class. + */ + operator mpz_class() const; + + /** + * @brief Returns the inverse of the element in the multi-field, see @cite boissonnat:hal-00922572. + * + * @return The inverse. + */ + Multi_field_element get_inverse() const; + /** + * @brief Returns the inverse of the element with respect to a sub-product of the characteristics in the multi-field, + * see @cite boissonnat:hal-00922572. + * + * @param productOfCharacteristics Sub-product of the characteristics. + * @return Pair of the inverse and the characteristic the inverse corresponds to. + */ + std::pair get_partial_inverse( + const characteristic_type& productOfCharacteristics) const; + + /** + * @brief Returns the additive identity of a field. + * + * @return The additive identity of a field. + */ + static Multi_field_element get_additive_identity(); + /** + * @brief Returns the multiplicative identity of a field. + * + * @return The multiplicative identity of a field. + */ + static Multi_field_element get_multiplicative_identity(); + /** + * @brief Returns the partial multiplicative identity of the multi-field from the given product. + * See @cite boissonnat:hal-00922572 for more details. + * + * @param productOfCharacteristics Product of the different characteristics to take into account in the multi-field. + * @return The partial multiplicative identity of the multi-field. + */ + static Multi_field_element get_partial_multiplicative_identity(const characteristic_type& productOfCharacteristics); + /** + * @brief Returns the product of all characteristics. + * + * @return The product of all characteristics. + */ + static characteristic_type get_characteristic(); + + /** + * @brief Returns the value of the element. + * + * @return Value of the element. + */ + element_type get_value() const; + + // static constexpr bool handles_only_z2() { return false; } + + private: + element_type element_; + static inline const std::vector primes_ = []() { + std::vector res; + + unsigned int curr_prime = minimum; + mpz_t tmp_prime; + mpz_init_set_ui(tmp_prime, minimum); + // test if min_prime is prime + int is_prime = mpz_probab_prime_p(tmp_prime, 25); // probabilistic primality test + + if (is_prime == 0) { // min_prime is composite + mpz_nextprime(tmp_prime, tmp_prime); + curr_prime = mpz_get_ui(tmp_prime); + } + + while (curr_prime <= maximum) { + res.push_back(curr_prime); + mpz_nextprime(tmp_prime, tmp_prime); + curr_prime = mpz_get_ui(tmp_prime); + } + mpz_clear(tmp_prime); + + return res; + }(); + static inline const characteristic_type productOfAllCharacteristics_ = []() { + characteristic_type res = 1; + for (const auto p : primes_) { + res *= p; + } + + return res; + }(); + static inline const std::vector partials_ = []() { + std::vector res; + + if (productOfAllCharacteristics_ == 1) return res; + + for (unsigned int i = 0; i < primes_.size(); ++i) { + unsigned int p = primes_[i]; + res.push_back(productOfAllCharacteristics_ / p); + mpz_powm_ui(res.back().get_mpz_t(), res.back().get_mpz_t(), p - 1, productOfAllCharacteristics_.get_mpz_t()); + } + + return res; + }(); + // If I understood the paper well, multiplicativeID_ always equals to 1. But in Clement's code, + // multiplicativeID_ is computed (see commented lambda function below). TODO: verify with Clement. + static inline const element_type multiplicativeID_ = 1; /*[](){ + mpz_class res = 0; + for (unsigned int i = 0; i < partials_.size(); ++i){ + res = (res + partials_[i]) % productOfAllCharacteristics_; + } + + return res; + }();*/ + + static constexpr bool _is_prime(const int p); +}; + +template +inline Multi_field_element::Multi_field_element() : element_(0) { + static_assert(maximum >= 2, "Characteristics have to be positive."); + static_assert(minimum <= maximum, "The given interval is not valid."); + static_assert(minimum != maximum || _is_prime(minimum), "The given interval does not contain a prime number."); + + if (productOfAllCharacteristics_ == 1) + throw std::runtime_error("The given interval does not contain a prime number."); +} + +template +inline Multi_field_element::Multi_field_element(const element_type& element) : element_(element) { + static_assert(maximum >= 2, "Characteristics has to be positive."); + static_assert(minimum <= maximum, "The given interval is not valid."); + static_assert(minimum != maximum || _is_prime(minimum), "The given interval does not contain a prime number."); + + if (productOfAllCharacteristics_ == 1) + throw std::runtime_error("The given interval does not contain a prime number."); + + mpz_mod(element_.get_mpz_t(), element_.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); +} + +template +inline Multi_field_element::Multi_field_element(const Multi_field_element& toCopy) + : element_(toCopy.element_) {} + +template +inline Multi_field_element::Multi_field_element( + Multi_field_element&& toMove) noexcept + : element_(std::move(toMove.element_)) {} + +template +inline Multi_field_element& Multi_field_element::operator=( + Multi_field_element other) { + std::swap(element_, other.element_); + return *this; +} + +template +inline Multi_field_element& Multi_field_element::operator=( + const element_type& value) { + mpz_mod(element_.get_mpz_t(), value.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); + return *this; +} + +template +inline Multi_field_element::operator unsigned int() const { + return element_.get_ui(); +} + +template +inline Multi_field_element::operator mpz_class() const { + return element_; +} + +template +inline Multi_field_element Multi_field_element::get_inverse() const { + return get_partial_inverse(productOfAllCharacteristics_).first; +} + +template +inline std::pair, + typename Multi_field_element::characteristic_type> +Multi_field_element::get_partial_inverse(const characteristic_type& productOfCharacteristics) const { + characteristic_type QR; + mpz_gcd(QR.get_mpz_t(), element_.get_mpz_t(), productOfCharacteristics.get_mpz_t()); // QR <- gcd(x,QS) + + if (QR == productOfCharacteristics) return {Multi_field_element(), multiplicativeID_}; // partial inverse is 0 + + characteristic_type QT = productOfCharacteristics / QR; + + characteristic_type inv_qt; + mpz_invert(inv_qt.get_mpz_t(), element_.get_mpz_t(), QT.get_mpz_t()); + + auto res = get_partial_multiplicative_identity(QT); + res *= inv_qt; + + return {res, QT}; +} + +template +inline Multi_field_element Multi_field_element::get_additive_identity() { + return Multi_field_element(); +} + +template +inline Multi_field_element Multi_field_element::get_multiplicative_identity() { + return Multi_field_element(multiplicativeID_); +} + +template +inline Multi_field_element Multi_field_element::get_partial_multiplicative_identity( + const characteristic_type& productOfCharacteristics) { + if (productOfCharacteristics == 0) { + return Multi_field_element(multiplicativeID_); + } + Multi_field_element mult; + for (unsigned int idx = 0; idx < primes_.size(); ++idx) { + if ((productOfCharacteristics % primes_[idx]) == 0) { + mult += partials_[idx]; + } + } + return mult; +} + +template +inline typename Multi_field_element::characteristic_type +Multi_field_element::get_characteristic() { + return productOfAllCharacteristics_; +} + +template +inline typename Multi_field_element::element_type Multi_field_element::get_value() + const { + return element_; +} + +template +inline constexpr bool Multi_field_element::_is_prime(const int p) { + if (p <= 1) return false; + if (p <= 3) return true; + if (p % 2 == 0 || p % 3 == 0) return false; + + for (long i = 5; i * i <= p; i = i + 6) + if (p % i == 0 || p % (i + 2) == 0) return false; + + return true; +} + +} // namespace persistence_fields +} // namespace Gudhi + +#endif // MATRIX_FIELD_MULTI_H_ diff --git a/src/Persistence_matrix/include/gudhi/Fields/Multi_field_operators.h b/src/Persistence_matrix/include/gudhi/Fields/Multi_field_operators.h new file mode 100644 index 0000000000..1c06dc2e92 --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Fields/Multi_field_operators.h @@ -0,0 +1,455 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber, Clément Maria + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file Multi_field_operators.h + * @author Hannah Schreiber, Clément Maria + * @brief Contains the @ref Multi_field_operators class. + */ + +#ifndef MATRIX_FIELD_MULTI_OPERATORS_H_ +#define MATRIX_FIELD_MULTI_OPERATORS_H_ + +#include +#include +#include +#include + +namespace Gudhi { +namespace persistence_fields { + +/** + * @class Multi_field_operators Multi_field_operators.h gudhi/Fields/Multi_field_operators.h + * @ingroup persistence_fields + * + * @brief Class defining operators for a multi-field with "consecutive" charateristic range. + */ +class Multi_field_operators +{ + public: + using element_type = mpz_class; /**< Type for the elements in the field. */ + using characteristic_type = element_type; /**< Type for the field characteristic. */ + + /** + * @brief Default constructor, sets the product of all characteristics to 0. + */ + Multi_field_operators() : productOfAllCharacteristics_(0) /* , multiplicativeID_(1) */ + {} + /** + * @brief Constructor setting the characteristics to all prime numbers between the two given integers. + * + * @param minCharacteristic Smallest value of a prime. + * @param maxCharacteristic Heighest value of a prime. + */ + Multi_field_operators(int minCharacteristic, int maxCharacteristic) + : productOfAllCharacteristics_(0) //, multiplicativeID_(1) + { + set_characteristic(minCharacteristic, maxCharacteristic); + } + /** + * @brief Copy constructor. + * + * @param toCopy Operators to copy. + */ + Multi_field_operators(const Multi_field_operators& toCopy) + : primes_(toCopy.primes_), + productOfAllCharacteristics_(toCopy.productOfAllCharacteristics_), + partials_(toCopy.partials_) /* , + multiplicativeID_(toCopy.multiplicativeID_) */ + {} + /** + * @brief Move constructor. + * + * @param toMove Operators to move. + */ + Multi_field_operators(Multi_field_operators&& toMove) noexcept + : primes_(std::move(toMove.primes_)), + productOfAllCharacteristics_(std::move(toMove.productOfAllCharacteristics_)), + partials_(std::move(toMove.partials_)) /* , + multiplicativeID_(std::move(toMove.multiplicativeID_)) */ + {} + + /** + * @brief Set the characteristics of the field, which are stored in a single value as a product of all of them. + * The characteristics will be all prime numbers in the given interval. + * + * @param minimum Smallest value of a prime. + * @param maximum Heighest value of a prime. + */ + void set_characteristic(int minimum, int maximum) { + if (maximum < 2) throw std::invalid_argument("Characteristic must be strictly positive"); + if (minimum > maximum) throw std::invalid_argument("The given interval is not valid."); + if (minimum == maximum && !_is_prime(minimum)) + throw std::invalid_argument("The given interval does not contain a prime number."); + + unsigned int curr_prime = minimum; + mpz_t tmp_prime; + mpz_init_set_ui(tmp_prime, minimum); + // test if min_prime is prime + int is_prime = mpz_probab_prime_p(tmp_prime, 25); // probabilistic primality test + + if (is_prime == 0) { // min_prime is composite + mpz_nextprime(tmp_prime, tmp_prime); + curr_prime = mpz_get_ui(tmp_prime); + } + + primes_.clear(); + while (curr_prime <= static_cast(maximum)) { + primes_.push_back(curr_prime); + mpz_nextprime(tmp_prime, tmp_prime); + curr_prime = mpz_get_ui(tmp_prime); + } + mpz_clear(tmp_prime); + + if (primes_.empty()) throw std::invalid_argument("The given interval does not contain a prime number."); + + productOfAllCharacteristics_ = 1; + for (const unsigned int p : primes_) { + productOfAllCharacteristics_ *= p; + } + + partials_.resize(primes_.size()); + for (unsigned int i = 0; i < primes_.size(); ++i) { + unsigned int p = primes_[i]; + partials_[i] = productOfAllCharacteristics_ / p; + mpz_powm_ui(partials_[i].get_mpz_t(), partials_[i].get_mpz_t(), p - 1, productOfAllCharacteristics_.get_mpz_t()); + } + + // If I understood the paper well, multiplicativeID_ always equals to 1. But in Clement's code, + // multiplicativeID_ is computed (see commented loop below). TODO: verify with Clement. + // for (unsigned int i = 0; i < partials_.size(); ++i) { + // multiplicativeID_ = (multiplicativeID_ + partials_[i]) % productOfAllCharacteristics_; + // } + } + /** + * @brief Returns the current characteristics as the product of all of them. + * + * @return The value of the current characteristic. + */ + const characteristic_type& get_characteristic() const { return productOfAllCharacteristics_; } + + /** + * @brief Returns the value of an element in the field. + * That is the positive value of the integer modulo the current characteristic. + * + * @param e Element to return the value from. + * @return @p e modulo the current characteristic, such that the result is positive. + */ + element_type get_value(element_type e) const { + get_value_inplace(e); + return e; + } + + /** + * @brief Stores in the given element the value of this element in the field. + * That is the positive value of the integer modulo the current characteristic. + * + * @param e Element to return the value from. + */ + void get_value_inplace(element_type& e) const { + if (e >= productOfAllCharacteristics_ || e < -productOfAllCharacteristics_) + mpz_mod(e.get_mpz_t(), e.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); + if (e < 0) e += productOfAllCharacteristics_; + } + + /** + * @brief Returns the sum of two elements in the field. + * + * @param e1 First element. + * @param e2 Second element. + * @return `(e1 + e2) % productOfAllCharacteristics`, such that the result is positive. + */ + element_type add(element_type e1, const element_type& e2) const { + add_inplace(e1, e2); + return e1; + } + + /** + * @brief Stores in the first element the sum of two given elements in the field, that is + * `(e1 + e2) % productOfAllCharacteristics`, such that the result is positive. + * + * @param e1 First element. + * @param e2 Second element. + */ + void add_inplace(element_type& e1, const element_type& e2) const { + e1 += e2; + get_value_inplace(e1); + } + + /** + * @brief Returns the substraction in the field of the first element by the second element. + * + * @param e1 First element. + * @param e2 Second element. + * @return `(e1 - e2) % productOfAllCharacteristics`, such that the result is positive. + */ + element_type substract(element_type e1, const element_type& e2) const { + substract_inplace_front(e1, e2); + return e1; + } + + /** + * @brief Stores in the first element the substraction in the field of the first element by the second element, + * that is `(e1 - e2) % productOfAllCharacteristics`, such that the result is positive. + * + * @param e1 First element. + * @param e2 Second element. + */ + void substract_inplace_front(element_type& e1, const element_type& e2) const { + e1 -= e2; + get_value_inplace(e1); + } + /** + * @brief Stores in the second element the substraction in the field of the first element by the second element, + * that is `(e1 - e2) % productOfAllCharacteristics`, such that the result is positive. + * + * @param e1 First element. + * @param e2 Second element. + */ + void substract_inplace_back(const element_type& e1, element_type& e2) const { + mpz_sub(e2.get_mpz_t(), e1.get_mpz_t(), e2.get_mpz_t()); + get_value_inplace(e2); + } + + /** + * @brief Returns the multiplication of two elements in the field. + * + * @param e1 First element. + * @param e2 Second element. + * @return `(e1 * e2) % productOfAllCharacteristics`, such that the result is positive. + */ + element_type multiply(element_type e1, const element_type& e2) const { + multiply_inplace(e1, e2); + return e1; + } + + /** + * @brief Stores in the first element the multiplication of two given elements in the field, + * that is `(e1 * e2) % productOfAllCharacteristics`, such that the result is positive. + * + * @param e1 First element. + * @param e2 Second element. + */ + void multiply_inplace(element_type& e1, const element_type& e2) const { + e1 *= e2; + get_value_inplace(e1); + } + + /** + * @brief Multiplies the first element with the second one and adds the third one. Returns the result in the field. + * + * @param e First element. + * @param m Second element. + * @param a Third element. + * @return `(e * m + a) % productOfAllCharacteristics`, such that the result is positive. + */ + element_type multiply_and_add(element_type e, const element_type& m, const element_type& a) const { + multiply_and_add_inplace_front(e, m, a); + return e; + } + + /** + * @brief Multiplies the first element with the second one and adds the third one, that is + * `(e * m + a) % productOfAllCharacteristics`, such that the result is positive. + * Stores the result in the first element. + * + * @param e First element. + * @param m Second element. + * @param a Third element. + */ + void multiply_and_add_inplace_front(element_type& e, const element_type& m, const element_type& a) const { + e *= m; + e += a; + get_value_inplace(e); + } + /** + * @brief Multiplies the first element with the second one and adds the third one, that is + * `(e * m + a) % productOfAllCharacteristics`, such that the result is positive. + * Stores the result in the third element. + * + * @param e First element. + * @param m Second element. + * @param a Third element. + */ + void multiply_and_add_inplace_back(const element_type& e, const element_type& m, element_type& a) const { + a += e * m; + get_value_inplace(a); + } + + /** + * @brief Adds the first element to the second one and multiplies the third one with it. + * Returns the result in the field. + * + * @param e First element. + * @param a Second element. + * @param m Third element. + * @return `((e + a) * m) % productOfAllCharacteristics`, such that the result is positive. + */ + element_type add_and_multiply(element_type e, const element_type& a, const element_type& m) const { + add_and_multiply_inplace_front(e, a, m); + return e; + } + + /** + * @brief Adds the first element to the second one and multiplies the third one with it, that is + * `((e + a) * m) % productOfAllCharacteristics`, such that the result is positive. + * Stores the result in the first element. + * + * @param e First element. + * @param a Second element. + * @param m Third element. + */ + void add_and_multiply_inplace_front(element_type& e, const element_type& a, const element_type& m) const { + e += a; + e *= m; + get_value_inplace(e); + } + /** + * @brief Adds the first element to the second one and multiplies the third one with it, that is + * `((e + a) * m) % productOfAllCharacteristics`, such that the result is positive. + * Stores the result in the third element. + * + * @param e First element. + * @param a Second element. + * @param m Third element. + */ + void add_and_multiply_inplace_back(const element_type& e, const element_type& a, element_type& m) const { + m *= e + a; + get_value_inplace(m); + } + + /** + * @brief Returns true if the two given elements are equal in the field, false otherwise. + * + * @param e1 First element to compare. + * @param e2 Second element to compare. + * @return true If `e1 % productOfAllCharacteristics == e2 % productOfAllCharacteristics`. + * @return false Otherwise. + */ + bool are_equal(const element_type& e1, const element_type& e2) const { + if (e1 == e2) return true; + return get_value(e1) == get_value(e2); + } + + /** + * @brief Returns the inverse of the given element in the sense of @cite boissonnat:hal-00922572 with respect + * to the product of all characteristics. + * + * @param e Element to get the inverse from. + * @return Inverse in the current field. + */ + element_type get_inverse(const element_type& e) const { + return get_partial_inverse(e, productOfAllCharacteristics_).first; + } + /** + * @brief Returns the inverse of the given element in the multi-field corresponding to the given sub-product + * of the product of all characteristics in the multi-field. See @cite boissonnat:hal-00922572 for more details. + * + * @param e Element to get the inverse from. + * @param productOfCharacteristics Product of the different characteristics to take into account in the multi-field. + * @return Pair of the inverse of @p e and the characteristic the inverse is coming from. + */ + std::pair get_partial_inverse( + const element_type& e, const characteristic_type& productOfCharacteristics) const { + characteristic_type QR; + mpz_gcd(QR.get_mpz_t(), e.get_mpz_t(), productOfCharacteristics.get_mpz_t()); // QR <- gcd(x,QS) + + if (QR == productOfCharacteristics) return {0, get_multiplicative_identity()}; // partial inverse is 0 + + characteristic_type QT = productOfCharacteristics / QR; + + characteristic_type inv_qt; + mpz_invert(inv_qt.get_mpz_t(), e.get_mpz_t(), QT.get_mpz_t()); + + std::pair res(get_partial_multiplicative_identity(QT), QT); + res.first *= inv_qt; + get_value_inplace(res.first); + + return res; + } + + /** + * @brief Returns the additive identity of a field. + * + * @return The additive identity of a field. + */ + static const element_type& get_additive_identity() { return additiveID_; } + /** + * @brief Returns the multiplicative identity of a field. + * + * @return The multiplicative identity of a field. + */ + static const element_type& get_multiplicative_identity() { return multiplicativeID_; } + + /** + * @brief Returns the partial multiplicative identity of the multi-field from the given product. + * See @cite boissonnat:hal-00922572 for more details. + * + * @param productOfCharacteristics Product of the different characteristics to take into account in the multi-field. + * @return The partial multiplicative identity of the multi-field. + */ + element_type get_partial_multiplicative_identity(const characteristic_type& productOfCharacteristics) const { + if (productOfCharacteristics == 0) { + return get_multiplicative_identity(); + } + element_type multIdentity(0); + for (unsigned int idx = 0; idx < primes_.size(); ++idx) { + if ((productOfCharacteristics % primes_[idx]) == 0) { + multIdentity += partials_[idx]; + } + } + get_value_inplace(multIdentity); + return multIdentity; + } + + // static constexpr bool handles_only_z2() { return false; } + + /** + * @brief Assign operator. + */ + Multi_field_operators& operator=(Multi_field_operators other) { + primes_.swap(other.primes_); + productOfAllCharacteristics_ = other.productOfAllCharacteristics_; + partials_.swap(other.partials_); + + return *this; + } + /** + * @brief Swap operator. + */ + friend void swap(Multi_field_operators& f1, Multi_field_operators& f2) { + f1.primes_.swap(f2.primes_); + std::swap(f1.productOfAllCharacteristics_, f2.productOfAllCharacteristics_); + f1.partials_.swap(f2.partials_); + } + + private: + std::vector primes_; /**< All characteristics. */ + characteristic_type productOfAllCharacteristics_; /**< Product of all characteristics. */ + std::vector partials_; /**< Partial products of the characteristics. */ + inline static const element_type multiplicativeID_ = 1; + inline static const element_type additiveID_ = 0; + + static constexpr bool _is_prime(const int p) { + if (p <= 1) return false; + if (p <= 3) return true; + if (p % 2 == 0 || p % 3 == 0) return false; + + for (long i = 5; i * i <= p; i = i + 6) + if (p % i == 0 || p % (i + 2) == 0) return false; + + return true; + } +}; + +} // namespace persistence_fields +} // namespace Gudhi + +#endif // MATRIX_FIELD_MULTI_OPERATORS_H_ diff --git a/src/Persistence_matrix/include/gudhi/Fields/Multi_field_shared.h b/src/Persistence_matrix/include/gudhi/Fields/Multi_field_shared.h new file mode 100644 index 0000000000..233fdebb46 --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Fields/Multi_field_shared.h @@ -0,0 +1,450 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber, Clément Maria + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file Multi_field_shared.h + * @author Hannah Schreiber, Clément Maria + * @brief Contains the @ref Shared_multi_field_element class. + */ + +#ifndef MATRIX_FIELD_MULTI_SHARED_H_ +#define MATRIX_FIELD_MULTI_SHARED_H_ + +#include +#include +#include +#include + +namespace Gudhi { +namespace persistence_fields { + +/** + * @class Shared_multi_field_element Multi_field_shared.h gudhi/Fields/Multi_field_shared.h + * @ingroup persistence_fields + * + * @brief Class representing an element of a multi-field. If each instanciation of the class can represent another + * element, they all share the same characteritics. That is if the characteristics are set for one, they will be + * set for all the others. The characteristics can be set before instianciating the elements with the static + * @ref Shared_multi_field_element::initialize method. + */ +class Shared_multi_field_element +{ + public: + using element_type = mpz_class; /**< Type for the elements in the field. */ + using characteristic_type = element_type; /**< Type for the field characteristic. */ + + /** + * @brief Default constructor. Sets the element to 0. + */ + Shared_multi_field_element(); + /** + * @brief Constructor setting the element to the given value. + * + * @param element Value of the element. + */ + Shared_multi_field_element(element_type element); + /** + * @brief Copy constructor. + * + * @param toCopy Element to copy. + */ + Shared_multi_field_element(const Shared_multi_field_element& toCopy); + /** + * @brief Move constructor. + * + * @param toMove Element to move. + */ + Shared_multi_field_element(Shared_multi_field_element&& toMove) noexcept; + + /** + * @brief Initialize the multi-field to the characteristics (primes) contained in the given interval. + * Should be called first before constructing the field elements. + * + * @param minimum Lowest value in the interval. + * @param maximum Highest value in the interval. + */ + static void initialize(unsigned int minimum, unsigned int maximum); + + /** + * @brief operator+= + */ + friend void operator+=(Shared_multi_field_element& f1, Shared_multi_field_element const& f2) { + f1.element_ += f2.element_; + mpz_mod(f1.element_.get_mpz_t(), f1.element_.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); + } + /** + * @brief operator+ + */ + friend Shared_multi_field_element operator+(Shared_multi_field_element f1, Shared_multi_field_element const& f2) { + f1 += f2; + return f1; + } + /** + * @brief operator+= + */ + friend void operator+=(Shared_multi_field_element& f, element_type const v) { + f.element_ += v; + mpz_mod(f.element_.get_mpz_t(), f.element_.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); + } + /** + * @brief operator+ + */ + friend Shared_multi_field_element operator+(Shared_multi_field_element f, element_type const v) { + f += v; + return f; + } + /** + * @brief operator+ + */ + friend element_type operator+(element_type v, Shared_multi_field_element const& f) { + v += f.element_; + mpz_mod(v.get_mpz_t(), v.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); + return v; + } + + /** + * @brief operator-= + */ + friend void operator-=(Shared_multi_field_element& f1, Shared_multi_field_element const& f2) { + f1.element_ -= f2.element_; + mpz_mod(f1.element_.get_mpz_t(), f1.element_.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); + } + /** + * @brief operator- + */ + friend Shared_multi_field_element operator-(Shared_multi_field_element f1, Shared_multi_field_element const& f2) { + f1 -= f2; + return f1; + } + /** + * @brief operator-= + */ + friend void operator-=(Shared_multi_field_element& f, element_type const v) { + f.element_ -= v; + mpz_mod(f.element_.get_mpz_t(), f.element_.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); + } + /** + * @brief operator- + */ + friend Shared_multi_field_element operator-(Shared_multi_field_element f, element_type const v) { + f -= v; + return f; + } + /** + * @brief operator- + */ + friend element_type operator-(element_type v, Shared_multi_field_element const& f) { + if (v >= productOfAllCharacteristics_) + mpz_mod(v.get_mpz_t(), v.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); + if (f.element_ > v) v += productOfAllCharacteristics_; + v -= f.element_; + return v; + } + + /** + * @brief operator*= + */ + friend void operator*=(Shared_multi_field_element& f1, Shared_multi_field_element const& f2) { + f1.element_ *= f2.element_; + mpz_mod(f1.element_.get_mpz_t(), f1.element_.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); + } + /** + * @brief operator* + */ + friend Shared_multi_field_element operator*(Shared_multi_field_element f1, Shared_multi_field_element const& f2) { + f1 *= f2; + return f1; + } + /** + * @brief operator*= + */ + friend void operator*=(Shared_multi_field_element& f, element_type const v) { + f.element_ *= v; + mpz_mod(f.element_.get_mpz_t(), f.element_.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); + } + /** + * @brief operator* + */ + friend Shared_multi_field_element operator*(Shared_multi_field_element f, element_type const v) { + f *= v; + return f; + } + /** + * @brief operator* + */ + friend element_type operator*(element_type v, Shared_multi_field_element const& f) { + v *= f.element_; + mpz_mod(v.get_mpz_t(), v.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); + return v; + } + + /** + * @brief operator== + */ + friend bool operator==(const Shared_multi_field_element& f1, const Shared_multi_field_element& f2) { + return f1.element_ == f2.element_; + } + /** + * @brief operator== + */ + friend bool operator==(const element_type& v, const Shared_multi_field_element& f) { + if (v < productOfAllCharacteristics_) return v == f.element_; + element_type e(v); + mpz_mod(e.get_mpz_t(), e.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); + return e == f.element_; + } + /** + * @brief operator== + */ + friend bool operator==(const Shared_multi_field_element& f, const element_type& v) { + if (v < productOfAllCharacteristics_) return v == f.element_; + element_type e(v); + mpz_mod(e.get_mpz_t(), e.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); + return e == f.element_; + } + /** + * @brief operator!= + */ + friend bool operator!=(const Shared_multi_field_element& f1, const Shared_multi_field_element& f2) { + return !(f1 == f2); + } + /** + * @brief operator!= + */ + friend bool operator!=(const element_type& v, const Shared_multi_field_element& f) { return !(v == f); } + /** + * @brief operator!= + */ + friend bool operator!=(const Shared_multi_field_element& f, const element_type& v) { return !(v == f); } + + /** + * @brief Assign operator. + */ + Shared_multi_field_element& operator=(Shared_multi_field_element other); + /** + * @brief Assign operator. + */ + Shared_multi_field_element& operator=(const element_type& value); + /** + * @brief Swap operator. + */ + friend void swap(Shared_multi_field_element& f1, Shared_multi_field_element& f2) { + std::swap(f1.element_, f2.element_); + } + + /** + * @brief Casts the element into an unsigned int. + */ + operator unsigned int() const; + /** + * @brief Casts the element into a mpz_class. + */ + operator mpz_class() const; + + /** + * @brief Returns the inverse of the element in the multi-field, see @cite boissonnat:hal-00922572. + * + * @return The inverse. + */ + Shared_multi_field_element get_inverse() const; + /** + * @brief Returns the inverse of the element with respect to a sub-product of the characteristics in the multi-field, + * see @cite boissonnat:hal-00922572. + * + * @param productOfCharacteristics Sub-product of the characteristics. + * @return Pair of the inverse and the characteristic the inverse corresponds to. + */ + std::pair get_partial_inverse( + const characteristic_type& productOfCharacteristics) const; + + /** + * @brief Returns the additive identity of a field. + * + * @return The additive identity of a field. + */ + static Shared_multi_field_element get_additive_identity(); + /** + * @brief Returns the multiplicative identity of a field. + * + * @return The multiplicative identity of a field. + */ + static Shared_multi_field_element get_multiplicative_identity(); + /** + * @brief Returns the partial multiplicative identity of the multi-field from the given product. + * See @cite boissonnat:hal-00922572 for more details. + * + * @param productOfCharacteristics Product of the different characteristics to take into account in the multi-field. + * @return The partial multiplicative identity of the multi-field. + */ + static Shared_multi_field_element get_partial_multiplicative_identity( + const characteristic_type& productOfCharacteristics); + /** + * @brief Returns the product of all characteristics. + * + * @return The product of all characteristics. + */ + static characteristic_type get_characteristic(); + + /** + * @brief Returns the value of the element. + * + * @return Value of the element. + */ + element_type get_value() const; + + // static constexpr bool handles_only_z2() { return false; } + + private: + element_type element_; /**< Element. */ + static inline std::vector primes_; /**< All characteristics. */ + static inline characteristic_type productOfAllCharacteristics_ = 0; /**< Product of all characteristics. */ + static inline std::vector partials_; /**< Partial products of the characteristics. */ + static inline const element_type multiplicativeID_ = 1; /**< Multiplicative identity. */ + + static constexpr bool _is_prime(const int p); +}; + +inline Shared_multi_field_element::Shared_multi_field_element() : element_(0) {} + +inline Shared_multi_field_element::Shared_multi_field_element(element_type element) : element_(element) { + mpz_mod(element_.get_mpz_t(), element_.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); +} + +inline Shared_multi_field_element::Shared_multi_field_element(const Shared_multi_field_element& toCopy) + : element_(toCopy.element_) {} + +inline Shared_multi_field_element::Shared_multi_field_element(Shared_multi_field_element&& toMove) noexcept + : element_(std::move(toMove.element_)) {} + +inline void Shared_multi_field_element::initialize(unsigned int minimum, unsigned int maximum) { + if (maximum < 2) throw std::invalid_argument("Characteristic must be strictly positive"); + if (minimum > maximum) throw std::invalid_argument("The given interval is not valid."); + if (minimum == maximum && !_is_prime(minimum)) + throw std::invalid_argument("The given interval does not contain a prime number."); + + unsigned int curr_prime = minimum; + mpz_t tmp_prime; + mpz_init_set_ui(tmp_prime, minimum); + // test if min_prime is prime + int is_prime = mpz_probab_prime_p(tmp_prime, 25); // probabilistic primality test + + if (is_prime == 0) { // min_prime is composite + mpz_nextprime(tmp_prime, tmp_prime); + curr_prime = mpz_get_ui(tmp_prime); + } + + primes_.clear(); + while (curr_prime <= maximum) { + primes_.push_back(curr_prime); + mpz_nextprime(tmp_prime, tmp_prime); + curr_prime = mpz_get_ui(tmp_prime); + } + mpz_clear(tmp_prime); + + if (primes_.empty()) throw std::invalid_argument("The given interval does not contain a prime number."); + + productOfAllCharacteristics_ = 1; + for (const unsigned int p : primes_) { + productOfAllCharacteristics_ *= p; + } + + partials_.resize(primes_.size()); + for (unsigned int i = 0; i < primes_.size(); ++i) { + unsigned int p = primes_[i]; + partials_[i] = productOfAllCharacteristics_ / p; + mpz_powm_ui(partials_[i].get_mpz_t(), partials_[i].get_mpz_t(), p - 1, productOfAllCharacteristics_.get_mpz_t()); + } + + // If I understood the paper well, multiplicativeID_ always equals to 1. But in Clement's code, + // multiplicativeID_ is computed (see commented loop below). TODO: verify with Clement. + // for (unsigned int i = 0; i < partials_.size(); ++i){ + // multiplicativeID_ = (multiplicativeID_ + partials_[i]) % productOfAllCharacteristics_; + // } +} + +inline Shared_multi_field_element& Shared_multi_field_element::operator=(Shared_multi_field_element other) { + std::swap(element_, other.element_); + return *this; +} + +inline Shared_multi_field_element& Shared_multi_field_element::operator=(const element_type& value) { + mpz_mod(element_.get_mpz_t(), value.get_mpz_t(), productOfAllCharacteristics_.get_mpz_t()); + return *this; +} + +inline Shared_multi_field_element::operator unsigned int() const { return element_.get_ui(); } + +inline Shared_multi_field_element::operator mpz_class() const { return element_; } + +inline Shared_multi_field_element Shared_multi_field_element::get_inverse() const { + return get_partial_inverse(productOfAllCharacteristics_).first; +} + +inline std::pair +Shared_multi_field_element::get_partial_inverse(const characteristic_type& productOfCharacteristics) const { + element_type QR; + mpz_gcd(QR.get_mpz_t(), element_.get_mpz_t(), productOfCharacteristics.get_mpz_t()); // QR <- gcd(x,QS) + + if (QR == productOfCharacteristics) return {Shared_multi_field_element(), multiplicativeID_}; // partial inverse is 0 + + element_type QT = productOfCharacteristics / QR; + + element_type inv_qt; + mpz_invert(inv_qt.get_mpz_t(), element_.get_mpz_t(), QT.get_mpz_t()); + + auto res = get_partial_multiplicative_identity(QT); + res *= inv_qt; + + return {res, QT}; +} + +inline Shared_multi_field_element Shared_multi_field_element::get_additive_identity() { + return Shared_multi_field_element(); +} + +inline Shared_multi_field_element Shared_multi_field_element::get_multiplicative_identity() { + return Shared_multi_field_element(multiplicativeID_); +} + +inline Shared_multi_field_element Shared_multi_field_element::get_partial_multiplicative_identity( + const characteristic_type& productOfCharacteristics) { + if (productOfCharacteristics == 0) { + return Shared_multi_field_element(multiplicativeID_); + } + Shared_multi_field_element mult; + for (unsigned int idx = 0; idx < primes_.size(); ++idx) { + if ((productOfCharacteristics % primes_[idx]) == 0) { + mult += partials_[idx]; + } + } + return mult; +} + +inline Shared_multi_field_element::characteristic_type Shared_multi_field_element::get_characteristic() { + return productOfAllCharacteristics_; +} + +inline Shared_multi_field_element::element_type Shared_multi_field_element::get_value() const { return element_; } + +inline constexpr bool Shared_multi_field_element::_is_prime(const int p) { + if (p <= 1) return false; + if (p <= 3) return true; + if (p % 2 == 0 || p % 3 == 0) return false; + + for (long i = 5; i * i <= p; i = i + 6) + if (p % i == 0 || p % (i + 2) == 0) return false; + + return true; +} + +} // namespace persistence_fields +} // namespace Gudhi + +#endif // MATRIX_FIELD_MULTI_SHARED_H_ diff --git a/src/Persistence_matrix/include/gudhi/Fields/Multi_field_small.h b/src/Persistence_matrix/include/gudhi/Fields/Multi_field_small.h new file mode 100644 index 0000000000..f10a8c7327 --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Fields/Multi_field_small.h @@ -0,0 +1,531 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber, Clément Maria + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file Multi_field_small.h + * @author Hannah Schreiber, Clément Maria + * @brief Contains the @ref Multi_field_element_with_small_characteristics class. + */ + +#ifndef MATRIX_FIELD_MULTI_SMALL_H_ +#define MATRIX_FIELD_MULTI_SMALL_H_ + +#include +#include +#include +#include + +namespace Gudhi { +namespace persistence_fields { + +/** + * @class Multi_field_element_with_small_characteristics Multi_field_small.h gudhi/Fields/Multi_field_small.h + * @ingroup persistence_fields + * + * @brief Class representing an element of a multi-field, such that the product of all characteristics fits into + * the given @p Unsigned_integer_type template argument. The characteristics will corresponds to all prime numbers + * in the interval given as other template arguments. + * + * @tparam minimum Interval closed lower bound. + * @tparam maximum Interval closed upper bound. + * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, long unsigned int, etc. + * Will be used as the field element type. + */ +template > > +class Multi_field_element_with_small_characteristics { + public: + using element_type = Unsigned_integer_type; /**< Type for the elements in the field. */ + using characteristic_type = element_type; /**< Type for the field characteristic. */ + template + using isInteger = std::enable_if_t >; + + /** + * @brief Default constructor. Sets the element to 0. + */ + Multi_field_element_with_small_characteristics() : element_(0) { + static_assert(maximum >= 2, "Characteristics have to be positive."); + static_assert(minimum <= maximum, "The given interval is not valid."); + static_assert(minimum != maximum || _is_prime(minimum), "The given interval does not contain a prime number."); + static_assert(productOfAllCharacteristics_ != 1, "The given interval does not contain a prime number."); + } + /** + * @brief Constructor setting the element to the given value. + * + * @param element Value of the element. + */ + template > + Multi_field_element_with_small_characteristics(Integer_type element) + : element_(_get_value(element)) { + static_assert(maximum >= 2, "Characteristics has to be positive."); + static_assert(minimum <= maximum, "The given interval is not valid."); + static_assert(minimum != maximum || _is_prime(minimum), "The given interval does not contain a prime number."); + static_assert(productOfAllCharacteristics_ != 1, "The given interval does not contain a prime number."); + } + /** + * @brief Copy constructor. + * + * @param toCopy Element to copy. + */ + Multi_field_element_with_small_characteristics(const Multi_field_element_with_small_characteristics& toCopy) + : element_(toCopy.element_) {} + /** + * @brief Move constructor. + * + * @param toMove Element to move. + */ + Multi_field_element_with_small_characteristics(Multi_field_element_with_small_characteristics&& toMove) noexcept + : element_(std::exchange(toMove.element_, 0)) {} + + /** + * @brief operator+= + */ + friend void operator+=(Multi_field_element_with_small_characteristics& f1, + Multi_field_element_with_small_characteristics const& f2) { + f1.element_ = _add(f1.element_, f2.element_); + } + /** + * @brief operator+ + */ + friend Multi_field_element_with_small_characteristics operator+( + Multi_field_element_with_small_characteristics f1, Multi_field_element_with_small_characteristics const& f2) { + f1 += f2; + return f1; + } + /** + * @brief operator+= + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend void operator+=(Multi_field_element_with_small_characteristics& f, const Integer_type& v) { + f.element_ = _add(f.element_, _get_value(v)); + } + /** + * @brief operator+ + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend Multi_field_element_with_small_characteristics operator+(Multi_field_element_with_small_characteristics f, + const Integer_type& v) { + f += v; + return f; + } + /** + * @brief operator+ + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend Integer_type operator+(const Integer_type& v, Multi_field_element_with_small_characteristics f) { + f += v; + return f.element_; + } + + /** + * @brief operator-= + */ + friend void operator-=(Multi_field_element_with_small_characteristics& f1, + Multi_field_element_with_small_characteristics const& f2) { + f1.element_ = _substract(f1.element_, f2.element_); + } + /** + * @brief operator- + */ + friend Multi_field_element_with_small_characteristics operator-( + Multi_field_element_with_small_characteristics f1, Multi_field_element_with_small_characteristics const& f2) { + f1 -= f2; + return f1; + } + /** + * @brief operator-= + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend void operator-=(Multi_field_element_with_small_characteristics& f, const Integer_type& v) { + f.element_ = _substract(f.element_, _get_value(v)); + } + /** + * @brief operator- + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend Multi_field_element_with_small_characteristics operator-(Multi_field_element_with_small_characteristics f, + const Integer_type& v) { + f -= v; + return f; + } + /** + * @brief operator- + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend Integer_type operator-(const Integer_type& v, const Multi_field_element_with_small_characteristics& f) { + return _substract(_get_value(v), f.element_); + } + + /** + * @brief operator*= + */ + friend void operator*=(Multi_field_element_with_small_characteristics& f1, + Multi_field_element_with_small_characteristics const& f2) { + f1.element_ = _multiply(f1.element_, f2.element_); + } + /** + * @brief operator* + */ + friend Multi_field_element_with_small_characteristics operator*( + Multi_field_element_with_small_characteristics f1, Multi_field_element_with_small_characteristics const& f2) { + f1 *= f2; + return f1; + } + /** + * @brief operator*= + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend void operator*=(Multi_field_element_with_small_characteristics& f, const Integer_type& v) { + f.element_ = _multiply(f.element_, _get_value(v)); + } + /** + * @brief operator* + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend Multi_field_element_with_small_characteristics operator*(Multi_field_element_with_small_characteristics f, + const Integer_type& v) { + f *= v; + return f; + } + /** + * @brief operator* + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend Integer_type operator*(const Integer_type& v, Multi_field_element_with_small_characteristics f) { + f *= v; + return f.element_; + } + + /** + * @brief operator== + */ + friend bool operator==(const Multi_field_element_with_small_characteristics& f1, + const Multi_field_element_with_small_characteristics& f2) { + return f1.element_ == f2.element_; + } + /** + * @brief operator== + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend bool operator==(const Integer_type v, const Multi_field_element_with_small_characteristics& f) { + return _get_value(v) == f.element_; + } + /** + * @brief operator== + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend bool operator==(const Multi_field_element_with_small_characteristics& f, const Integer_type v) { + return _get_value(v) == f.element_; + } + /** + * @brief operator!= + */ + friend bool operator!=(const Multi_field_element_with_small_characteristics& f1, + const Multi_field_element_with_small_characteristics& f2) { + return !(f1 == f2); + } + /** + * @brief operator!= + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend bool operator!=(const Integer_type v, const Multi_field_element_with_small_characteristics& f) { + return !(v == f); + } + /** + * @brief operator!= + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend bool operator!=(const Multi_field_element_with_small_characteristics& f, const Integer_type v) { + return !(v == f); + } + + /** + * @brief Assign operator. + */ + Multi_field_element_with_small_characteristics& operator=(Multi_field_element_with_small_characteristics other) { + std::swap(element_, other.element_); + return *this; + } + /** + * @brief Assign operator. + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + Multi_field_element_with_small_characteristics& operator=(const Integer_type& value) { + element_ = _get_value(value); + return *this; + } + /** + * @brief Swap operator. + */ + friend void swap(Multi_field_element_with_small_characteristics& f1, + Multi_field_element_with_small_characteristics& f2) { + std::swap(f1.element_, f2.element_); + } + + /** + * @brief Casts the element into an unsigned int. + */ + operator unsigned int() const { return element_; } + + /** + * @brief Returns the inverse of the element in the multi-field, see @cite boissonnat:hal-00922572. + * + * @return The inverse. + */ + Multi_field_element_with_small_characteristics get_inverse() const { + return get_partial_inverse(productOfAllCharacteristics_).first; + } + /** + * @brief Returns the inverse of the element with respect to a sub-product of the characteristics in the multi-field, + * see @cite boissonnat:hal-00922572. + * + * @param productOfCharacteristics Sub-product of the characteristics. + * @return Pair of the inverse and the characteristic the inverse corresponds to. + */ + std::pair get_partial_inverse( + characteristic_type productOfCharacteristics) const { + characteristic_type gcd = std::gcd(element_, productOfAllCharacteristics_); + + if (gcd == productOfCharacteristics) + return {Multi_field_element_with_small_characteristics(), multiplicativeID_}; // partial inverse is 0 + + characteristic_type QT = productOfCharacteristics / gcd; + + const element_type inv_qt = _get_inverse(element_, QT); + + auto res = get_partial_multiplicative_identity(QT); + res *= inv_qt; + + return {res, QT}; + } + + /** + * @brief Returns the additive identity of a field. + * + * @return The additive identity of a field. + */ + static Multi_field_element_with_small_characteristics get_additive_identity() { + return Multi_field_element_with_small_characteristics(); + } + /** + * @brief Returns the multiplicative identity of a field. + * + * @return The multiplicative identity of a field. + */ + static Multi_field_element_with_small_characteristics get_multiplicative_identity() { + return Multi_field_element_with_small_characteristics(multiplicativeID_); + } + /** + * @brief Returns the partial multiplicative identity of the multi-field from the given product. + * See @cite boissonnat:hal-00922572 for more details. + * + * @param productOfCharacteristics Product of the different characteristics to take into account in the multi-field. + * @return The partial multiplicative identity of the multi-field. + */ + static Multi_field_element_with_small_characteristics get_partial_multiplicative_identity( + const characteristic_type& productOfCharacteristics) { + if (productOfCharacteristics == 0) { + return Multi_field_element_with_small_characteristics(multiplicativeID_); + } + Multi_field_element_with_small_characteristics mult; + for (characteristic_type idx = 0; idx < primes_.size(); ++idx) { + if ((productOfCharacteristics % primes_[idx]) == 0) { + mult += partials_[idx]; + } + } + return mult; + } + /** + * @brief Returns the product of all characteristics. + * + * @return The product of all characteristics. + */ + static constexpr characteristic_type get_characteristic() { return productOfAllCharacteristics_; } + + /** + * @brief Returns the value of the element. + * + * @return Value of the element. + */ + element_type get_value() const { return element_; } + + // static constexpr bool handles_only_z2() { return false; } + + private: + static constexpr bool _is_prime(const unsigned int p) { + if (p <= 1) return false; + if (p <= 3) return true; + if (p % 2 == 0 || p % 3 == 0) return false; + + for (unsigned long i = 5; i * i <= p; i = i + 6) + if (p % i == 0 || p % (i + 2) == 0) return false; + + return true; + } + static constexpr element_type _multiply(element_type a, element_type b) { + element_type res = 0; + element_type temp_b = 0; + + if (b < a) std::swap(a, b); + + while (a != 0) { + if (a & 1) { + /* Add b to res, modulo m, without overflow */ + if (b >= productOfAllCharacteristics_ - res) res -= productOfAllCharacteristics_; + res += b; + } + a >>= 1; + + /* Double b, modulo m */ + temp_b = b; + if (b >= productOfAllCharacteristics_ - b) temp_b -= productOfAllCharacteristics_; + b += temp_b; + } + return res; + } + static constexpr element_type _add(element_type element, element_type v) { + if (UINT_MAX - element < v) { + // automatic unsigned integer overflow behaviour will make it work + element += v; + element -= productOfAllCharacteristics_; + return element; + } + + element += v; + if (element >= productOfAllCharacteristics_) element -= productOfAllCharacteristics_; + + return element; + } + static constexpr element_type _substract(element_type element, element_type v) { + if (element < v) { + element += productOfAllCharacteristics_; + } + element -= v; + + return element; + } + static constexpr int _get_inverse(element_type element, const element_type mod) { + // to solve: Ax + My = 1 + int M = mod; + int A = element; + int y = 0, x = 1; + // extended euclidien division + while (A > 1) { + int quotient = A / M; + int temp = M; + + M = A % M, A = temp; + temp = y; + + y = x - quotient * y; + x = temp; + } + + if (x < 0) x += mod; + + return x; + } + + template > + static constexpr element_type _get_value(Integer_type e) { + if constexpr (std::is_signed_v){ + if (e < -static_cast(productOfAllCharacteristics_)) e = e % productOfAllCharacteristics_; + if (e < 0) return e += productOfAllCharacteristics_; + return e < static_cast(productOfAllCharacteristics_) ? e : e % productOfAllCharacteristics_; + } else { + return e < productOfAllCharacteristics_ ? e : e % productOfAllCharacteristics_; + } + } + + element_type element_; + static inline const std::vector primes_ = []() { + std::vector res; + for (characteristic_type i = minimum; i <= maximum; ++i) { + if (_is_prime(i)) { + res.push_back(i); + } + } + return res; + }(); + static inline constexpr characteristic_type productOfAllCharacteristics_ = []() { + characteristic_type res = 1; + for (characteristic_type i = minimum; i <= maximum; ++i) { + if (_is_prime(i)) { + res *= i; + } + } + return res; + }(); + static inline const std::vector partials_ = []() { + std::vector res; + + if (productOfAllCharacteristics_ == 1) return res; + + for (characteristic_type i = 0; i < primes_.size(); ++i) { + characteristic_type p = primes_[i]; + characteristic_type base = productOfAllCharacteristics_ / p; + characteristic_type exp = p - 1; + res.push_back(1); + + while (exp > 0) { + // If exp is odd, multiply with result + if (exp & 1) res.back() = _multiply(res.back(), base); + // y must be even now + exp = exp >> 1; + base = _multiply(base, base); + } + } + + return res; + }(); + // If I understood the paper well, multiplicativeID_ always equals to 1. But in Clement's code, + // multiplicativeID_ is computed (see commented lambda function below). TODO: verify with Clement. + static inline constexpr element_type multiplicativeID_ = 1; /*= [](){ + unsigned int res = 0; + for (unsigned int i = 0; i < partials_.size(); ++i){ + res = (res + partials_[i]) % productOfAllCharacteristics_; + } + + return res; + }();*/ +}; + +} // namespace persistence_fields +} // namespace Gudhi + +#endif // MATRIX_FIELD_MULTI_SMALL_H_ diff --git a/src/Persistence_matrix/include/gudhi/Fields/Multi_field_small_operators.h b/src/Persistence_matrix/include/gudhi/Fields/Multi_field_small_operators.h new file mode 100644 index 0000000000..f369e98528 --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Fields/Multi_field_small_operators.h @@ -0,0 +1,507 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber, Clément Maria + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file Multi_field_small_operators.h + * @author Hannah Schreiber, Clément Maria + * @brief Contains the @ref Multi_field_operators_with_small_characteristics class. + */ + +#ifndef MATRIX_FIELD_MULTI_SMALL_OPERATORS_H_ +#define MATRIX_FIELD_MULTI_SMALL_OPERATORS_H_ + +#include +#include +#include +#include +#include + +namespace Gudhi { +namespace persistence_fields { + +/** + * @class Multi_field_operators_with_small_characteristics Multi_field_small_operators.h \ + * gudhi/Fields/Multi_field_small_operators.h + * @ingroup persistence_fields + * + * @brief Class defining operators for a multi-field with "consecutive" charateristic range, such that + * `productOfAllCharacteristics ^ 2` fits into an unsigned int. + */ +class Multi_field_operators_with_small_characteristics +{ + public: + using element_type = unsigned int; /**< Type for the elements in the field. */ + using characteristic_type = element_type; /**< Type for the field characteristic. */ + + /** + * @brief Default constructor, sets the product of all characteristics to 0. + */ + Multi_field_operators_with_small_characteristics() : productOfAllCharacteristics_(0) /* , multiplicativeID_(1) */ + {} + /** + * @brief Constructor setting the characteristics to all prime numbers between the two given integers. + * The product of all primes to the square has to fit into an unsigned int. + * + * @param minCharacteristic Smallest value of a prime. + * @param maxCharacteristic Heighest value of a prime. + */ + Multi_field_operators_with_small_characteristics(int minCharacteristic, int maxCharacteristic) + : productOfAllCharacteristics_(0) //, multiplicativeID_(1) + { + set_characteristic(minCharacteristic, maxCharacteristic); + } + /** + * @brief Copy constructor. + * + * @param toCopy Operators to copy. + */ + Multi_field_operators_with_small_characteristics(const Multi_field_operators_with_small_characteristics& toCopy) + : primes_(toCopy.primes_), + productOfAllCharacteristics_(toCopy.productOfAllCharacteristics_), + partials_(toCopy.partials_) /* , + multiplicativeID_(toCopy.multiplicativeID_) */ + {} + /** + * @brief Move constructor. + * + * @param toMove Operators to move. + */ + Multi_field_operators_with_small_characteristics(Multi_field_operators_with_small_characteristics&& toMove) noexcept + : primes_(std::move(toMove.primes_)), + productOfAllCharacteristics_(std::move(toMove.productOfAllCharacteristics_)), + partials_(std::move(toMove.partials_)) /* , + multiplicativeID_(std::move(toMove.multiplicativeID_)) */ + {} + + /** + * @brief Set the characteristics of the field, which are stored in a single value as a product of all of them. + * The characteristics will be all prime numbers in the given interval. + * The product of all primes to the square has to fit into an unsigned int. + * + * @param minimum Smallest value of a prime. + * @param maximum Heighest value of a prime. + */ + void set_characteristic(int minimum, int maximum) { + if (maximum < 2) throw std::invalid_argument("Characteristic must be strictly positive"); + if (minimum > maximum) throw std::invalid_argument("The given interval is not valid."); + if (minimum == maximum && !_is_prime(minimum)) + throw std::invalid_argument("The given interval does not contain a prime number."); + + productOfAllCharacteristics_ = 1; + primes_.clear(); + for (unsigned int i = minimum; i <= static_cast(maximum); ++i) { + if (_is_prime(i)) { + primes_.push_back(i); + productOfAllCharacteristics_ *= i; + } + } + + if (primes_.empty()) throw std::invalid_argument("The given interval does not contain a prime number."); + + partials_.resize(primes_.size()); + for (unsigned int i = 0; i < primes_.size(); ++i) { + unsigned int p = primes_[i]; + characteristic_type base = productOfAllCharacteristics_ / p; + unsigned int exp = p - 1; + partials_[i] = 1; + + while (exp > 0) { + // If exp is odd, multiply with result + if (exp & 1) partials_[i] = _multiply(partials_[i], base, productOfAllCharacteristics_); + // y must be even now + exp = exp >> 1; // y = y/2 + base = _multiply(base, base, productOfAllCharacteristics_); + } + } + + // If I understood the paper well, multiplicativeID_ always equals to 1. But in Clement's code, + // multiplicativeID_ is computed (see commented loop below). TODO: verify with Clement. + // for (unsigned int i = 0; i < partials_.size(); ++i){ + // multiplicativeID_ = (multiplicativeID_ + partials_[i]) % productOfAllCharacteristics_; + // } + } + /** + * @brief Returns the current characteristics as the product of all of them. + * + * @return The value of the current characteristic. + */ + const characteristic_type& get_characteristic() const { return productOfAllCharacteristics_; } + + /** + * @brief Returns the value of an element in the field. + * That is the positive value of the integer modulo the current characteristic. + * + * @param e Integer to return the value from. + * @return @p e modulo the current characteristic, such that the result is positive. + */ + element_type get_value(element_type e) const { + return e < productOfAllCharacteristics_ ? e : e % productOfAllCharacteristics_; + } + + /** + * @brief Returns the sum of two elements in the field. + * + * @param e1 First element. + * @param e2 Second element. + * @return `(e1 + e2) % productOfAllCharacteristics`, such that the result is positive. + */ + element_type add(element_type e1, element_type e2) const { + return _add(get_value(e1), get_value(e2), productOfAllCharacteristics_); + } + + /** + * @brief Stores in the first element the sum of two given elements in the field, that is + * `(e1 + e2) % productOfAllCharacteristics`, such that the result is positive. + * + * @param e1 First element. + * @param e2 Second element. + */ + void add_inplace(element_type& e1, element_type e2) const { + e1 = _add(get_value(e1), get_value(e2), productOfAllCharacteristics_); + } + + /** + * @brief Returns the substraction in the field of the first element by the second element. + * + * @param e1 First element. + * @param e2 Second element. + * @return `(e1 - e2) % productOfAllCharacteristics`, such that the result is positive. + */ + element_type substract(element_type e1, element_type e2) const { + return _substract(get_value(e1), get_value(e2), productOfAllCharacteristics_); + } + + /** + * @brief Stores in the first element the substraction in the field of the first element by the second element, + * that is `(e1 - e2) % productOfAllCharacteristics`, such that the result is positive. + * + * @param e1 First element. + * @param e2 Second element. + */ + void substract_inplace_front(element_type& e1, element_type e2) const { + e1 = _substract(get_value(e1), get_value(e2), productOfAllCharacteristics_); + } + /** + * @brief Stores in the second element the substraction in the field of the first element by the second element, + * that is `(e1 - e2) % productOfAllCharacteristics`, such that the result is positive. + * + * @param e1 First element. + * @param e2 Second element. + */ + void substract_inplace_back(element_type e1, element_type& e2) const { + e2 = _substract(get_value(e1), get_value(e2), productOfAllCharacteristics_); + } + + /** + * @brief Returns the multiplication of two elements in the field. + * + * @param e1 First element. + * @param e2 Second element. + * @return `(e1 * e2) % productOfAllCharacteristics`, such that the result is positive. + */ + element_type multiply(element_type e1, element_type e2) const { + return _multiply(get_value(e1), get_value(e2), productOfAllCharacteristics_); + } + + /** + * @brief Stores in the first element the multiplication of two given elements in the field, + * that is `(e1 * e2) % productOfAllCharacteristics`, such that the result is positive. + * + * @param e1 First element. + * @param e2 Second element. + */ + void multiply_inplace(element_type& e1, element_type e2) const { + e1 = _multiply(get_value(e1), get_value(e2), productOfAllCharacteristics_); + } + + /** + * @brief Multiplies the first element with the second one and adds the third one. Returns the result in the field. + * + * @warning Not overflow safe. + * + * @param e First element. + * @param m Second element. + * @param a Third element. + * @return `(e * m + a) % productOfAllCharacteristics`, such that the result is positive. + */ + element_type multiply_and_add(element_type e, element_type m, element_type a) const { return get_value(e * m + a); } + + /** + * @brief Multiplies the first element with the second one and adds the third one, that is + * `(e * m + a) % productOfAllCharacteristics`, such that the result is positive. + * Stores the result in the first element. + * + * @warning Not overflow safe. + * + * @param e First element. + * @param m Second element. + * @param a Third element. + */ + void multiply_and_add_inplace_front(element_type& e, element_type m, element_type a) const { + e = get_value(e * m + a); + } + /** + * @brief Multiplies the first element with the second one and adds the third one, that is + * `(e * m + a) % productOfAllCharacteristics`, such that the result is positive. + * Stores the result in the third element. + * + * @warning Not overflow safe. + * + * @param e First element. + * @param m Second element. + * @param a Third element. + */ + void multiply_and_add_inplace_back(element_type e, element_type m, element_type& a) const { + a = get_value(e * m + a); + } + + /** + * @brief Adds the first element to the second one and multiplies the third one with it. + * Returns the result in the field. + * + * @warning Not overflow safe. + * + * @param e First element. + * @param a Second element. + * @param m Third element. + * @return `((e + a) * m) % productOfAllCharacteristics`, such that the result is positive. + */ + element_type add_and_multiply(element_type e, element_type a, element_type m) const { return get_value((e + a) * m); } + + /** + * @brief Adds the first element to the second one and multiplies the third one with it, that is + * `((e + a) * m) % productOfAllCharacteristics`, such that the result is positive. + * Stores the result in the first element. + * + * @warning Not overflow safe. + * + * @param e First element. + * @param a Second element. + * @param m Third element. + */ + void add_and_multiply_inplace_front(element_type& e, element_type a, element_type m) const { + e = get_value((e + a) * m); + } + /** + * @brief Adds the first element to the second one and multiplies the third one with it, that is + * `((e + a) * m) % productOfAllCharacteristics`, such that the result is positive. + * Stores the result in the third element. + * + * @warning Not overflow safe. + * + * @param e First element. + * @param a Second element. + * @param m Third element. + */ + void add_and_multiply_inplace_back(element_type e, element_type a, element_type& m) const { + m = get_value((e + a) * m); + } + + /** + * @brief Returns true if the two given elements are equal in the field, false otherwise. + * + * @param e1 First element to compare. + * @param e2 Second element to compare. + * @return true If `e1 % productOfAllCharacteristics == e2 % productOfAllCharacteristics`. + * @return false Otherwise. + */ + bool are_equal(element_type e1, element_type e2) const { return get_value(e1) == get_value(e2); } + + /** + * @brief Returns the inverse of the given element in the sense of @cite boissonnat:hal-00922572 with respect + * to the product of all characteristics. + * + * @param e Element to get the inverse from. + * @return Inverse in the current field. + */ + element_type get_inverse(const element_type& e) const { + return get_partial_inverse(e, productOfAllCharacteristics_).first; + } + /** + * @brief Returns the inverse of the given element in the multi-field corresponding to the given sub-product + * of the product of all characteristics in the multi-field. See @cite boissonnat:hal-00922572 for more details. + * + * @param e Element to get the inverse from. + * @param productOfCharacteristics Product of the different characteristics to take into account in the multi-field. + * @return Pair of the inverse of @p e and the characteristic the inverse is coming from. + */ + std::pair get_partial_inverse( + const element_type& e, const characteristic_type& productOfCharacteristics) const { + characteristic_type gcd = std::gcd(e, productOfAllCharacteristics_); + + if (gcd == productOfCharacteristics) return {0, get_multiplicative_identity()}; // partial inverse is 0 + + characteristic_type QT = productOfCharacteristics / gcd; + + const characteristic_type inv_qt = _get_inverse(e, QT); + + auto res = get_partial_multiplicative_identity(QT); + res = _multiply(res, inv_qt, productOfAllCharacteristics_); + + return {res, QT}; + } + + /** + * @brief Returns the additive identity of a field. + * + * @return The additive identity of a field. + */ + static constexpr element_type get_additive_identity() { return 0; } + /** + * @brief Returns the multiplicative identity of a field. + * + * @return The multiplicative identity of a field. + */ + static constexpr element_type get_multiplicative_identity() { return 1; } + // static element_type get_multiplicative_identity(){ return multiplicativeID_; } + /** + * @brief Returns the partial multiplicative identity of the multi-field from the given product. + * See @cite boissonnat:hal-00922572 for more details. + * + * @param productOfCharacteristics Product of the different characteristics to take into account in the multi-field. + * @return The partial multiplicative identity of the multi-field. + */ + element_type get_partial_multiplicative_identity(const characteristic_type& productOfCharacteristics) const { + if (productOfCharacteristics == 0) { + return get_multiplicative_identity(); + } + element_type multIdentity = 0; + for (unsigned int idx = 0; idx < primes_.size(); ++idx) { + if ((productOfCharacteristics % primes_[idx]) == 0) { + multIdentity = _add(multIdentity, partials_[idx], productOfAllCharacteristics_); + } + } + return multIdentity; + } + + // static constexpr bool handles_only_z2() { return false; } + + /** + * @brief Assign operator. + */ + Multi_field_operators_with_small_characteristics& operator=(Multi_field_operators_with_small_characteristics other) { + primes_.swap(other.primes_); + productOfAllCharacteristics_ = other.productOfAllCharacteristics_; + partials_.swap(other.partials_); + + return *this; + } + /** + * @brief Swap operator. + */ + friend void swap(Multi_field_operators_with_small_characteristics& f1, + Multi_field_operators_with_small_characteristics& f2) { + f1.primes_.swap(f2.primes_); + std::swap(f1.productOfAllCharacteristics_, f2.productOfAllCharacteristics_); + f1.partials_.swap(f2.partials_); + } + + private: + std::vector primes_; /**< All characteristics. */ + characteristic_type productOfAllCharacteristics_; /**< Product of all characteristics. */ + std::vector partials_; /**< Partial products of the characteristics. */ + // static inline constexpr unsigned int multiplicativeID_ = 1; + + static element_type _add(element_type element, element_type v, characteristic_type characteristic); + static element_type _substract(element_type element, element_type v, characteristic_type characteristic); + static element_type _multiply(element_type a, element_type b, characteristic_type characteristic); + static constexpr long int _get_inverse(element_type element, characteristic_type mod); + static constexpr bool _is_prime(const int p); +}; + +inline Multi_field_operators_with_small_characteristics::element_type +Multi_field_operators_with_small_characteristics::_add(element_type element, element_type v, + characteristic_type characteristic) { + if (UINT_MAX - element < v) { + // automatic unsigned integer overflow behaviour will make it work + element += v; + element -= characteristic; + return element; + } + + element += v; + if (element >= characteristic) element -= characteristic; + + return element; +} + +inline Multi_field_operators_with_small_characteristics::element_type +Multi_field_operators_with_small_characteristics::_substract(element_type element, element_type v, + characteristic_type characteristic) { + if (element < v) { + element += characteristic; + } + element -= v; + + return element; +} + +inline Multi_field_operators_with_small_characteristics::element_type +Multi_field_operators_with_small_characteristics::_multiply(element_type a, element_type b, + characteristic_type characteristic) { + element_type res = 0; + element_type temp_b = 0; + + if (b < a) std::swap(a, b); + + while (a != 0) { + if (a & 1) { + /* Add b to res, modulo m, without overflow */ + if (b >= characteristic - res) res -= characteristic; + res += b; + } + a >>= 1; + + /* Double b, modulo m */ + temp_b = b; + if (b >= characteristic - b) temp_b -= characteristic; + b += temp_b; + } + return res; +} + +inline constexpr long int Multi_field_operators_with_small_characteristics::_get_inverse(element_type element, + characteristic_type mod) { + // to solve: Ax + My = 1 + element_type M = mod; + element_type A = element; + long int y = 0, x = 1; + // extended euclidien division + while (A > 1) { + int quotient = A / M; + int temp = M; + + M = A % M, A = temp; + temp = y; + + y = x - quotient * y; + x = temp; + } + + if (x < 0) x += mod; + + return x; +} + +inline constexpr bool Multi_field_operators_with_small_characteristics::_is_prime(const int p) { + if (p <= 1) return false; + if (p <= 3) return true; + if (p % 2 == 0 || p % 3 == 0) return false; + + for (long i = 5; i * i <= p; i = i + 6) + if (p % i == 0 || p % (i + 2) == 0) return false; + + return true; +} + +} // namespace persistence_fields +} // namespace Gudhi + +#endif // MATRIX_FIELD_MULTI_SMALL_OPERATORS_H_ diff --git a/src/Persistence_matrix/include/gudhi/Fields/Multi_field_small_shared.h b/src/Persistence_matrix/include/gudhi/Fields/Multi_field_small_shared.h new file mode 100644 index 0000000000..3a024cad7d --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Fields/Multi_field_small_shared.h @@ -0,0 +1,531 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber, Clément Maria + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file Multi_field_small_shared.h + * @author Hannah Schreiber, Clément Maria + * @brief Contains the @ref Shared_multi_field_element_with_small_characteristics class. + */ + +#ifndef MATRIX_FIELD_MULTI_SMALL_SHARED_H_ +#define MATRIX_FIELD_MULTI_SMALL_SHARED_H_ + +#include +#include +#include +#include +#include + +namespace Gudhi { +namespace persistence_fields { + +/** + * @class Shared_multi_field_element_with_small_characteristics Multi_field_small_shared.h \ + * gudhi/Fields/Multi_field_small_shared.h + * @ingroup persistence_fields + * + * @brief Class representing an element of a multi-field, such that `productOfAllCharacteristics ^ 2` fits into + * the given @p Unsigned_integer_type template argument. If each instanciation of the class can represent another + * element, they all share the same characteritics. That is if the characteristics are set for one, they will be + * set for all the others. The characteristics can be set before instanciating the elements with the static + * @ref Shared_multi_field_element_with_small_characteristics::initialize method. + * + * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, long unsigned int, etc. + * Will be used as the field element type. + */ +template > > +class Shared_multi_field_element_with_small_characteristics { + public: + using element_type = Unsigned_integer_type; /**< Type for the elements in the field. */ + using characteristic_type = element_type; /**< Type for the field characteristic. */ + template + using isInteger = std::enable_if_t >; + + /** + * @brief Default constructor. Sets the element to 0. + */ + Shared_multi_field_element_with_small_characteristics() : element_(0) {} + /** + * @brief Constructor setting the element to the given value. + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if negative. + * @param element Value of the element. + */ + template > + Shared_multi_field_element_with_small_characteristics(Integer_type element) : element_(_get_value(element)) {} + /** + * @brief Copy constructor. + * + * @param toCopy Element to copy. + */ + Shared_multi_field_element_with_small_characteristics( + const Shared_multi_field_element_with_small_characteristics& toCopy) + : element_(toCopy.element_) {} + /** + * @brief Move constructor. + * + * @param toMove Element to move. + */ + Shared_multi_field_element_with_small_characteristics( + Shared_multi_field_element_with_small_characteristics&& toMove) noexcept + : element_(std::exchange(toMove.element_, 0)) {} + + /** + * @brief Initialize the multi-field to the characteristics (primes) contained in the given interval. + * Should be called first before constructing the field elements. + * The characteristics must be small enough such that `productOfAllCharacteristics ^ 2` fits into an unsigned int. + * + * @param minimum Lowest value in the interval. + * @param maximum Highest value in the interval. + */ + static void initialize(unsigned int minimum, unsigned int maximum) { + if (maximum < 2) throw std::invalid_argument("Characteristic must be strictly positive"); + if (minimum > maximum) throw std::invalid_argument("The given interval is not valid."); + if (minimum == maximum && !_is_prime(minimum)) + throw std::invalid_argument("The given interval does not contain a prime number."); + + productOfAllCharacteristics_ = 1; + primes_.clear(); + for (unsigned int i = minimum; i <= maximum; ++i) { + if (_is_prime(i)) { + primes_.push_back(i); + productOfAllCharacteristics_ *= i; + } + } + + if (primes_.empty()) throw std::invalid_argument("The given interval does not contain a prime number."); + + partials_.resize(primes_.size()); + for (characteristic_type i = 0; i < primes_.size(); ++i) { + characteristic_type p = primes_[i]; + characteristic_type base = productOfAllCharacteristics_ / p; + characteristic_type exp = p - 1; + partials_[i] = 1; + + while (exp > 0) { + // If exp is odd, multiply with result + if (exp & 1) partials_[i] = _multiply(partials_[i], base); + // y must be even now + exp = exp >> 1; // y = y/2 + base = _multiply(base, base); + } + } + + // If I understood the paper well, multiplicativeID_ always equals to 1. But in Clement's code, + // multiplicativeID_ is computed (see commented loop below). TODO: verify with Clement. + // for (unsigned int i = 0; i < partials_.size(); ++i){ + // multiplicativeID_ = (multiplicativeID_ + partials_[i]) % productOfAllCharacteristics_; + // } + } + + /** + * @brief operator+= + */ + friend void operator+=(Shared_multi_field_element_with_small_characteristics& f1, + Shared_multi_field_element_with_small_characteristics const& f2) { + f1.element_ = _add(f1.element_, f2.element_); + } + /** + * @brief operator+ + */ + friend Shared_multi_field_element_with_small_characteristics operator+( + Shared_multi_field_element_with_small_characteristics f1, + Shared_multi_field_element_with_small_characteristics const& f2) { + f1 += f2; + return f1; + } + /** + * @brief operator+= + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend void operator+=(Shared_multi_field_element_with_small_characteristics& f, const Integer_type& v) { + f.element_ = _add(f.element_, _get_value(v)); + } + /** + * @brief operator+ + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend Shared_multi_field_element_with_small_characteristics operator+( + Shared_multi_field_element_with_small_characteristics f, const Integer_type& v) { + f += v; + return f; + } + /** + * @brief operator+ + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend Integer_type operator+(const Integer_type& v, Shared_multi_field_element_with_small_characteristics f) { + f += v; + return f.element_; + } + + /** + * @brief operator-= + */ + friend void operator-=(Shared_multi_field_element_with_small_characteristics& f1, + Shared_multi_field_element_with_small_characteristics const& f2) { + f1.element_ = _substract(f1.element_, f2.element_); + } + /** + * @brief operator- + */ + friend Shared_multi_field_element_with_small_characteristics operator-( + Shared_multi_field_element_with_small_characteristics f1, + Shared_multi_field_element_with_small_characteristics const& f2) { + f1 -= f2; + return f1; + } + /** + * @brief operator-= + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend void operator-=(Shared_multi_field_element_with_small_characteristics& f, const Integer_type& v) { + f.element_ = _substract(f.element_, _get_value(v)); + } + /** + * @brief operator- + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend Shared_multi_field_element_with_small_characteristics operator-( + Shared_multi_field_element_with_small_characteristics f, const Integer_type& v) { + f -= v; + return f; + } + /** + * @brief operator- + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend Integer_type operator-(const Integer_type& v, const Shared_multi_field_element_with_small_characteristics& f) { + return _substract(_get_value(v), f.element_); + } + + /** + * @brief operator*= + */ + friend void operator*=(Shared_multi_field_element_with_small_characteristics& f1, + Shared_multi_field_element_with_small_characteristics const& f2) { + f1.element_ = _multiply(f1.element_, f2.element_); + } + /** + * @brief operator* + */ + friend Shared_multi_field_element_with_small_characteristics operator*( + Shared_multi_field_element_with_small_characteristics f1, + Shared_multi_field_element_with_small_characteristics const& f2) { + f1 *= f2; + return f1; + } + /** + * @brief operator*= + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend void operator*=(Shared_multi_field_element_with_small_characteristics& f, const Integer_type& v) { + f.element_ = _multiply(f.element_, _get_value(v)); + } + /** + * @brief operator* + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend Shared_multi_field_element_with_small_characteristics operator*( + Shared_multi_field_element_with_small_characteristics f, const Integer_type& v) { + f *= v; + return f; + } + /** + * @brief operator* + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend Integer_type operator*(const Integer_type& v, Shared_multi_field_element_with_small_characteristics f) { + f *= v; + return f.element_; + } + + /** + * @brief operator== + */ + friend bool operator==(const Shared_multi_field_element_with_small_characteristics& f1, + const Shared_multi_field_element_with_small_characteristics& f2) { + return f1.element_ == f2.element_; + } + /** + * @brief operator== + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend bool operator==(const Integer_type& v, const Shared_multi_field_element_with_small_characteristics& f) { + return _get_value(v) == f.element_; + } + /** + * @brief operator== + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend bool operator==(const Shared_multi_field_element_with_small_characteristics& f, const Integer_type& v) { + return _get_value(v) == f.element_; + } + /** + * @brief operator!= + */ + friend bool operator!=(const Shared_multi_field_element_with_small_characteristics& f1, + const Shared_multi_field_element_with_small_characteristics& f2) { + return !(f1 == f2); + } + /** + * @brief operator!= + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend bool operator!=(const Integer_type v, const Shared_multi_field_element_with_small_characteristics& f) { + return !(v == f); + } + /** + * @brief operator!= + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend bool operator!=(const Shared_multi_field_element_with_small_characteristics& f, const Integer_type v) { + return !(v == f); + } + + /** + * @brief Assign operator. + */ + Shared_multi_field_element_with_small_characteristics& operator=( + Shared_multi_field_element_with_small_characteristics other) { + std::swap(element_, other.element_); + return *this; + } + /** + * @brief Assign operator. + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + Shared_multi_field_element_with_small_characteristics& operator=(const Integer_type& value) { + element_ = _get_value(value); + return *this; + } + /** + * @brief Swap operator. + */ + friend void swap(Shared_multi_field_element_with_small_characteristics& f1, + Shared_multi_field_element_with_small_characteristics& f2) { + std::swap(f1.element_, f2.element_); + } + + /** + * @brief Casts the element into an unsigned int. + */ + operator unsigned int() const { return element_; } + + /** + * @brief Returns the inverse of the element in the multi-field, see @cite boissonnat:hal-00922572. + * + * @return The inverse. + */ + Shared_multi_field_element_with_small_characteristics get_inverse() const { + return get_partial_inverse(productOfAllCharacteristics_).first; + } + /** + * @brief Returns the inverse of the element with respect to a sub-product of the characteristics in the multi-field, + * see @cite boissonnat:hal-00922572. + * + * @param productOfCharacteristics Sub-product of the characteristics. + * @return Pair of the inverse and the characteristic the inverse corresponds to. + */ + std::pair get_partial_inverse( + characteristic_type productOfCharacteristics) const { + characteristic_type gcd = std::gcd(element_, productOfAllCharacteristics_); + + if (gcd == productOfCharacteristics) + return {Shared_multi_field_element_with_small_characteristics(), multiplicativeID_}; // partial inverse is 0 + + characteristic_type QT = productOfCharacteristics / gcd; + + const element_type inv_qt = _get_inverse(element_, QT); + + auto res = get_partial_multiplicative_identity(QT); + res *= inv_qt; + + return {res, QT}; + } + + /** + * @brief Returns the additive identity of a field. + * + * @return The additive identity of a field. + */ + static Shared_multi_field_element_with_small_characteristics get_additive_identity() { + return Shared_multi_field_element_with_small_characteristics(); + } + /** + * @brief Returns the multiplicative identity of a field. + * + * @return The multiplicative identity of a field. + */ + static Shared_multi_field_element_with_small_characteristics get_multiplicative_identity() { + return Shared_multi_field_element_with_small_characteristics(multiplicativeID_); + } + /** + * @brief Returns the partial multiplicative identity of the multi-field from the given product. + * See @cite boissonnat:hal-00922572 for more details. + * + * @param productOfCharacteristics Product of the different characteristics to take into account in the multi-field. + * @return The partial multiplicative identity of the multi-field. + */ + static Shared_multi_field_element_with_small_characteristics get_partial_multiplicative_identity( + const characteristic_type& productOfCharacteristics) { + if (productOfCharacteristics == 0) { + return Shared_multi_field_element_with_small_characteristics(multiplicativeID_); + } + Shared_multi_field_element_with_small_characteristics mult; + for (characteristic_type idx = 0; idx < primes_.size(); ++idx) { + if ((productOfCharacteristics % primes_[idx]) == 0) { + mult += partials_[idx]; + } + } + return mult; + } + /** + * @brief Returns the product of all characteristics. + * + * @return The product of all characteristics. + */ + static characteristic_type get_characteristic() { return productOfAllCharacteristics_; } + + /** + * @brief Returns the value of the element. + * + * @return Value of the element. + */ + element_type get_value() const { return element_; } + + // static constexpr bool handles_only_z2() { return false; } + + private: + static constexpr bool _is_prime(const unsigned int p) { + if (p <= 1) return false; + if (p <= 3) return true; + if (p % 2 == 0 || p % 3 == 0) return false; + + for (unsigned long i = 5; i * i <= p; i = i + 6) + if (p % i == 0 || p % (i + 2) == 0) return false; + + return true; + } + static element_type _multiply(element_type a, element_type b) { + element_type res = 0; + element_type temp_b = 0; + + if (b < a) std::swap(a, b); + + while (a != 0) { + if (a & 1) { + /* Add b to res, modulo m, without overflow */ + if (b >= productOfAllCharacteristics_ - res) res -= productOfAllCharacteristics_; + res += b; + } + a >>= 1; + + /* Double b, modulo m */ + temp_b = b; + if (b >= productOfAllCharacteristics_ - b) temp_b -= productOfAllCharacteristics_; + b += temp_b; + } + return res; + } + static element_type _add(element_type element, element_type v) { + if (UINT_MAX - element < v) { + // automatic unsigned integer overflow behaviour will make it work + element += v; + element -= productOfAllCharacteristics_; + return element; + } + + element += v; + if (element >= productOfAllCharacteristics_) element -= productOfAllCharacteristics_; + + return element; + } + static element_type _substract(element_type element, element_type v) { + if (element < v) { + element += productOfAllCharacteristics_; + } + element -= v; + + return element; + } + static constexpr int _get_inverse(element_type element, const characteristic_type mod) { + // to solve: Ax + My = 1 + int M = mod; + int A = element; + int y = 0, x = 1; + // extended euclidien division + while (A > 1) { + int quotient = A / M; + int temp = M; + + M = A % M, A = temp; + temp = y; + + y = x - quotient * y; + x = temp; + } + + if (x < 0) x += mod; + + return x; + } + + template > + static constexpr element_type _get_value(Integer_type e) { + if constexpr (std::is_signed_v){ + if (e < -static_cast(productOfAllCharacteristics_)) e = e % productOfAllCharacteristics_; + if (e < 0) return e += productOfAllCharacteristics_; + return e < static_cast(productOfAllCharacteristics_) ? e : e % productOfAllCharacteristics_; + } else { + return e < productOfAllCharacteristics_ ? e : e % productOfAllCharacteristics_; + } + } + + element_type element_; /**< Element. */ + static inline std::vector primes_; /**< All characteristics. */ + static inline characteristic_type productOfAllCharacteristics_; /**< Product of all characteristics. */ + static inline std::vector partials_; /**< Partial products of the characteristics. */ + static inline constexpr element_type multiplicativeID_ = 1; /**< Multiplicative identity. */ +}; + +} // namespace persistence_fields +} // namespace Gudhi + +#endif // MATRIX_FIELD_MULTI_SMALL_SHARED_H_ diff --git a/src/Persistence_matrix/include/gudhi/Fields/Z2_field.h b/src/Persistence_matrix/include/gudhi/Fields/Z2_field.h new file mode 100644 index 0000000000..4877fba31e --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Fields/Z2_field.h @@ -0,0 +1,355 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file Z2_field.h + * @author Hannah Schreiber + * @brief Contains the @ref Z2_field_element class. + */ + +#ifndef MATRIX_FIELD_Z2_H_ +#define MATRIX_FIELD_Z2_H_ + +#include + +namespace Gudhi { +namespace persistence_fields { + +/** + * @class Z2_field_element Z2_field.h gudhi/Fields/Z2_field.h + * @ingroup persistence_fields + * + * @brief Class representing an element of the \f$ \mathbb{F}_2 \f$ field. + */ +class Z2_field_element +{ + public: + using element_type = bool; /**< Type for the elements in the field. */ + template + using isInteger = std::enable_if_t >; + + /** + * @brief Default constructor. + */ + Z2_field_element(); + /** + * @brief Constructor setting the element to the given value. + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic. + * @param element Value of the element. + */ + template > + Z2_field_element(Integer_type element); + /** + * @brief Copy constructor. + * + * @param toCopy Element to copy. + */ + Z2_field_element(const Z2_field_element& toCopy); + /** + * @brief Move constructor. + * + * @param toMove Element to move. + */ + Z2_field_element(Z2_field_element&& toMove) noexcept; + + /** + * @brief operator+= + */ + friend void operator+=(Z2_field_element& f1, Z2_field_element const& f2) { + f1.element_ = (f1.element_ != f2.element_); + } + /** + * @brief operator+ + */ + friend Z2_field_element operator+(Z2_field_element f1, Z2_field_element const& f2) { + f1 += f2; + return f1; + } + /** + * @brief operator+= + * + * @tparam Integer_type A native integer type: int, unsigned int, long int, bool, etc. + */ + template > + friend void operator+=(Z2_field_element& f, const Integer_type& v) { f.element_ = (f.element_ != _get_value(v)); } + /** + * @brief operator+ + * + * @tparam Integer_type A native integer type: int, unsigned int, long int, bool, etc. + */ + template > + friend Z2_field_element operator+(Z2_field_element f, const Integer_type& v) { + f += v; + return f; + } + /** + * @brief operator+ + * + * @tparam Integer_type A native integer type: int, unsigned int, long int, bool, etc. + */ + template > + friend Integer_type operator+(const Integer_type& v, const Z2_field_element& f) { + return f.element_ != _get_value(v); + } + + /** + * @brief operator-= + */ + friend void operator-=(Z2_field_element& f1, Z2_field_element const& f2) { + f1.element_ = (f1.element_ != f2.element_); + } + /** + * @brief operator- + */ + friend Z2_field_element operator-(Z2_field_element f1, Z2_field_element const& f2) { + f1 -= f2; + return f1; + } + /** + * @brief operator-= + * + * @tparam Integer_type A native integer type: int, unsigned int, long int, bool, etc. + */ + template > + friend void operator-=(Z2_field_element& f, const Integer_type& v) { f.element_ = (f.element_ != _get_value(v)); } + /** + * @brief operator- + * + * @tparam Integer_type A native integer type: int, unsigned int, long int, bool, etc. + */ + template > + friend Z2_field_element operator-(Z2_field_element f, const Integer_type& v) { + f -= v; + return f; + } + /** + * @brief operator- + * + * @tparam Integer_type A native integer type: int, unsigned int, long int, bool, etc. + */ + template > + friend Integer_type operator-(const Integer_type v, Z2_field_element const& f) { + return f.element_ != _get_value(v); + } + + /** + * @brief operator*= + */ + friend void operator*=(Z2_field_element& f1, Z2_field_element const& f2) { + f1.element_ = (f1.element_ && f2.element_); + } + /** + * @brief operator* + */ + friend Z2_field_element operator*(Z2_field_element f1, Z2_field_element const& f2) { + f1 *= f2; + return f1; + } + /** + * @brief operator*= + * + * @tparam Integer_type A native integer type: int, unsigned int, long int, bool, etc. + */ + template > + friend void operator*=(Z2_field_element& f, const Integer_type& v) { f.element_ = (f.element_ && _get_value(v)); } + /** + * @brief operator* + * + * @tparam Integer_type A native integer type: int, unsigned int, long int, bool, etc. + */ + template > + friend Z2_field_element operator*(Z2_field_element f, const Integer_type& v) { + f *= v; + return f; + } + /** + * @brief operator* + * + * @tparam Integer_type A native integer type: int, unsigned int, long int, bool, etc. + */ + template > + friend Integer_type operator*(const Integer_type& v, Z2_field_element const& f) { + return f.element_ && _get_value(v); + } + + /** + * @brief operator== + */ + friend bool operator==(const Z2_field_element& f1, const Z2_field_element& f2) { return f1.element_ == f2.element_; } + /** + * @brief operator== + * + * @tparam Integer_type A native integer type: int, unsigned int, long int, bool, etc. + */ + template > + friend bool operator==(const Integer_type& v, const Z2_field_element& f) { + return _get_value(v) == f.element_; + } + /** + * @brief operator== + * + * @tparam Integer_type A native integer type: int, unsigned int, long int, bool, etc. + */ + template > + friend bool operator==(const Z2_field_element& f, const Integer_type& v) { + return _get_value(v) == f.element_; + } + /** + * @brief operator!= + */ + friend bool operator!=(const Z2_field_element& f1, const Z2_field_element& f2) { return !(f1 == f2); } + /** + * @brief operator!= + * + * @tparam Integer_type A native integer type: int, unsigned int, long int, bool, etc. + */ + template > + friend bool operator!=(const Integer_type v, const Z2_field_element& f) { + return !(v == f); + } + /** + * @brief operator!= + * + * @tparam Integer_type A native integer type: int, unsigned int, long int, bool, etc. + */ + template > + friend bool operator!=(const Z2_field_element& f, const Integer_type v) { + return !(v == f); + } + + /** + * @brief Assign operator. + */ + Z2_field_element& operator=(Z2_field_element other); + /** + * @brief Assign operator. + */ + Z2_field_element& operator=(const unsigned int value); + /** + * @brief Swap operator. + */ + friend void swap(Z2_field_element& f1, Z2_field_element& f2) { std::swap(f1.element_, f2.element_); } + + /** + * @brief Casts the element into an unsigned int. + */ + operator unsigned int() const; + + /** + * @brief Returns the inverse of the element. + * + * @return The inverse. + */ + Z2_field_element get_inverse() const; + /** + * @brief For interface purposes with multi-fields. Returns the inverse together with the second argument. + * + * @param productOfCharacteristics Some value. + * @return Pair whose first element is the inverse of @p e and the second element is @p productOfCharacteristics. + */ + std::pair get_partial_inverse(unsigned int productOfCharacteristics) const; + + /** + * @brief Returns the additive identity of the field. + * + * @return false. + */ + static Z2_field_element get_additive_identity(); + /** + * @brief Returns the multiplicative identity of the field. + * + * @return true. + */ + static Z2_field_element get_multiplicative_identity(); + /** + * @brief For interface purposes with multi-fields. Returns the multiplicative identity of the field. + * + * @param productOfCharacteristics Some value. + * @return true. + */ + static Z2_field_element get_partial_multiplicative_identity([[maybe_unused]] unsigned int productOfCharacteristics); + /** + * @brief Returns the characteristic of the field, that is `2`. + * + * @return 2. + */ + static constexpr unsigned int get_characteristic(); + + /** + * @brief Returns the value of the element. + * + * @return Value of the element. + */ + element_type get_value() const; + + // static constexpr bool handles_only_z2() { return true; } + + private: + element_type element_; + + template > + static constexpr element_type _get_value(Integer_type e) { + if constexpr (std::is_same_v) { + return e; + } else { + return e < 2 && e >= 0 ? e : e % 2; // returns bool, so %2 won't be negative and is optimized to & + } + } +}; + +inline Z2_field_element::Z2_field_element() : element_(false) {} + +template +inline Z2_field_element::Z2_field_element(Integer_type element) : element_(_get_value(element)) {} + +inline Z2_field_element::Z2_field_element(const Z2_field_element& toCopy) : element_(toCopy.element_) {} + +inline Z2_field_element::Z2_field_element(Z2_field_element&& toMove) noexcept + : element_(std::exchange(toMove.element_, 0)) {} + +inline Z2_field_element& Z2_field_element::operator=(Z2_field_element other) { + std::swap(element_, other.element_); + return *this; +} + +inline Z2_field_element& Z2_field_element::operator=(unsigned int const value) { + element_ = _get_value(value); + return *this; +} + +inline Z2_field_element::operator unsigned int() const { return element_; } + +inline Z2_field_element Z2_field_element::get_inverse() const { + return element_ ? Z2_field_element(1) : Z2_field_element(); +} + +inline std::pair Z2_field_element::get_partial_inverse( + unsigned int productOfCharacteristics) const { + return {get_inverse(), productOfCharacteristics}; +} + +inline Z2_field_element Z2_field_element::get_additive_identity() { return Z2_field_element(); } + +inline Z2_field_element Z2_field_element::get_multiplicative_identity() { return Z2_field_element(1); } + +inline Z2_field_element Z2_field_element::get_partial_multiplicative_identity( + [[maybe_unused]] unsigned int productOfCharacteristics) { + return Z2_field_element(1); +} + +inline constexpr unsigned int Z2_field_element::get_characteristic() { return 2; } + +inline Z2_field_element::element_type Z2_field_element::get_value() const { return element_; } + +} // namespace persistence_fields +} // namespace Gudhi + +#endif // MATRIX_FIELD_Z2_H_ diff --git a/src/Persistence_matrix/include/gudhi/Fields/Z2_field_operators.h b/src/Persistence_matrix/include/gudhi/Fields/Z2_field_operators.h new file mode 100644 index 0000000000..bf6ec53a67 --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Fields/Z2_field_operators.h @@ -0,0 +1,376 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2024 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file Z2_field_operators.h + * @author Hannah Schreiber + * @brief Contains the @ref Z2_field_operators class. + */ + +#ifndef MATRIX_FIELD_Z2_OPERATORS_H_ +#define MATRIX_FIELD_Z2_OPERATORS_H_ + +#include + +namespace Gudhi { +namespace persistence_fields { + +/** + * @class Z2_field_operators Z2_field_operators.h gudhi/Fields/Z2_field_operators.h + * @ingroup persistence_fields + * + * @brief Class defining operators for the \f$ \mathbb{F}_2 \f$ field. + */ +class Z2_field_operators +{ + public: + using element_type = bool; /**< Type for the elements in the field. */ + using characteristic_type = unsigned int; /**< Type for the field characteristic. */ + template + using isUnsignedInteger = std::enable_if_t >; + template + using isInteger = std::enable_if_t >; + + /** + * @brief Default constructor. + */ + Z2_field_operators(){}; + + /** + * @brief Returns the characteristic of the field, that is `2`. + * + * @return 2. + */ + static constexpr characteristic_type get_characteristic() { return 2; } + + /** + * @brief Returns the value of an integer in the field. + * That is the positive value of the integer modulo the current characteristic. + * + * @tparam Integer_type A native integer type: int, unsigned int, long int, bool, etc. + * @param e Integer to return the value from. + * @return A boolean representing `e % 2`. + */ + template > + static element_type get_value(Integer_type e) { + if constexpr (std::is_same_v) { + return e; + } else { + return e < 2 && e >= 0 ? e : e % 2; // returns bool, so %2 won't be negative and is optimized to & + } + } + + /** + * @brief Returns the sum of two elements in the field. + * + * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, bool, etc. + * @param e1 First element. + * @param e2 Second element. + * @return `(e1 + e2) % 2` as a boolean. + */ + template > + static element_type add(Unsigned_integer_type e1, Unsigned_integer_type e2) { + if constexpr (std::is_same_v) { + return e1 != e2; + } else { + return get_value(e1) != get_value(e2); + } + } + + /** + * @brief Stores in the first element the sum of two given elements in the field, that is + * `(e1 + e2) % 2`, such that the result is positive. + * + * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, bool, etc. + * @param e1 First element. + * @param e2 Second element. + */ + template > + static void add_inplace(Unsigned_integer_type& e1, Unsigned_integer_type e2) { + if constexpr (std::is_same_v) { + e1 = e1 != e2; + } else { + e1 = get_value(e1) != get_value(e2); + } + } + + /** + * @brief Returns the substraction in the field of the first element by the second element. + * + * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, bool, etc. + * @param e1 First element. + * @param e2 Second element. + * @return `(e1 - e2) % 2` as a boolean. + */ + template > + static element_type substract(Unsigned_integer_type e1, Unsigned_integer_type e2) { + if constexpr (std::is_same_v) { + return e1 != e2; + } else { + return get_value(e1) != get_value(e2); + } + } + + /** + * @brief Stores in the first element the substraction in the field of the first element by the second element, + * that is `(e1 - e2) % 2`, such that the result is positive. + * + * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, bool, etc. + * @param e1 First element. + * @param e2 Second element. + */ + template > + static void substract_inplace_front(Unsigned_integer_type& e1, Unsigned_integer_type e2) { + if constexpr (std::is_same_v) { + e1 = e1 != e2; + } else { + e1 = get_value(e1) != get_value(e2); + } + } + /** + * @brief Stores in the second element the substraction in the field of the first element by the second element, + * that is `(e1 - e2) % 2`, such that the result is positive. + * + * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, bool, etc. + * @param e1 First element. + * @param e2 Second element. + */ + template > + static void substract_inplace_back(Unsigned_integer_type e1, Unsigned_integer_type& e2) { + if constexpr (std::is_same_v) { + e2 = e1 != e2; + } else { + e2 = get_value(e1) != get_value(e2); + } + } + + /** + * @brief Returns the multiplication of two elements in the field. + * + * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, bool, etc. + * @param e1 First element. + * @param e2 Second element. + * @return `(e1 * e2) % 2` as a boolean. + */ + template > + static element_type multiply(Unsigned_integer_type e1, Unsigned_integer_type e2) { + if constexpr (std::is_same_v) { + return e1 && e2; + } else { + return get_value(e1) ? get_value(e2) : false; + } + } + + /** + * @brief Stores in the first element the multiplication of two given elements in the field, + * that is `(e1 * e2) % 2`, such that the result is positive. + * + * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, bool, etc. + * @param e1 First element. + * @param e2 Second element. + */ + template > + static void multiply_inplace(Unsigned_integer_type& e1, Unsigned_integer_type e2) { + if constexpr (std::is_same_v) { + e1 = e1 && e2; + } else { + e1 = get_value(e1) ? get_value(e2) : false; + } + } + + /** + * @brief Multiplies the first element with the second one and adds the third one. Returns the result in the field. + * + * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, bool, etc. + * @param e First element. + * @param m Second element. + * @param a Third element. + * @return `(e * m + a) % 2` as a boolean. + */ + template > + static element_type multiply_and_add(Unsigned_integer_type e, Unsigned_integer_type m, Unsigned_integer_type a) { + if constexpr (std::is_same_v) { + return (e && m) != a; + } else { + return multiply(e, m) != get_value(a); + } + } + + /** + * @brief Multiplies the first element with the second one and adds the third one, that is + * `(e * m + a) % 2`, such that the result is positive. Stores the result in the first element. + * + * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, bool, etc. + * @param e First element. + * @param m Second element. + * @param a Third element. + */ + template > + static void multiply_and_add_inplace_front(Unsigned_integer_type& e, + Unsigned_integer_type m, + Unsigned_integer_type a) { + if constexpr (std::is_same_v) { + e = (e && m) != a; + } else { + e = multiply(e, m) != get_value(a); + } + } + + /** + * @brief Multiplies the first element with the second one and adds the third one, that is + * `(e * m + a) % 2`, such that the result is positive. Stores the result in the third element. + * + * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, bool, etc. + * @param e First element. + * @param m Second element. + * @param a Third element. + */ + template > + static void multiply_and_add_inplace_back(Unsigned_integer_type e, + Unsigned_integer_type m, + Unsigned_integer_type& a) { + if constexpr (std::is_same_v) { + a = (e && m) != a; + } else { + a = multiply(e, m) != get_value(a); + } + } + + /** + * @brief Adds the first element to the second one and multiplies the third one with it. + * Returns the result in the field. + * + * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, bool, etc. + * @param e First element. + * @param a Second element. + * @param m Third element. + * @return `((e + a) * m) % 2` as a boolean. + */ + template > + static element_type add_and_multiply(Unsigned_integer_type e, Unsigned_integer_type a, Unsigned_integer_type m) { + if constexpr (std::is_same_v) { + return (e != a) && m; + } else { + return add(e, a) ? get_value(m) : false; + } + } + + /** + * @brief Adds the first element to the second one and multiplies the third one with it, that is + * `((e + a) * m) % 2`, such that the result is positive. Stores the result in the first element. + * + * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, bool, etc. + * @param e First element. + * @param a Second element. + * @param m Third element. + */ + template > + static void add_and_multiply_inplace_front(Unsigned_integer_type& e, Unsigned_integer_type a, Unsigned_integer_type m) { + if constexpr (std::is_same_v) { + e = (e != a) && m; + } else { + e = add(e, a) ? get_value(m) : false; + } + } + /** + * @brief Adds the first element to the second one and multiplies the third one with it, that is + * `((e + a) * m) % 2`, such that the result is positive. Stores the result in the third element. + * + * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, bool, etc. + * @param e First element. + * @param a Second element. + * @param m Third element. + */ + template > + static void add_and_multiply_inplace_back(Unsigned_integer_type& e, Unsigned_integer_type a, Unsigned_integer_type m) { + if constexpr (std::is_same_v) { + m = (e != a) && m; + } else { + m = add(e, a) ? get_value(m) : false; + } + } + + /** + * @brief Returns true if the two given elements are equal in the field, false otherwise. + * + * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, bool, etc. + * @param e1 First element to compare. + * @param e2 Second element to compare. + * @return true If `e1 % 2 == e2 % 2`. + * @return false Otherwise. + */ + template > + static bool are_equal(Unsigned_integer_type e1, Unsigned_integer_type e2) { + if constexpr (std::is_same_v) { + return e1 == e2; + } else { + return get_value(e1) == get_value(e2); + } + } + + /** + * @brief Returns the inverse of the given element in the field. + * + * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, bool, etc. + * @param e Element to get the inverse from. + * @return Inverse in the current field of `e % 2`. + */ + template > + static element_type get_inverse(Unsigned_integer_type e) { + if constexpr (std::is_same_v) { + return e; + } else { + return get_value(e); + } + } + /** + * @brief For interface purposes with multi-fields. Returns the inverse together with the second argument. + * + * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, bool, etc. + * @param e Element to get the inverse from. + * @param productOfCharacteristics Some value. + * @return Pair whose first element is the inverse of @p e and the second element is @p productOfCharacteristics. + */ + template > + static std::pair get_partial_inverse( + Unsigned_integer_type e, characteristic_type productOfCharacteristics) { + return {get_inverse(e), productOfCharacteristics}; + } + + /** + * @brief Returns the additive identity of the field. + * + * @return false. + */ + static constexpr element_type get_additive_identity() { return false; } + /** + * @brief Returns the multiplicative identity of the field. + * + * @return true. + */ + static constexpr element_type get_multiplicative_identity() { return true; } + /** + * @brief For interface purposes with multi-fields. Returns the multiplicative identity of the field. + * + * @param productOfCharacteristics Some value. + * @return true. + */ + static constexpr element_type get_partial_multiplicative_identity( + [[maybe_unused]] characteristic_type productOfCharacteristics) { + return true; + } + + // static constexpr bool handles_only_z2() { return true; } +}; + +} // namespace persistence_fields +} // namespace Gudhi + +#endif // MATRIX_FIELD_Z2_OPERATORS_H_ diff --git a/src/Persistence_matrix/include/gudhi/Fields/Zp_field.h b/src/Persistence_matrix/include/gudhi/Fields/Zp_field.h new file mode 100644 index 0000000000..56fb4cf4f3 --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Fields/Zp_field.h @@ -0,0 +1,420 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file Zp_field.h + * @author Hannah Schreiber + * @brief Contains the @ref Zp_field_element class. + */ + +#ifndef MATRIX_FIELD_ZP_H_ +#define MATRIX_FIELD_ZP_H_ + +#include +#include +#include + +namespace Gudhi { +namespace persistence_fields { + +/** + * @class Zp_field_element Zp_field.h gudhi/Fields/Zp_field.h + * @ingroup persistence_fields + * + * @brief Class representing an element of the \f$ \mathbb{F}_p \f$ field for any prime number \f$ p \f$. + * + * @tparam characteristic Value of the characteristic of the field. Has to be a positive prime number. + * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, long unsigned int, etc. + * Will be used as the field element type. + */ +template > > +class Zp_field_element { + public: + using element_type = Unsigned_integer_type; /**< Type for the elements in the field. */ + using characteristic_type = element_type; /**< Type for the field characteristic. */ + template + using isInteger = std::enable_if_t >; + + /** + * @brief Default constructor. Sets the element to 0. + */ + Zp_field_element() : element_(0) { static_assert(_is_prime(), "Characteristic has to be a prime number."); } + /** + * @brief Constructor setting the element to the given value. + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + * @param element Value of the element. + */ + template > + Zp_field_element(Integer_type element) : element_(_get_value(element)) { + static_assert(_is_prime(), "Characteristic has to be a prime number."); + } + /** + * @brief Copy constructor. + * + * @param toCopy Element to copy. + */ + Zp_field_element(const Zp_field_element& toCopy) : element_(toCopy.element_) {} + /** + * @brief Move constructor. + * + * @param toMove Element to move. + */ + Zp_field_element(Zp_field_element&& toMove) noexcept : element_(std::exchange(toMove.element_, 0)) {} + + /** + * @brief operator+= + */ + friend void operator+=(Zp_field_element& f1, const Zp_field_element& f2) { + f1.element_ = Zp_field_element::_add(f1.element_, f2.element_); + } + /** + * @brief operator+ + */ + friend Zp_field_element operator+(Zp_field_element f1, const Zp_field_element& f2) { + f1 += f2; + return f1; + } + /** + * @brief operator+= + */ + template > + friend void operator+=(Zp_field_element& f, const Integer_type& v) { + f.element_ = Zp_field_element::_add(f.element_, _get_value(v)); + } + /** + * @brief operator+ + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend Zp_field_element operator+(Zp_field_element f, const Integer_type& v) { + f += v; + return f; + } + /** + * @brief operator+ + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend Integer_type operator+(const Integer_type& v, Zp_field_element f) { + f += v; + return f.element_; + } + + /** + * @brief operator-= + */ + friend void operator-=(Zp_field_element& f1, const Zp_field_element& f2) { + f1.element_ = Zp_field_element::_substract(f1.element_, f2.element_); + } + /** + * @brief operator- + */ + friend Zp_field_element operator-(Zp_field_element f1, const Zp_field_element& f2) { + f1 -= f2; + return f1; + } + /** + * @brief operator-= + */ + template > + friend void operator-=(Zp_field_element& f, const Integer_type& v) { + f.element_ = Zp_field_element::_substract(f.element_, _get_value(v)); + } + /** + * @brief operator- + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend Zp_field_element operator-(Zp_field_element f, const Integer_type& v) { + f -= v; + return f; + } + /** + * @brief operator- + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend Integer_type operator-(const Integer_type& v, const Zp_field_element& f) { + return Zp_field_element::_substract(_get_value(v), f.element_); + } + + /** + * @brief operator*= + */ + friend void operator*=(Zp_field_element& f1, const Zp_field_element& f2) { + f1.element_ = Zp_field_element::_multiply(f1.element_, f2.element_); + } + /** + * @brief operator* + */ + friend Zp_field_element operator*(Zp_field_element f1, const Zp_field_element& f2) { + f1 *= f2; + return f1; + } + /** + * @brief operator*= + */ + template > + friend void operator*=(Zp_field_element& f, const Integer_type& v) { + f.element_ = Zp_field_element::_multiply(f.element_, _get_value(v)); + } + /** + * @brief operator* + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend Zp_field_element operator*(Zp_field_element f, const Integer_type& v) { + f *= v; + return f; + } + /** + * @brief operator* + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend Integer_type operator*(const Integer_type& v, Zp_field_element f) { + f *= v; + return f.element_; + } + + /** + * @brief operator== + */ + friend bool operator==(const Zp_field_element& f1, const Zp_field_element& f2) { + return f1.element_ == f2.element_; + } + /** + * @brief operator== + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend bool operator==(const Integer_type& v, const Zp_field_element& f) { + return Zp_field_element::_get_value(v) == f.element_; + } + /** + * @brief operator== + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend bool operator==(const Zp_field_element& f, const Integer_type& v) { + return Zp_field_element::_get_value(v) == f.element_; + } + /** + * @brief operator!= + */ + friend bool operator!=(const Zp_field_element& f1, const Zp_field_element& f2) { return !(f1 == f2); } + /** + * @brief operator!= + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend bool operator!=(const Integer_type& v, const Zp_field_element& f) { + return !(v == f); + } + /** + * @brief operator!= + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend bool operator!=(const Zp_field_element& f, const Integer_type& v) { + return !(v == f); + } + + /** + * @brief Assign operator. + */ + Zp_field_element& operator=(Zp_field_element other) { + std::swap(element_, other.element_); + return *this; + } + /** + * @brief Assign operator. + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + Zp_field_element& operator=(const Integer_type& value) { + element_ = _get_value(value); + return *this; + } + /** + * @brief Swap operator. + */ + friend void swap(Zp_field_element& f1, Zp_field_element& f2) { std::swap(f1.element_, f2.element_); } + + /** + * @brief Casts the element into an unsigned int. + */ + operator unsigned int() const { return element_; } + + /** + * @brief Returns the inverse of the element in the field. + * + * @return The inverse. + */ + Zp_field_element get_inverse() const { + if (element_ != 0 && inverse_[element_] == 0) { // initialize everything at instanciation instead? + inverse_[element_] = _get_inverse(element_); + } + + return Zp_field_element(inverse_[element_]); + } + /** + * @brief For interface purposes with multi-fields. Returns the inverse together with the argument. + * + * @param productOfCharacteristics Some value. + * @return Pair whose first element is the inverse and the second element is @p productOfCharacteristics. + */ + std::pair get_partial_inverse(unsigned int productOfCharacteristics) const { + return {get_inverse(), productOfCharacteristics}; + } + + /** + * @brief Returns the additive identity of the field. + * + * @return 0. + */ + static Zp_field_element get_additive_identity() { return Zp_field_element(); } + /** + * @brief Returns the multiplicative identity of the field. + * + * @return 1. + */ + static Zp_field_element get_multiplicative_identity() { return Zp_field_element(1); } + /** + * @brief For interface purposes with multi-fields. Returns the multiplicative identity of the field. + * + * @param productOfCharacteristics Some value. + * @return 1. + */ + static Zp_field_element get_partial_multiplicative_identity([[maybe_unused]] unsigned int productOfCharacteristics) { + return Zp_field_element(1); + } + /** + * @brief Returns the current characteristic. + * + * @return The value of the current characteristic. + */ + static constexpr unsigned int get_characteristic() { return characteristic; } + + /** + * @brief Returns the value of the element. + * + * @return Value of the element. + */ + element_type get_value() const { return element_; } + + // static constexpr bool handles_only_z2() { return false; } + + private: + element_type element_; /**< Field element. */ + static inline std::array inverse_; /**< All inverse elements. */ + + static element_type _add(element_type element, element_type v) { + if (UINT_MAX - element < v) { + // automatic unsigned integer overflow behaviour will make it work + element += v; + element -= characteristic; + return element; + } + + element += v; + if (element >= characteristic) element -= characteristic; + + return element; + } + static element_type _substract(element_type element, element_type v) { + if (element < v) { + element += characteristic; + } + element -= v; + + return element; + } + static element_type _multiply(element_type element, element_type v) { + element_type a = element; + element = 0; + element_type temp_b; + + while (a != 0) { + if (a & 1) { + if (v >= characteristic - element) element -= characteristic; + element += v; + } + a >>= 1; + + temp_b = v; + if (v >= characteristic - v) temp_b -= characteristic; + v += temp_b; + } + + return element; + } + static int _get_inverse(element_type element) { + // to solve: Ax + My = 1 + int M = characteristic; + int A = element; + int y = 0, x = 1; + // extended euclidien division + while (A > 1) { + int quotient = A / M; + int temp = M; + + M = A % M, A = temp; + temp = y; + + y = x - quotient * y; + x = temp; + } + + if (x < 0) x += characteristic; + + return x; + } + + template > + static constexpr element_type _get_value(Integer_type e) { + if constexpr (std::is_signed_v) { + if (e < -static_cast(characteristic)) e = e % characteristic; + if (e < 0) return e += characteristic; + return e < static_cast(characteristic) ? e : e % characteristic; + } else { + return e < characteristic ? e : e % characteristic; + } + } + + static constexpr bool _is_prime() { + if (characteristic <= 1) return false; + if (characteristic <= 3) return true; + if (characteristic % 2 == 0 || characteristic % 3 == 0) return false; + + for (long i = 5; i * i <= characteristic; i = i + 6) + if (characteristic % i == 0 || characteristic % (i + 2) == 0) return false; + + return true; + } +}; + +} // namespace persistence_fields +} // namespace Gudhi + +#endif // MATRIX_FIELD_ZP_H_ diff --git a/src/Persistence_matrix/include/gudhi/Fields/Zp_field_operators.h b/src/Persistence_matrix/include/gudhi/Fields/Zp_field_operators.h new file mode 100644 index 0000000000..eb6b9c4ca9 --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Fields/Zp_field_operators.h @@ -0,0 +1,400 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2024 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file Zp_field_operators.h + * @author Hannah Schreiber + * @brief Contains the @ref Zp_field_operators class. + */ + +#ifndef MATRIX_FIELD_ZP_OPERATOR_H_ +#define MATRIX_FIELD_ZP_OPERATOR_H_ + +#include +#include +#include +#include + +namespace Gudhi { +/// Field namespace +namespace persistence_fields { + +/** + * @class Zp_field_operators Zp_field_operators.h gudhi/Fields/Zp_field_operators.h + * @ingroup persistence_fields + * + * @brief Class defining operators for the \f$ \mathbb{F}_p \f$ field for any prime number \f$ p \f$. + * + * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, long unsigned int, etc. + * Will be used as the field element type. + */ +template > > +class Zp_field_operators +{ + public: + using element_type = Unsigned_integer_type; /**< Type for the elements in the field. */ + using characteristic_type = element_type; /**< Type for the field characteristic. */ + template + using isSignedInteger = std::enable_if_t >; + + /** + * @brief Default constructor. If a non-zero characteristic is given, initializes the field with it. + * The characteristic can later be changed again or initialized with @ref set_characteristic. + * + * @param characteristic Prime number corresponding to the desired characteristic of the field. + */ + Zp_field_operators(characteristic_type characteristic = 0) : characteristic_(0) { + if (characteristic != 0) set_characteristic(characteristic); + } + /** + * @brief Copy constructor. + * + * @param toCopy Operators to copy. + */ + Zp_field_operators(const Zp_field_operators& toCopy) + : characteristic_(toCopy.characteristic_), inverse_(toCopy.inverse_) {} + /** + * @brief Move constructor. + * + * @param toMove Operators to move. + */ + Zp_field_operators(Zp_field_operators&& toMove) noexcept + : characteristic_(std::exchange(toMove.characteristic_, 0)), inverse_(std::move(toMove.inverse_)) {} + + /** + * @brief Sets the characteristic of the field. + * + * @param characteristic Prime number corresponding to the desired characteristic of the field. + */ + void set_characteristic(characteristic_type characteristic) { + if (characteristic <= 1) + throw std::invalid_argument("Characteristic must be strictly positive and a prime number."); + + inverse_.resize(characteristic); + inverse_[0] = 0; + for (unsigned int i = 1; i < characteristic; ++i) { + unsigned int inv = 1; + unsigned int mult = inv * i; + while ((mult % characteristic) != 1) { + ++inv; + if (mult == characteristic) throw std::invalid_argument("Characteristic must be a prime number."); + mult = inv * i; + } + inverse_[i] = inv; + } + + characteristic_ = characteristic; + } + /** + * @brief Returns the current characteristic. + * + * @return The value of the current characteristic. + */ + const characteristic_type& get_characteristic() const { return characteristic_; } + + /** + * @brief Returns the value of an integer in the field. + * That is the positive value of the integer modulo the current characteristic. + * + * @param e Unsigned integer to return the value from. + * @return @p e modulo the current characteristic, such that the result is positive. + */ + element_type get_value(element_type e) const { return e < characteristic_ ? e : e % characteristic_; } + /** + * @brief Returns the value of an integer in the field. + * That is the positive value of the integer modulo the current characteristic. + * + * @tparam Signed_integer_type A native signed integer type: int, long int, etc. + * @param e Integer to return the value from. + * @return @p e modulo the current characteristic, such that the result is positive. + */ + template > + element_type get_value(Signed_integer_type e) const { + if (e < -static_cast(characteristic_)) e = e % characteristic_; + if (e < 0) return e += characteristic_; + return e < static_cast(characteristic_) ? e : e % characteristic_; + } + + /** + * @brief Returns the sum of two elements in the field. + * + * @param e1 First element. + * @param e2 Second element. + * @return `(e1 + e2) % characteristic`, such that the result is positive. + */ + element_type add(element_type e1, element_type e2) const { + return _add(get_value(e1), get_value(e2), characteristic_); + } + + /** + * @brief Stores in the first element the sum of two given elements in the field, that is + * `(e1 + e2) % characteristic`, such that the result is positive. + * + * @param e1 First element. + * @param e2 Second element. + */ + void add_inplace(element_type& e1, element_type e2) const { + e1 = _add(get_value(e1), get_value(e2), characteristic_); + } + + /** + * @brief Returns the substraction in the field of the first element by the second element. + * + * @param e1 First element. + * @param e2 Second element. + * @return `(e1 - e2) % characteristic`, such that the result is positive. + */ + element_type substract(element_type e1, element_type e2) const { + return _substract(get_value(e1), get_value(e2), characteristic_); + } + + /** + * @brief Stores in the first element the substraction in the field of the first element by the second element, + * that is `(e1 - e2) % 2`, such that the result is positive. + * + * @param e1 First element. + * @param e2 Second element. + */ + void substract_inplace_front(element_type& e1, element_type e2) const { + e1 = _substract(get_value(e1), get_value(e2), characteristic_); + } + /** + * @brief Stores in the second element the substraction in the field of the first element by the second element, + * that is `(e1 - e2) % 2`, such that the result is positive. + * + * @param e1 First element. + * @param e2 Second element. + */ + void substract_inplace_back(element_type e1, element_type& e2) const { + e2 = _substract(get_value(e1), get_value(e2), characteristic_); + } + + /** + * @brief Returns the multiplication of two elements in the field. + * + * @param e1 First element. + * @param e2 Second element. + * @return `(e1 * e2) % characteristic`, such that the result is positive. + */ + element_type multiply(element_type e1, element_type e2) const { + return _multiply(get_value(e1), get_value(e2), characteristic_); + } + + /** + * @brief Stores in the first element the multiplication of two given elements in the field, + * that is `(e1 * e2) % characteristic`, such that the result is positive. + * + * @param e1 First element. + * @param e2 Second element. + */ + void multiply_inplace(element_type& e1, element_type e2) const { + e1 = _multiply(get_value(e1), get_value(e2), characteristic_); + } + + /** + * @brief Multiplies the first element with the second one and adds the third one. Returns the result in the field. + * + * @warning Not overflow safe. + * + * @param e First element. + * @param m Second element. + * @param a Third element. + * @return `(e * m + a) % characteristic`, such that the result is positive. + */ + element_type multiply_and_add(element_type e, element_type m, element_type a) const { return get_value(e * m + a); } + + /** + * @brief Multiplies the first element with the second one and adds the third one, that is + * `(e * m + a) % characteristic`, such that the result is positive. Stores the result in the first element. + * + * @warning Not overflow safe. + * + * @param e First element. + * @param m Second element. + * @param a Third element. + */ + void multiply_and_add_inplace_front(element_type& e, element_type m, element_type a) const { + e = get_value(e * m + a); + } + /** + * @brief Multiplies the first element with the second one and adds the third one, that is + * `(e * m + a) % characteristic`, such that the result is positive. Stores the result in the third element. + * + * @warning Not overflow safe. + * + * @param e First element. + * @param m Second element. + * @param a Third element. + */ + void multiply_and_add_inplace_back(element_type e, element_type m, element_type& a) const { + a = get_value(e * m + a); + } + + /** + * @brief Adds the first element to the second one and multiplies the third one with it. + * Returns the result in the field. + * + * @warning Not overflow safe. + * + * @param e First element. + * @param a Second element. + * @param m Third element. + * @return `((e + a) * m) % characteristic`, such that the result is positive. + */ + element_type add_and_multiply(element_type e, element_type a, element_type m) const { return get_value((e + a) * m); } + + /** + * @brief Adds the first element to the second one and multiplies the third one with it, that is + * `((e + a) * m) % characteristic`, such that the result is positive. Stores the result in the first element. + * + * @warning Not overflow safe. + * + * @param e First element. + * @param a Second element. + * @param m Third element. + */ + void add_and_multiply_inplace_front(element_type& e, element_type a, element_type m) const { + e = get_value((e + a) * m); + } + /** + * @brief Adds the first element to the second one and multiplies the third one with it, that is + * `((e + a) * m) % characteristic`, such that the result is positive. Stores the result in the third element. + * + * @warning Not overflow safe. + * + * @param e First element. + * @param a Second element. + * @param m Third element. + */ + void add_and_multiply_inplace_back(element_type e, element_type a, element_type& m) const { + m = get_value((e + a) * m); + } + + /** + * @brief Returns true if the two given elements are equal in the field, false otherwise. + * + * @param e1 First element to compare. + * @param e2 Second element to compare. + * @return true If `e1 % characteristic == e2 % characteristic`. + * @return false Otherwise. + */ + bool are_equal(element_type e1, element_type e2) const { return get_value(e1) == get_value(e2); } + + /** + * @brief Returns the inverse of the given element in the field. + * + * @param e Element to get the inverse from. + * @return Inverse in the current field of `e % characteristic`. + */ + element_type get_inverse(element_type e) const { return inverse_[get_value(e)]; } + /** + * @brief For interface purposes with multi-fields. Returns the inverse together with the second argument. + * + * @param e Element to get the inverse from. + * @param productOfCharacteristics Some value. + * @return Pair whose first element is the inverse of @p e and the second element is @p productOfCharacteristics. + */ + std::pair get_partial_inverse(element_type e, + characteristic_type productOfCharacteristics) const { + return {get_inverse(e), productOfCharacteristics}; + } + + /** + * @brief Returns the additive identity of the field. + * + * @return 0. + */ + static constexpr element_type get_additive_identity() { return 0; } + /** + * @brief Returns the multiplicative identity of the field. + * + * @return 1. + */ + static constexpr element_type get_multiplicative_identity() { return 1; } + /** + * @brief For interface purposes with multi-fields. Returns the multiplicative identity of the field. + * + * @param productOfCharacteristics Some value. + * @return 1. + */ + static constexpr element_type get_partial_multiplicative_identity( + [[maybe_unused]] characteristic_type productOfCharacteristics) { + return 1; + } + + // static constexpr bool handles_only_z2() { return false; } + + /** + * @brief Assign operator. + */ + Zp_field_operators& operator=(Zp_field_operators other) { + std::swap(characteristic_, other.characteristic_); + inverse_.swap(other.inverse_); + return *this; + } + /** + * @brief Swap operator. + */ + friend void swap(Zp_field_operators& f1, Zp_field_operators& f2) { + std::swap(f1.characteristic_, f2.characteristic_); + f1.inverse_.swap(f2.inverse_); + } + + private: + characteristic_type characteristic_; /**< Current characteristic of the field. */ + std::vector inverse_; /**< All inverse elements. */ + + static element_type _add(element_type e1, element_type e2, characteristic_type characteristic) { + if (UINT_MAX - e1 < e2) { + // automatic unsigned integer overflow behaviour will make it work + e1 += e2; + e1 -= characteristic; + return e1; + } + + e1 += e2; + if (e1 >= characteristic) e1 -= characteristic; + + return e1; + } + static element_type _substract(element_type e1, element_type e2, characteristic_type characteristic) { + if (e1 < e2) { + e1 += characteristic; + } + e1 -= e2; + + return e1; + } + static element_type _multiply(element_type e1, element_type e2, characteristic_type characteristic) { + unsigned int a = e1; + e1 = 0; + unsigned int temp_b; + + while (a != 0) { + if (a & 1) { + if (e2 >= characteristic - e1) e1 -= characteristic; + e1 += e2; + } + a >>= 1; + + temp_b = e2; + if (e2 >= characteristic - e2) temp_b -= characteristic; + e2 += temp_b; + } + + return e1; + } +}; + +} // namespace persistence_fields +} // namespace Gudhi + +#endif // MATRIX_FIELD_ZP_OPERATOR_H_ diff --git a/src/Persistence_matrix/include/gudhi/Fields/Zp_field_shared.h b/src/Persistence_matrix/include/gudhi/Fields/Zp_field_shared.h new file mode 100644 index 0000000000..d553559405 --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Fields/Zp_field_shared.h @@ -0,0 +1,418 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file Zp_field_shared.h + * @author Hannah Schreiber + * @brief Contains the @ref Shared_Zp_field_element class. + */ + +#ifndef MATRIX_FIELD_ZP_VAR_H_ +#define MATRIX_FIELD_ZP_VAR_H_ + +#include +#include +#include +#include + +namespace Gudhi { +namespace persistence_fields { + +/** + * @class Shared_Zp_field_element Zp_field_shared.h gudhi/Fields/Zp_field_shared.h + * @ingroup persistence_fields + * + * @brief Class representing an element of the \f$ \mathbb{F}_p \f$ field for any prime number \f$ p \f$. + * If each instanciation of the class can represent another element, they all share the same characteritics. + * That is if the characteristics are set for one, they will be set for all the others. The characteristics can + * be set before instianciating the elements with the static @ref Shared_Zp_field_element::initialize method. + * + * @tparam Unsigned_integer_type A native unsigned integer type: unsigned int, long unsigned int, etc. + * Will be used as the field element type. + */ +template > > +class Shared_Zp_field_element { + public: + using element_type = Unsigned_integer_type; /**< Type for the elements in the field. */ + using characteristic_type = element_type; /**< Type for the field characteristic. */ + template + using isInteger = std::enable_if_t >; + + /** + * @brief Default constructor. Sets the element to 0. + */ + Shared_Zp_field_element() : element_(0) {} + /** + * @brief Constructor setting the element to the given value. + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + * @param element Value of the element. + */ + template > + Shared_Zp_field_element(Integer_type element) : element_(_get_value(element)) {} + /** + * @brief Copy constructor. + * + * @param toCopy Element to copy. + */ + Shared_Zp_field_element(const Shared_Zp_field_element& toCopy) : element_(toCopy.element_) {} + /** + * @brief Move constructor. + * + * @param toMove Element to move. + */ + Shared_Zp_field_element(Shared_Zp_field_element&& toMove) noexcept : element_(std::exchange(toMove.element_, 0)) {} + + /** + * @brief Initialize the field to the given characteristic. + * Should be called first before constructing the field elements. + * + * @param characteristic Characteristic of the field. A positive prime number. + */ + static void initialize(characteristic_type characteristic) { + if (characteristic <= 1) + throw std::invalid_argument("Characteristic must be strictly positive and a prime number."); + + inverse_.resize(characteristic); + inverse_[0] = 0; + for (element_type i = 1; i < characteristic; ++i) { + element_type inv = 1; + element_type mult = inv * i; + while ((mult % characteristic) != 1) { + ++inv; + if (mult == characteristic) throw std::invalid_argument("Characteristic must be a prime number."); + mult = inv * i; + } + inverse_[i] = inv; + } + + characteristic_ = characteristic; + } + + /** + * @brief Returns the value of the element. + * + * @return Value of the element. + */ + element_type get_value() const { return element_; } + + /** + * @brief operator+= + */ + friend void operator+=(Shared_Zp_field_element& f1, const Shared_Zp_field_element& f2) { + f1.element_ = Shared_Zp_field_element::_add(f1.element_, f2.element_); + } + /** + * @brief operator+ + */ + friend Shared_Zp_field_element operator+(Shared_Zp_field_element f1, const Shared_Zp_field_element& f2) { + f1 += f2; + return f1; + } + /** + * @brief operator+= + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend void operator+=(Shared_Zp_field_element& f, const Integer_type& v) { + f.element_ = Shared_Zp_field_element::_add(f.element_, _get_value(v)); + } + /** + * @brief operator+ + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend Shared_Zp_field_element operator+(Shared_Zp_field_element f, const Integer_type& v) { + f += v; + return f; + } + /** + * @brief operator+ + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend Integer_type operator+(const Integer_type& v, Shared_Zp_field_element f) { + f += v; + return f.element_; + } + + /** + * @brief operator-= + */ + friend void operator-=(Shared_Zp_field_element& f1, const Shared_Zp_field_element& f2) { + f1.element_ = Shared_Zp_field_element::_substract(f1.element_, f2.element_); + } + /** + * @brief operator- + */ + friend Shared_Zp_field_element operator-(Shared_Zp_field_element f1, const Shared_Zp_field_element& f2) { + f1 -= f2; + return f1; + } + /** + * @brief operator-= + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend void operator-=(Shared_Zp_field_element& f, const Integer_type& v) { + f.element_ = Shared_Zp_field_element::_substract(f.element_, _get_value(v)); + } + /** + * @brief operator- + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend Shared_Zp_field_element operator-(Shared_Zp_field_element f, const Integer_type& v) { + f -= v; + return f; + } + /** + * @brief operator- + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend Integer_type operator-(const Integer_type& v, const Shared_Zp_field_element& f) { + return Shared_Zp_field_element::_substract(_get_value(v), f.element_); + } + + /** + * @brief operator*= + */ + friend void operator*=(Shared_Zp_field_element& f1, const Shared_Zp_field_element& f2) { + f1.element_ = Shared_Zp_field_element::_multiply(f1.element_, f2.element_); + } + /** + * @brief operator* + */ + friend Shared_Zp_field_element operator*(Shared_Zp_field_element f1, const Shared_Zp_field_element& f2) { + f1 *= f2; + return f1; + } + /** + * @brief operator*= + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend void operator*=(Shared_Zp_field_element& f, const Integer_type& v) { + f.element_ = Shared_Zp_field_element::_multiply(f.element_, _get_value(v)); + } + /** + * @brief operator* + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend Shared_Zp_field_element operator*(Shared_Zp_field_element f, const Integer_type& v) { + f *= v; + return f; + } + /** + * @brief operator* + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic. if signed. + */ + template > + friend Integer_type operator*(const Integer_type& v, Shared_Zp_field_element f) { + f *= v; + return f.element_; + } + + /** + * @brief operator== + */ + friend bool operator==(const Shared_Zp_field_element& f1, const Shared_Zp_field_element& f2) { + return f1.element_ == f2.element_; + } + /** + * @brief operator== + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend bool operator==(const Integer_type& v, const Shared_Zp_field_element& f) { + return Shared_Zp_field_element::_get_value(v) == f.element_; + } + /** + * @brief operator== + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend bool operator==(const Shared_Zp_field_element& f, const Integer_type& v) { + return Shared_Zp_field_element::_get_value(v) == f.element_; + } + /** + * @brief operator!= + */ + friend bool operator!=(const Shared_Zp_field_element& f1, const Shared_Zp_field_element& f2) { return !(f1 == f2); } + /** + * @brief operator!= + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend bool operator!=(const Integer_type& v, const Shared_Zp_field_element& f) { + return !(v == f); + } + /** + * @brief operator!= + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + friend bool operator!=(const Shared_Zp_field_element& f, const Integer_type& v) { + return !(v == f); + } + + /** + * @brief Assign operator. + */ + Shared_Zp_field_element& operator=(Shared_Zp_field_element other) { + std::swap(element_, other.element_); + return *this; + } + /** + * @brief Assign operator. + * + * @tparam Integer_type A native integer type. Should be able to contain the characteristic if signed. + */ + template > + Shared_Zp_field_element& operator=(const Integer_type& value) { + element_ = Shared_Zp_field_element::_get_value(value); + return *this; + } + /** + * @brief Swap operator. + */ + friend void swap(Shared_Zp_field_element& f1, Shared_Zp_field_element& f2) { std::swap(f1.element_, f2.element_); } + + /** + * @brief Casts the element into an unsigned int. + */ + operator unsigned int() const { return element_; } + + /** + * @brief Returns the inverse of the element in the field. + * + * @return The inverse. + */ + Shared_Zp_field_element get_inverse() const { return Shared_Zp_field_element(inverse_[element_]); } + /** + * @brief For interface purposes with multi-fields. Returns the inverse together with the argument. + * + * @param productOfCharacteristics Some value. + * @return Pair whose first element is the inverse and the second element is @p productOfCharacteristics. + */ + std::pair get_partial_inverse( + characteristic_type productOfCharacteristics) const { + return {get_inverse(), productOfCharacteristics}; + } + + /** + * @brief Returns the additive identity of the field. + * + * @return 0. + */ + static Shared_Zp_field_element get_additive_identity() { return Shared_Zp_field_element(); } + /** + * @brief Returns the multiplicative identity of the field. + * + * @return 1. + */ + static Shared_Zp_field_element get_multiplicative_identity() { return Shared_Zp_field_element(1); } + /** + * @brief For interface purposes with multi-fields. Returns the multiplicative identity of the field. + * + * @param productOfCharacteristics Some value. + * @return 1. + */ + static Shared_Zp_field_element get_partial_multiplicative_identity( + [[maybe_unused]] characteristic_type productOfCharacteristics) { + return Shared_Zp_field_element(1); + } + /** + * @brief Returns the current characteristic. + * + * @return The value of the current characteristic. + */ + static characteristic_type get_characteristic() { return characteristic_; } + + // static constexpr bool handles_only_z2() { return false; } + + private: + element_type element_; /**< Field element. */ + static inline characteristic_type characteristic_; /**< Current characteristic of the field. */ + static inline std::vector inverse_; /**< All inverse elements. */ + + static element_type _add(element_type element, element_type v) { + if (UINT_MAX - element < v) { + // automatic unsigned integer overflow behaviour will make it work + element += v; + element -= characteristic_; + return element; + } + + element += v; + if (element >= characteristic_) element -= characteristic_; + + return element; + } + static element_type _substract(element_type element, element_type v) { + if (element < v) { + element += characteristic_; + } + element -= v; + + return element; + } + static element_type _multiply(element_type element, element_type v) { + element_type a = element; + element = 0; + element_type temp_b; + + while (a != 0) { + if (a & 1) { + if (v >= characteristic_ - element) element -= characteristic_; + element += v; + } + a >>= 1; + + temp_b = v; + if (v >= characteristic_ - v) temp_b -= characteristic_; + v += temp_b; + } + + return element; + } + + template > + static constexpr element_type _get_value(Integer_type e) { + if constexpr (std::is_signed_v){ + if (e < -static_cast(characteristic_)) e = e % characteristic_; + if (e < 0) return e += characteristic_; + return e < static_cast(characteristic_) ? e : e % characteristic_; + } else { + return e < characteristic_ ? e : e % characteristic_; + } + } +}; + +} // namespace persistence_fields +} // namespace Gudhi + +#endif // MATRIX_FIELD_ZP_VAR_H_ diff --git a/src/Persistence_matrix/include/gudhi/Persistence_matrix/allocators/cell_constructors.h b/src/Persistence_matrix/include/gudhi/Persistence_matrix/allocators/cell_constructors.h new file mode 100644 index 0000000000..9d3548c932 --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Persistence_matrix/allocators/cell_constructors.h @@ -0,0 +1,137 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2024 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file cell_constructors.h + * @author Hannah Schreiber + * @brief Contains the @ref New_cell_constructor and @ref Pool_cell_constructor structures. + */ + +#ifndef PM_COLUMN_CELL_CONSTRUCTORS_H +#define PM_COLUMN_CELL_CONSTRUCTORS_H + +#include //std::swap + +#include + +namespace Gudhi { +namespace persistence_matrix { + +/** + * @ingroup persistence_matrix + * + * @brief @ref Cell factory. Constructs and destroyes cell pointers with new and delete. + * + * @tparam Cell @ref Cell with the right templates. + */ +template +struct New_cell_constructor +{ + /** + * @brief Default constructor. + */ + New_cell_constructor() {} + + /** + * @brief Constructs a cell with the given cell arguments. + * + * @param u Arguments forwarded to the @ref Cell constructor. + * @return @ref Cell pointer. + */ + template + Cell* construct(U&&... u) const { + return new Cell(std::forward(u)...); + } + + /** + * @brief Destroyes the given cell. + * + * @param cell @ref Cell pointer. + */ + void destroy(Cell* cell) const { delete cell; } + + /** + * @brief Swap operator. + */ + friend void swap(New_cell_constructor& col1, New_cell_constructor& col2) {} +}; + +/** + * @ingroup persistence_matrix + * + * @brief @ref Cell factory. Uses @ref Gudhi::Simple_object_pool, which is based on boost::object_pool, + * to construct and destroy cell pointer. + * + * @tparam Cell @ref Cell with the right templates. + */ +template +struct Pool_cell_constructor +{ + public: + /** + * @brief Default constructor. + * + */ + Pool_cell_constructor() : cellPool_() {} + //TODO: what does happen when the pool is copied? + /** + * @brief Copy constructor. + * + * @param col Factory to copy. + */ + Pool_cell_constructor(const Pool_cell_constructor& col) : cellPool_(col.cellPool_) {} + /** + * @brief Move constructor. + * + * @param col Factory to move. + */ + Pool_cell_constructor(Pool_cell_constructor&& col) : cellPool_(std::move(col.cellPool_)) {} + + /** + * @brief Constructs a cell with the given cell arguments. + * + * @param u Arguments forwarded to the @ref Cell constructor. + * @return @ref Cell pointer. + */ + template + Cell* construct(U&&... u) { + return cellPool_.construct(std::forward(u)...); + } + + /** + * @brief Destroyes the given cell. + * + * @param cell @ref Cell pointer. + */ + void destroy(Cell* cell) { cellPool_.destroy(cell); } + + //TODO: Again, what does it mean to copy the pool? + /** + * @brief Assign operator. + */ + Pool_cell_constructor& operator=(const Pool_cell_constructor& other) { + cellPool_ = other.cellPool_; + return *this; + } + /** + * @brief Swap operator. + */ + friend void swap(Pool_cell_constructor& col1, Pool_cell_constructor& col2) { + std::swap(col1.cellPool_, col2.cellPool_); + } + + private: + Simple_object_pool cellPool_; /**< Cell pool. */ +}; + +} // namespace persistence_matrix +} // namespace Gudhi + +#endif // PM_COLUMN_CELL_CONSTRUCTORS_H diff --git a/src/Persistence_matrix/include/gudhi/Persistence_matrix/base_matrix.h b/src/Persistence_matrix/include/gudhi/Persistence_matrix/base_matrix.h new file mode 100644 index 0000000000..d930b96403 --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Persistence_matrix/base_matrix.h @@ -0,0 +1,762 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file base_matrix.h + * @author Hannah Schreiber + * @brief Contains the @ref Base_matrix class. + */ + +#ifndef PM_BASE_MATRIX_H +#define PM_BASE_MATRIX_H + +#include //print() only +#include +#include //std::swap, std::move & std::exchange + +namespace Gudhi { +namespace persistence_matrix { + +/** + * @class Base_matrix base_matrix.h gudhi/Persistence_matrix/base_matrix.h + * @ingroup persistence_matrix + * + * @brief A @ref basematrix "basic matrix" structure allowing to easily manipulate and access entire columns and rows, + * but not individual cells. + * + * @tparam Master_matrix An instanciation of @ref Matrix from which all types and options are deduced. + */ +template +class Base_matrix : public Master_matrix::template Base_swap_option >, + protected Master_matrix::Matrix_row_access_option +{ + public: + using index = typename Master_matrix::index; /**< Container index type. */ + using dimension_type = typename Master_matrix::dimension_type; /**< Dimension value type. */ + /** + * @brief Field operators class. Necessary only if @ref PersistenceMatrixOptions::is_z2 is false. + */ + using Field_operators = typename Master_matrix::Field_operators; + using Field_element_type = typename Master_matrix::element_type; /**< Type of a field element. */ + using Column_type = typename Master_matrix::Column_type; /**< Column type. */ + using container_type = typename Master_matrix::boundary_type; /**< Type of the column container. */ + using Row_type = typename Master_matrix::Row_type; /**< Row type, + only necessary with row access option. */ + using Cell_constructor = typename Master_matrix::Cell_constructor; /**< Factory of @ref Cell classes. */ + using Column_settings = typename Master_matrix::Column_settings; /**< Structure giving access to the columns to + necessary external classes. */ + + /** + * @brief Constructs an empty matrix. + * + * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all + * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + */ + Base_matrix(Column_settings* colSettings); + /** + * @brief Constructs a matrix from the given ordered columns. The columns are inserted in the given order. + * + * @tparam Container_type Range type for @ref Matrix::cell_rep_type ranges. + * Assumed to have a begin(), end() and size() method. + * @param columns A vector of @ref Matrix::cell_rep_type ranges to construct the columns from. + * The content of the ranges are assumed to be sorted by increasing ID value. + * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all + * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + */ + template + Base_matrix(const std::vector& columns, + Column_settings* colSettings); + /** + * @brief Constructs a new empty matrix and reserves space for the given number of columns. + * + * @param numberOfColumns Number of columns to reserve space for. + * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all + * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + */ + Base_matrix(unsigned int numberOfColumns, Column_settings* colSettings); + /** + * @brief Copy constructor. If @p colSettings is not a null pointer, its value is kept + * instead of the one in the copied matrix. + * + * @param matrixToCopy Matrix to copy. + * @param colSettings Either a pointer to an existing setting structure for the columns or a null pointer. + * The structure should contain all the necessary external classes specifically necessary for the choosen column type, + * such as custom allocators. If null pointer, the pointer stored in @p matrixToCopy is used instead. + */ + Base_matrix(const Base_matrix& matrixToCopy, + Column_settings* colSettings = nullptr); + /** + * @brief Move constructor. + * + * @param other Matrix to move. + */ + Base_matrix(Base_matrix&& other) noexcept; + + /** + * @brief Inserts a new ordered column at the end of the matrix by copying the given range of + * @ref Matrix::cell_rep_type. The content of the range is assumed to be sorted by increasing ID value. + * + * @tparam Container_type Range of @ref Matrix::cell_rep_type. Assumed to have a begin(), end() and size() method. + * @param column Range of @ref Matrix::cell_rep_type from which the column has to be constructed. Assumed to be + * ordered by increasing ID value. + */ + template + void insert_column(const Container_type& column); + /** + * @brief Inserts a new ordered column at the given index by copying the given range of @ref Matrix::cell_rep_type. + * There should not be any other column inserted at that index which was not explicitely removed before. + * The content of the range is assumed to be sorted by increasing ID value. + * Not available when row access is enabled. + * + * @tparam Container_type Range of @ref Matrix::cell_rep_type. Assumed to have a begin(), end() and size() method. + * @param column Range of @ref Matrix::cell_rep_type from which the column has to be constructed. Assumed to be + * ordered by increasing ID value. + * @param columnIndex @ref MatIdx index to which the column has to be inserted. + */ + template + void insert_column(const Container_type& column, index columnIndex); + /** + * @brief Same as @ref insert_column, only for interface purposes. The given dimension is ignored and not stored. + * + * @tparam Boundary_type Range of @ref Matrix::cell_rep_type. Assumed to have a begin(), end() and size() method. + * @param boundary Range of @ref Matrix::cell_rep_type from which the column has to be constructed. Assumed to be + * ordered by increasing ID value. + * @param dim Ignored. + */ + template + void insert_boundary(const Boundary_type& boundary, dimension_type dim = -1); + /** + * @brief Returns the column at the given @ref MatIdx index. + * The type of the column depends on the choosen options, see @ref PersistenceMatrixOptions::column_type. + * + * Note that before returning the column, all column cells can eventually be reordered, if lazy swaps occurred. + * It is therefore recommended to avoid calling @ref get_column between column or row swaps, otherwise the benefits + * of the the lazyness is lost. + * + * @param columnIndex @ref MatIdx index of the column to return. + * @return Reference to the column. + */ + Column_type& get_column(index columnIndex); + /** + * @brief Only available if @ref PersistenceMatrixOptions::has_row_access is true. + * Returns the row at the given @ref rowindex "row index" of the matrix. + * The type of the row depends on the choosen options, see @ref PersistenceMatrixOptions::has_intrusive_rows. + * + * Note that before returning the row, all column cells can eventually be reordered, if lazy swaps occurred. + * It is therefore recommended to avoid calling @ref get_row between column or row swaps, otherwise the benefits + * of the the lazyness is lost. + * + * @param rowIndex @ref rowindex "Row index" of the row to return. + * @return Reference to the row. + */ + Row_type& get_row(index rowIndex); + /** + * @brief Only available if @ref PersistenceMatrixOptions::has_map_column_container is true. Otherwise, see + * @ref remove_last. Erases the given column from the matrix. If the given column index corresponded to the last + * used column index, the "new last column index" will be `columnIndex - 1`, even if a column was never explicitely + * inserted at this index (possible when + * @ref insert_column(const Container_type& column, index columnIndex) "insert_column(column, columnIndex)" was used). + * If the column didn't existed, it will simply be considered as an empty column. + * + * If @ref PersistenceMatrixOptions::has_row_access is also true, the deleted column cells are also automatically + * removed from their respective rows. + * + * @param columnIndex @ref MatIdx index of the column to remove. + */ + void remove_column(index columnIndex); + /** + * @brief Removes the last column from the matrix. The last column is at index \f$ max(ins1, ins2, rem - 1) \f$, + * where: + * - \f$ ins1 \f$ is the index of the last column inserted by + * @ref insert_column(const Container_type& column) "insert_column(column)", + * - \f$ ins2 \f$ is largest index used with + * @ref insert_column(const Container_type& column, index columnIndex) "insert_column(column, columnIndex)", + * - \f$ rem \f$ is the last index just before the last call of @ref remove_column or @ref remove_last. + * + * If \f$ max(ins1, ins2, rem - 1) = rem - 1 \f$ but no column was explicitely inserted at that index (possible + * by the use of + * @ref insert_column(const Container_type& column, index columnIndex) "insert_column(column, columnIndex)"), + * the column is assumed to be an empty column. + * + * See also @ref remove_column. + */ + void remove_last(); + /** + * @brief If @ref PersistenceMatrixOptions::has_row_access and @ref PersistenceMatrixOptions::has_removable_rows + * are true: assumes that the row is empty and removes it. If @ref PersistenceMatrixOptions::has_map_column_container + * and @ref PersistenceMatrixOptions::has_column_and_row_swaps are true: cleans up maps used for the lazy row swaps. + * Otherwise, does nothing. + * + * @warning The removed rows are always assumed to be empty. If it is not the case, the deleted row cells are not + * removed from their columns. And in the case of intrusive rows, this will generate a segmentation fault when + * the column cells are destroyed later. The row access is just meant as a "read only" access to the rows and the + * @ref erase_empty_row method just as a way to specify that a row is empty and can therefore be removed from + * dictionnaries. This allows to avoid testing the emptiness of a row at each column cell removal, what can be + * quite frequent. + * + * @param rowIndex @ref rowindex "Row index" of the empty row. + */ + void erase_empty_row(index rowIndex); + + /** + * @brief Returns the current number of columns in the matrix. + * + * @return The number of columns. + */ + index get_number_of_columns() const; + + /** + * @brief Adds column represented by @p sourceColumn onto the column at @p targetColumnIndex in the matrix. + * + * @tparam Cell_range_or_column_index Either a range of @ref Cell with a begin() and end() method, + * or any integer type. + * @param sourceColumn Either a cell range or the @ref MatIdx index of the column to add. + * @param targetColumnIndex @ref MatIdx index of the target column. + */ + template + void add_to(const Cell_range_or_column_index& sourceColumn, index targetColumnIndex); + /** + * @brief Multiplies the target column with the coefficiant and then adds the source column to it. + * That is: `targetColumn = (targetColumn * coefficient) + sourceColumn`. + * + * @tparam Cell_range_or_column_index Either a range of @ref Cell with a begin() and end() method, + * or any integer type. + * @param sourceColumn Either a cell range or the @ref MatIdx index of the column to add. + * @param coefficient Value to multiply. + * @param targetColumnIndex @ref MatIdx index of the target column. + */ + template + void multiply_target_and_add_to(const Cell_range_or_column_index& sourceColumn, + const Field_element_type& coefficient, + index targetColumnIndex); + /** + * @brief Multiplies the source column with the coefficiant before adding it to the target column. + * That is: `targetColumn += (coefficient * sourceColumn)`. The source column will **not** be modified. + * + * @tparam Cell_range_or_column_index Either a range of @ref Cell with a begin() and end() method, + * or any integer type. + * @param coefficient Value to multiply. + * @param sourceColumn Either a cell range or the @ref MatIdx index of the column to add. + * @param targetColumnIndex @ref MatIdx index of the target column. + */ + template + void multiply_source_and_add_to(const Field_element_type& coefficient, + const Cell_range_or_column_index& sourceColumn, + index targetColumnIndex); + + /** + * @brief Zeroes the cell at the given coordinates. + * + * @param columnIndex @ref MatIdx index of the column of the cell. + * @param rowIndex @ref rowindex "Row index" of the row of the cell. + */ + void zero_cell(index columnIndex, index rowIndex); + /** + * @brief Zeroes the column at the given index. + * + * @param columnIndex @ref MatIdx index of the column to zero. + */ + void zero_column(index columnIndex); + /** + * @brief Indicates if the cell at given coordinates has value zero. + * + * @param columnIndex @ref MatIdx index of the column of the cell. + * @param rowIndex @ref rowindex "Row index" of the row of the cell. + * @return true If the cell has value zero. + * @return false Otherwise. + */ + bool is_zero_cell(index columnIndex, index rowIndex) const; + /** + * @brief Indicates if the column at given index has value zero. + * + * @param columnIndex @ref MatIdx index of the column. + * @return true If the column has value zero. + * @return false Otherwise. + */ + bool is_zero_column(index columnIndex); + + /** + * @brief Resets the matrix to an empty matrix. + * + * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all + * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + */ + void reset(Column_settings* colSettings) { + matrix_.clear(); + nextInsertIndex_ = 0; + colSettings_ = colSettings; + } + + /** + * @brief Assign operator. + */ + Base_matrix& operator=(const Base_matrix& other); + /** + * @brief Swap operator. + */ + friend void swap(Base_matrix& matrix1, Base_matrix& matrix2) { + swap(static_cast >&>(matrix1), + static_cast >&>(matrix2)); + matrix1.matrix_.swap(matrix2.matrix_); + std::swap(matrix1.nextInsertIndex_, matrix2.nextInsertIndex_); + std::swap(matrix1.colSettings_, matrix2.colSettings_); + + if constexpr (Master_matrix::Option_list::has_row_access) { + swap(static_cast(matrix1), + static_cast(matrix2)); + } + } + + void print(); // for debug + + private: + using swap_opt = typename Master_matrix::template Base_swap_option >; + using ra_opt = typename Master_matrix::Matrix_row_access_option; + using matrix_type = typename Master_matrix::column_container_type; + using cell_rep_type = + typename std::conditional + >::type; + + friend swap_opt; // direct access to matrix_ to avoid row reorder. + + matrix_type matrix_; /**< Column container. */ + index nextInsertIndex_; /**< Next unused column index. */ + Column_settings* colSettings_; /**< Cell factory. */ + + template + void _insert(const Container_type& column, index columnIndex, dimension_type dim); + void _orderRowsIfNecessary(); + const Column_type& _get_column(index columnIndex) const; + Column_type& _get_column(index columnIndex); + index _get_real_row_index(index rowIndex) const; + template + void _container_insert(const Container_type& column, index pos, dimension_type dim); + void _container_insert(const Column_type& column, [[maybe_unused]] index pos = 0); +}; + +template +inline Base_matrix::Base_matrix(Column_settings* colSettings) + : swap_opt(), ra_opt(), nextInsertIndex_(0), colSettings_(colSettings) +{} + +template +template +inline Base_matrix::Base_matrix(const std::vector& columns, + Column_settings* colSettings) + : swap_opt(columns.size()), + // not ideal if max row index is much smaller than max column index, does that happen often? + ra_opt(columns.size()), + matrix_(!Master_matrix::Option_list::has_map_column_container && Master_matrix::Option_list::has_row_access + ? 0 + : columns.size()), + nextInsertIndex_(columns.size()), + colSettings_(colSettings) +{ + if constexpr (!Master_matrix::Option_list::has_map_column_container && Master_matrix::Option_list::has_row_access) + matrix_.reserve(columns.size()); + + for (index i = 0; i < columns.size(); i++) { + _container_insert(columns[i], i, columns[i].size() == 0 ? 0 : columns[i].size() - 1); + } +} + +template +inline Base_matrix::Base_matrix(unsigned int numberOfColumns, + Column_settings* colSettings) + : swap_opt(numberOfColumns), + ra_opt(numberOfColumns), + matrix_(!Master_matrix::Option_list::has_map_column_container && Master_matrix::Option_list::has_row_access + ? 0 + : numberOfColumns), + nextInsertIndex_(0), + colSettings_(colSettings) +{ + if constexpr (!Master_matrix::Option_list::has_map_column_container && Master_matrix::Option_list::has_row_access) + matrix_.reserve(numberOfColumns); +} + +template +inline Base_matrix::Base_matrix(const Base_matrix& matrixToCopy, + Column_settings* colSettings) + : swap_opt(static_cast(matrixToCopy)), + ra_opt(static_cast(matrixToCopy)), + nextInsertIndex_(matrixToCopy.nextInsertIndex_), + colSettings_(colSettings == nullptr ? matrixToCopy.colSettings_ : colSettings) +{ + matrix_.reserve(matrixToCopy.matrix_.size()); + for (const auto& cont : matrixToCopy.matrix_){ + if constexpr (Master_matrix::Option_list::has_map_column_container){ + _container_insert(cont.second, cont.first); + } else { + _container_insert(cont); + } + } +} + +template +inline Base_matrix::Base_matrix(Base_matrix&& other) noexcept + : swap_opt(std::move(static_cast(other))), + ra_opt(std::move(static_cast(other))), + matrix_(std::move(other.matrix_)), + nextInsertIndex_(std::exchange(other.nextInsertIndex_, 0)), + colSettings_(std::exchange(other.colSettings_, nullptr)) +{} + +template +template +inline void Base_matrix::insert_column(const Container_type& column) +{ + //TODO: dim not actually stored right now, so either get rid of it or store it again + _insert(column, nextInsertIndex_, column.size() == 0 ? 0 : column.size() - 1); + ++nextInsertIndex_; +} + +template +template +inline void Base_matrix::insert_column(const Container_type& column, index columnIndex) +{ + static_assert(!Master_matrix::Option_list::has_row_access, + "Columns have to be inserted at the end of the matrix when row access is enabled."); + + if (columnIndex >= nextInsertIndex_) nextInsertIndex_ = columnIndex + 1; + //TODO: dim not actually stored right now, so either get rid of it or store it again + _insert(column, columnIndex, column.size() == 0 ? 0 : column.size() - 1); +} + +template +template +inline void Base_matrix::insert_boundary(const Boundary_type& boundary, dimension_type dim) +{ + if (dim == -1) dim = boundary.size() == 0 ? 0 : boundary.size() - 1; + //TODO: dim not actually stored right now, so either get rid of it or store it again + _insert(boundary, nextInsertIndex_++, dim); +} + +template +inline typename Base_matrix::Column_type& Base_matrix::get_column(index columnIndex) +{ + _orderRowsIfNecessary(); + return _get_column(columnIndex); +} + +template +inline typename Base_matrix::Row_type& Base_matrix::get_row(index rowIndex) +{ + static_assert(Master_matrix::Option_list::has_row_access, "Row access has to be enabled for this method."); + + _orderRowsIfNecessary(); + return ra_opt::get_row(rowIndex); +} + +template +inline void Base_matrix::remove_column(index columnIndex) +{ + static_assert(Master_matrix::Option_list::has_map_column_container, + "'remove_column' is not implemented for the chosen options."); + + // assumes that eventual "holes" left at unused indices are considered as empty columns. + if (columnIndex == nextInsertIndex_ - 1) --nextInsertIndex_; + + matrix_.erase(columnIndex); +} + +template +inline void Base_matrix::remove_last() +{ + if (nextInsertIndex_ == 0) return; //empty matrix + --nextInsertIndex_; // assumes that eventual "holes" left at unused indices are considered as empty columns. + + if constexpr (Master_matrix::Option_list::has_map_column_container) { + matrix_.erase(nextInsertIndex_); + } else { + if constexpr (Master_matrix::Option_list::has_row_access) { + GUDHI_CHECK(nextInsertIndex_ == matrix_.size() - 1, + std::logic_error("Base_matrix::remove_last - Indexation problem.")); + matrix_.pop_back(); + } else { + matrix_[nextInsertIndex_].clear(); + } + } +} + +template +inline void Base_matrix::erase_empty_row(index rowIndex) +{ + if constexpr (Master_matrix::Option_list::has_row_access && Master_matrix::Option_list::has_removable_rows) { + ra_opt::erase_empty_row(_get_real_row_index(rowIndex)); + } + if constexpr ((Master_matrix::Option_list::has_column_and_row_swaps || Master_matrix::Option_list::has_vine_update) && + Master_matrix::Option_list::has_map_column_container) { + auto it = swap_opt::indexToRow_.find(rowIndex); + swap_opt::rowToIndex_.erase(it->second); + swap_opt::indexToRow_.erase(it); + } +} + +template +inline typename Base_matrix::index Base_matrix::get_number_of_columns() const +{ + if constexpr (Master_matrix::Option_list::has_map_column_container) { + return matrix_.size(); + } else { + return nextInsertIndex_; // matrix could have been resized much bigger while insert + } +} + +template +template +inline void Base_matrix::add_to(const Cell_range_or_column_index& sourceColumn, + index targetColumnIndex) +{ + if constexpr (std::is_integral_v) { + _get_column(targetColumnIndex) += _get_column(sourceColumn); + } else { + _get_column(targetColumnIndex) += sourceColumn; + } +} + +template +template +inline void Base_matrix::multiply_target_and_add_to(const Cell_range_or_column_index& sourceColumn, + const Field_element_type& coefficient, + index targetColumnIndex) +{ + if constexpr (std::is_integral_v) { + _get_column(targetColumnIndex).multiply_target_and_add(coefficient, _get_column(sourceColumn)); + } else { + _get_column(targetColumnIndex).multiply_target_and_add(coefficient, sourceColumn); + } +} + +template +template +inline void Base_matrix::multiply_source_and_add_to(const Field_element_type& coefficient, + const Cell_range_or_column_index& sourceColumn, + index targetColumnIndex) +{ + if constexpr (std::is_integral_v) { + _get_column(targetColumnIndex).multiply_source_and_add(_get_column(sourceColumn), coefficient); + } else { + _get_column(targetColumnIndex).multiply_source_and_add(sourceColumn, coefficient); + } +} + +template +inline void Base_matrix::zero_cell(index columnIndex, index rowIndex) +{ + _get_column(columnIndex).clear(_get_real_row_index(rowIndex)); +} + +template +inline void Base_matrix::zero_column(index columnIndex) { + _get_column(columnIndex).clear(); +} + +template +inline bool Base_matrix::is_zero_cell(index columnIndex, index rowIndex) const +{ + return !(_get_column(columnIndex).is_non_zero(_get_real_row_index(rowIndex))); +} + +template +inline bool Base_matrix::is_zero_column(index columnIndex) +{ + return _get_column(columnIndex).is_empty(); +} + +template +inline Base_matrix& Base_matrix::operator=(const Base_matrix& other) +{ + swap_opt::operator=(other); + ra_opt::operator=(other); + matrix_.clear(); + nextInsertIndex_ = other.nextInsertIndex_; + colSettings_ = other.colSettings_; + + matrix_.reserve(other.matrix_.size()); + for (const auto& cont : other.matrix_){ + if constexpr (Master_matrix::Option_list::has_map_column_container){ + _container_insert(cont.second, cont.first); + } else { + _container_insert(cont); + } + } + + return *this; +} + +template +inline void Base_matrix::print() +{ + _orderRowsIfNecessary(); + std::cout << "Base_matrix:\n"; + for (index i = 0; i < nextInsertIndex_; ++i) { + const Column_type& col = matrix_[i]; + for (const auto& e : col.get_content(nextInsertIndex_)) { + if (e == 0u) + std::cout << "- "; + else + std::cout << e << " "; + } + std::cout << "\n"; + } + std::cout << "\n"; + if constexpr (Master_matrix::Option_list::has_row_access) { + std::cout << "Row Matrix:\n"; + for (index i = 0; i < nextInsertIndex_; ++i) { + const auto& row = ra_opt::rows_[i]; + for (const auto& cell : row) { + std::cout << cell.get_column_index() << " "; + } + std::cout << "(" << i << ")\n"; + } + std::cout << "\n"; + } +} + +template +template +inline void Base_matrix::_insert(const Container_type& column, index columnIndex, dimension_type dim) +{ + _orderRowsIfNecessary(); + + //resize of containers when necessary: + index pivot = 0; + if (column.begin() != column.end()) { + //first, compute pivot of `column` + if constexpr (Master_matrix::Option_list::is_z2) { + pivot = *std::prev(column.end()); + } else { + pivot = std::prev(column.end())->first; + } + //row container + if constexpr (Master_matrix::Option_list::has_row_access && !Master_matrix::Option_list::has_removable_rows) + if (ra_opt::rows_->size() <= pivot) ra_opt::rows_->resize(pivot + 1); + } + + //row swap map containers + if constexpr (Master_matrix::Option_list::has_map_column_container) { + if constexpr (Master_matrix::Option_list::has_column_and_row_swaps || Master_matrix::Option_list::has_vine_update) { + for (auto id : column) { + index idx; + if constexpr (Master_matrix::Option_list::is_z2) { + idx = id; + } else { + idx = id.first; + } + swap_opt::indexToRow_[idx] = idx; + swap_opt::rowToIndex_[idx] = idx; + } + } + } else { + if constexpr (Master_matrix::Option_list::has_column_and_row_swaps || Master_matrix::Option_list::has_vine_update) { + index size = swap_opt::indexToRow_.size(); + if (size <= pivot) { + for (index i = size; i <= pivot; i++) { + swap_opt::indexToRow_.push_back(i); + swap_opt::rowToIndex_.push_back(i); + } + } + } + //column container + if constexpr (!Master_matrix::Option_list::has_row_access) { + if (matrix_.size() <= columnIndex) { + matrix_.resize(columnIndex + 1); + } + } + } + + _container_insert(column, columnIndex, dim); +} + +template +inline void Base_matrix::_orderRowsIfNecessary() +{ + if constexpr (Master_matrix::Option_list::has_column_and_row_swaps || Master_matrix::Option_list::has_vine_update) { + if (swap_opt::rowSwapped_) swap_opt::_orderRows(); + } +} + +template +inline const typename Base_matrix::Column_type& Base_matrix::_get_column( + index columnIndex) const +{ + if constexpr (Master_matrix::Option_list::has_map_column_container) { + return matrix_.at(columnIndex); + } else { + return matrix_[columnIndex]; + } +} + +template +inline typename Base_matrix::Column_type& Base_matrix::_get_column(index columnIndex) +{ + if constexpr (Master_matrix::Option_list::has_map_column_container) { + return matrix_.at(columnIndex); + } else { + return matrix_[columnIndex]; + } +} + +template +inline typename Base_matrix::index Base_matrix::_get_real_row_index(index rowIndex) const +{ + if constexpr (Master_matrix::Option_list::has_column_and_row_swaps || Master_matrix::Option_list::has_vine_update) { + if constexpr (Master_matrix::Option_list::has_map_column_container) { + return swap_opt::indexToRow_.at(rowIndex); + } else { + return swap_opt::indexToRow_[rowIndex]; + } + } else { + return rowIndex; + } +} + +template +template +inline void Base_matrix::_container_insert(const Container_type& column, index pos, dimension_type dim){ + if constexpr (Master_matrix::Option_list::has_map_column_container) { + if constexpr (Master_matrix::Option_list::has_row_access) { + matrix_.try_emplace(pos, Column_type(pos, column, dim, ra_opt::rows_, colSettings_)); + } else { + matrix_.try_emplace(pos, Column_type(column, dim, colSettings_)); + } + } else { + if constexpr (Master_matrix::Option_list::has_row_access) { + matrix_.emplace_back(pos, column, dim, ra_opt::rows_, colSettings_); + } else { + matrix_[pos] = Column_type(column, dim, colSettings_); + } + } +} + +template +inline void Base_matrix::_container_insert(const Column_type& column, [[maybe_unused]] index pos){ + if constexpr (Master_matrix::Option_list::has_map_column_container) { + if constexpr (Master_matrix::Option_list::has_row_access) { + matrix_.try_emplace(pos, Column_type(column, column.get_column_index(), ra_opt::rows_, colSettings_)); + } else { + matrix_.try_emplace(pos, Column_type(column, colSettings_)); + } + } else { + if constexpr (Master_matrix::Option_list::has_row_access) { + matrix_.emplace_back(column, column.get_column_index(), ra_opt::rows_, colSettings_); + } else { + matrix_.emplace_back(column, colSettings_); + } + } +} + +} // namespace persistence_matrix +} // namespace Gudhi + +#endif // PM_BASE_MATRIX_H diff --git a/src/Persistence_matrix/include/gudhi/Persistence_matrix/base_matrix_with_column_compression.h b/src/Persistence_matrix/include/gudhi/Persistence_matrix/base_matrix_with_column_compression.h new file mode 100644 index 0000000000..95205c5d24 --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Persistence_matrix/base_matrix_with_column_compression.h @@ -0,0 +1,678 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file base_matrix_with_column_compression.h + * @author Hannah Schreiber + * @brief Contains the @ref Base_matrix_with_column_compression class. + */ + +#ifndef PM_BASE_MATRIX_COMPRESSION_H +#define PM_BASE_MATRIX_COMPRESSION_H + +#include //print() only +#include +#include //std::swap, std::move & std::exchange + +#include +#include + +#include + +namespace Gudhi { +namespace persistence_matrix { + +/** + * @class Base_matrix_with_column_compression base_matrix_with_column_compression.h \ + * gudhi/Persistence_matrix/base_matrix_with_column_compression.h + * @ingroup persistence_matrix + * + * @brief A @ref basematrix "base matrix" (also see @ref Base_matrix), but with column compression. That is, all + * identical columns in the matrix are compressed together as the same column. For matrices with a lot of redundant + * columns, this will save a lot of space. Also, any addition made onto a column will be performed at the same time + * on all other identical columns, which is an advantage for the cohomology algorithm for example. + * + * @tparam Master_matrix An instanciation of @ref Matrix from which all types and options are deduced. + */ +template +class Base_matrix_with_column_compression : protected Master_matrix::Matrix_row_access_option +{ + public: + using index = typename Master_matrix::index; /**< Container index type. */ + using dimension_type = typename Master_matrix::dimension_type; /**< Dimension value type. */ + /** + * @brief Field operators class. Necessary only if @ref PersistenceMatrixOptions::is_z2 is false. + */ + using Field_operators = typename Master_matrix::Field_operators; + using Field_element_type = typename Master_matrix::element_type; /**< Field element value type. */ + using Row_type = typename Master_matrix::Row_type; /**< Row type, + only necessary with row access option. */ + using Cell_constructor = typename Master_matrix::Cell_constructor; /**< Factory of @ref Cell classes. */ + using Column_settings = typename Master_matrix::Column_settings; /**< Structure giving access to the columns to + necessary external classes. */ + + /** + * @brief Type for columns. Only one for each "column class" is explicitely constructed. + */ + class Column_type + : public Master_matrix::Column_type, + public boost::intrusive::set_base_hook > + { + public: + using Base = typename Master_matrix::Column_type; + + Column_type(Column_settings* colSettings = nullptr) + : Base(colSettings) {} + template + Column_type(const Container_type& nonZeroRowIndices, Column_settings* colSettings) + : Base(nonZeroRowIndices, colSettings) {} + template + Column_type(index columnIndex, const Container_type& nonZeroRowIndices, Row_container_type& rowContainer, + Column_settings* colSettings) + : Base(columnIndex, nonZeroRowIndices, rowContainer, colSettings) {} + template + Column_type(const Container_type& nonZeroRowIndices, dimension_type dimension, Column_settings* colSettings) + : Base(nonZeroRowIndices, dimension, colSettings) {} + template + Column_type(index columnIndex, const Container_type& nonZeroRowIndices, dimension_type dimension, + Row_container_type& rowContainer, Column_settings* colSettings) + : Base(columnIndex, nonZeroRowIndices, dimension, rowContainer, colSettings) {} + Column_type(const Column_type& column, Column_settings* colSettings = nullptr) + : Base(static_cast(column), colSettings) {} + template + Column_type(const Column_type& column, index columnIndex, Row_container_type& rowContainer, + Column_settings* colSettings = nullptr) + : Base(static_cast(column), columnIndex, rowContainer, colSettings) {} + Column_type(Column_type&& column) noexcept : Base(std::move(static_cast(column))) {} + + //TODO: is it possible to make this work? + // template + // Column_type(U&&... u) : Base(std::forward(u)...) {} + + index get_rep() const { return rep_; } + void set_rep(const index& rep) { rep_ = rep; } + + struct Hasher { + size_t operator()(const Column_type& c) const { return std::hash()(static_cast(c)); } + }; + + private: + index rep_; /**< Index in the union-find of the root of the set representing this column class. */ + }; + + /** + * @brief Constructs an empty matrix. + * + * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all + * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + */ + Base_matrix_with_column_compression(Column_settings* colSettings); + /** + * @brief Constructs a matrix from the given ordered columns. The columns are inserted in the given order. + * If no identical column already existed, a copy of the column is stored. If an identical one existed, no new + * column is constructed and the relationship between the two is registered in an union-find structure. + * + * @tparam Container_type Range type for @ref Matrix::cell_rep_type ranges. + * Assumed to have a begin(), end() and size() method. + * @param columns A vector of @ref Matrix::cell_rep_type ranges to construct the columns from. + * The content of the ranges are assumed to be sorted by increasing ID value. + * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all + * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + */ + template + Base_matrix_with_column_compression(const std::vector& columns, + Column_settings* colSettings); + /** + * @brief Constructs a new empty matrix and reserves space for the given number of columns. + * + * @param numberOfColumns Number of columns to reserve space for. + * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all + * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + */ + Base_matrix_with_column_compression(unsigned int numberOfColumns, + Column_settings* colSettings); + /** + * @brief Copy constructor. If @p colSettings is not a null pointer, its value is kept + * instead of the one in the copied matrix. + * + * @param matrixToCopy Matrix to copy. + * @param colSettings Either a pointer to an existing setting structure for the columns or a null pointer. + * The structure should contain all the necessary external classes specifically necessary for the choosen column type, + * such as custom allocators. If null pointer, the pointer stored in @p matrixToCopy is used instead. + */ + Base_matrix_with_column_compression(const Base_matrix_with_column_compression& matrixToCopy, + Column_settings* colSettings = nullptr); + /** + * @brief Move constructor. + * + * @param other Matrix to move. + */ + Base_matrix_with_column_compression(Base_matrix_with_column_compression&& other) noexcept; + /** + * @brief Destructor. + */ + ~Base_matrix_with_column_compression(); + + /** + * @brief Inserts a new ordered column at the end of the matrix by copying the given range of + * @ref Matrix::cell_rep_type. The content of the range is assumed to be sorted by increasing ID value. + * + * @tparam Container_type Range of @ref Matrix::cell_rep_type. Assumed to have a begin(), end() and size() method. + * @param column Range of @ref Matrix::cell_rep_type from which the column has to be constructed. Assumed to be + * ordered by increasing ID value. + */ + template + void insert_column(const Container_type& column); + /** + * @brief Same as @ref insert_column, only for interface purposes. The given dimension is ignored and not stored. + * + * @tparam Boundary_type Range of @ref Matrix::cell_rep_type. Assumed to have a begin(), end() and size() method. + * @param boundary Range of @ref Matrix::cell_rep_type from which the column has to be constructed. Assumed to be + * ordered by increasing ID value. + * @param dim Ignored. + */ + template + void insert_boundary(const Boundary_type& boundary, dimension_type dim = -1); + /** + * @brief Returns the column at the given @ref MatIdx index. + * The type of the column depends on the choosen options, see @ref PersistenceMatrixOptions::column_type. + * + * Remark: the method it-self is not const, because of the path compression optimization of the union-find structure, + * when a column is looked up. + * + * @param columnIndex @ref MatIdx index of the column to return. + * @return Const reference to the column. + */ + const Column_type& get_column(index columnIndex); + /** + * @brief Only available if @ref PersistenceMatrixOptions::has_row_access is true. + * Returns the row at the given @ref rowindex "row index" of the compressed matrix. + * The type of the row depends on the choosen options, see @ref PersistenceMatrixOptions::has_intrusive_rows. + * Note that the row will be from the compressed matrix, that is, the one with only unique columns. + * + * @param rowIndex @ref rowindex "Row index" of the row to return. + * @return Const reference to the row. + */ + const Row_type& get_row(index rowIndex) const; + /** + * @brief If @ref PersistenceMatrixOptions::has_row_access and @ref PersistenceMatrixOptions::has_removable_rows + * are true: assumes that the row is empty and removes it. Otherwise, does nothing. + * + * @warning The removed rows are always assumed to be empty. If it is not the case, the deleted row cells are not + * removed from their columns. And in the case of intrusive rows, this will generate a segmentation fault when + * the column cells are destroyed later. The row access is just meant as a "read only" access to the rows and the + * @ref erase_empty_row method just as a way to specify that a row is empty and can therefore be removed from + * dictionnaries. This allows to avoid testing the emptiness of a row at each column cell removal, what can be + * quite frequent. + * + * @param rowIndex @ref rowindex "Row index" of the empty row. + */ + void erase_empty_row(index rowIndex); + + /** + * @brief Returns the current number of columns in the matrix, counting also the redundant columns. + * + * @return The number of columns. + */ + index get_number_of_columns() const; + + /** + * @brief Adds column represented by @p sourceColumn onto the column at @p targetColumnIndex in the matrix. + * + * The representatives of redundant columns are summed together, which means that + * all column compressed together with the target column are affected by the change, not only the target. + * + * @tparam Cell_range_or_column_index Either a range of @ref Cell with a begin() and end() method, + * or any integer type. + * @param sourceColumn Either a cell range or the @ref MatIdx index of the column to add. + * @param targetColumnIndex @ref MatIdx index of the target column. + */ + template + void add_to(const Cell_range_or_column_index& sourceColumn, index targetColumnIndex); + /** + * @brief Multiplies the target column with the coefficiant and then adds the source column to it. + * That is: `targetColumn = (targetColumn * coefficient) + sourceColumn`. + * + * The representatives of redundant columns are summed together, which means that + * all column compressed together with the target column are affected by the change, not only the target. + * + * @tparam Cell_range_or_column_index Either a range of @ref Cell with a begin() and end() method, + * or any integer type. + * @param sourceColumn Either a @ref Cell range or the @ref MatIdx index of the column to add. + * @param coefficient Value to multiply. + * @param targetColumnIndex @ref MatIdx index of the target column. + */ + template + void multiply_target_and_add_to(const Cell_range_or_column_index& sourceColumn, + const Field_element_type& coefficient, + index targetColumnIndex); + /** + * @brief Multiplies the source column with the coefficiant before adding it to the target column. + * That is: `targetColumn += (coefficient * sourceColumn)`. The source column will **not** be modified. + * + * The representatives of redundant columns are summed together, which means that + * all column compressed together with the target column are affected by the change, not only the target. + * + * @tparam Cell_range_or_column_index Either a range of @ref Cell with a begin() and end() method, + * or any integer type. + * @param coefficient Value to multiply. + * @param sourceColumn Either a @ref Cell range or the @ref MatIdx index of the column to add. + * @param targetColumnIndex @ref MatIdx index of the target column. + */ + template + void multiply_source_and_add_to(const Field_element_type& coefficient, + const Cell_range_or_column_index& sourceColumn, + index targetColumnIndex); + + /** + * @brief Indicates if the cell at given coordinates has value zero. + * + * @param columnIndex @ref MatIdx index of the column of the cell. + * @param rowIndex @ref rowindex "Row index" of the row of the cell. + * @return true If the cell has value zero. + * @return false Otherwise. + */ + bool is_zero_cell(index columnIndex, index rowIndex); + /** + * @brief Indicates if the column at given index has value zero. + * + * @param columnIndex @ref MatIdx index of the column. + * @return true If the column has value zero. + * @return false Otherwise. + */ + bool is_zero_column(index columnIndex); + + /** + * @brief Resets the matrix to an empty matrix. + * + * @param operators Pointer to the field operators. + * @param cellConstructor Pointer to the cell factory. + */ + void reset(Column_settings* colSettings) { + columnToRep_.clear_and_dispose(delete_disposer()); + columnClasses_ = boost::disjoint_sets_with_storage<>(); + repToColumn_.clear(); + nextColumnIndex_ = 0; + colSettings_ = colSettings; + } + + /** + * @brief Assign operator. + */ + Base_matrix_with_column_compression& operator=(const Base_matrix_with_column_compression& other); + /** + * @brief Swap operator. + */ + friend void swap(Base_matrix_with_column_compression& matrix1, Base_matrix_with_column_compression& matrix2) { + matrix1.columnToRep_.swap(matrix2.columnToRep_); + std::swap(matrix1.columnClasses_, matrix2.columnClasses_); + matrix1.repToColumn_.swap(matrix2.repToColumn_); // be careful when columnPool_ becomes not static + std::swap(matrix1.nextColumnIndex_, matrix2.nextColumnIndex_); + std::swap(matrix1.colSettings_, matrix2.colSettings_); + + if constexpr (Master_matrix::Option_list::has_row_access) { + swap(static_cast(matrix1), + static_cast(matrix2)); + } + } + + void print(); // for debug + + private: + /** + * @brief The disposer object function for boost intrusive container + */ + struct delete_disposer { + void operator()(Column_type* delete_this) { columnPool_.destroy(delete_this); } + }; + + using ra_opt = typename Master_matrix::Matrix_row_access_option; + using col_dict_type = boost::intrusive::set >; + + col_dict_type columnToRep_; /**< Map from a column to the index of its representative. */ + boost::disjoint_sets_with_storage<> columnClasses_; /**< Union-find structure, + where two columns in the same set are identical. */ + std::vector repToColumn_; /**< Map from the representative index to + the representative Column. */ + index nextColumnIndex_; /**< Next unused column index. */ + Column_settings* colSettings_; /**< Cell factory. */ + /** + * @brief Column factory. + * @warning As the member is static, they can eventually be problems if the matrix is duplicated in several threads. + * If this become necessary, the static should be removed in the future. + */ + inline static Simple_object_pool columnPool_; + inline static const Column_type empty_column_; /**< Representative for empty columns. */ + + void _insert_column(index columnIndex); + void _insert_double_column(index columnIndex, typename col_dict_type::iterator& doubleIt); +}; + +template +inline Base_matrix_with_column_compression::Base_matrix_with_column_compression( + Column_settings* colSettings) + : ra_opt(), nextColumnIndex_(0), colSettings_(colSettings) +{} + +template +template +inline Base_matrix_with_column_compression::Base_matrix_with_column_compression( + const std::vector& columns, Column_settings* colSettings) + : ra_opt(columns.size()), + columnClasses_(columns.size()), + repToColumn_(columns.size(), nullptr), + nextColumnIndex_(0), + colSettings_(colSettings) +{ + for (const Container_type& c : columns) { + insert_column(c); + } +} + +template +inline Base_matrix_with_column_compression::Base_matrix_with_column_compression( + unsigned int numberOfColumns, Column_settings* colSettings) + : ra_opt(numberOfColumns), + columnClasses_(numberOfColumns), + repToColumn_(numberOfColumns, nullptr), + nextColumnIndex_(0), + colSettings_(colSettings) +{} + +template +inline Base_matrix_with_column_compression::Base_matrix_with_column_compression( + const Base_matrix_with_column_compression& matrixToCopy, Column_settings* colSettings) + : ra_opt(static_cast(matrixToCopy)), + columnClasses_(matrixToCopy.columnClasses_), + repToColumn_(matrixToCopy.repToColumn_.size(), nullptr), + nextColumnIndex_(0), + colSettings_(colSettings == nullptr ? matrixToCopy.colSettings_ : colSettings) +{ + for (const Column_type* col : matrixToCopy.repToColumn_) { + if (col != nullptr) { + if constexpr (Master_matrix::Option_list::has_row_access) { + repToColumn_[nextColumnIndex_] = + columnPool_.construct(*col, col->get_column_index(), ra_opt::rows_, colSettings_); + } else { + repToColumn_[nextColumnIndex_] = columnPool_.construct(*col, colSettings_); + } + columnToRep_.insert(columnToRep_.end(), *repToColumn_[nextColumnIndex_]); + repToColumn_[nextColumnIndex_]->set_rep(nextColumnIndex_); + } + nextColumnIndex_++; + } +} + +template +inline Base_matrix_with_column_compression::Base_matrix_with_column_compression( + Base_matrix_with_column_compression&& other) noexcept + : ra_opt(std::move(static_cast(other))), + columnToRep_(std::move(other.columnToRep_)), + columnClasses_(std::move(other.columnClasses_)), + repToColumn_(std::move(other.repToColumn_)), + nextColumnIndex_(std::exchange(other.nextColumnIndex_, 0)), + colSettings_(std::exchange(other.colSettings_, nullptr)) +{} + +template +inline Base_matrix_with_column_compression::~Base_matrix_with_column_compression() +{ + columnToRep_.clear_and_dispose(delete_disposer()); +} + +template +template +inline void Base_matrix_with_column_compression::insert_column(const Container_type& column) +{ + insert_boundary(column); +} + +template +template +inline void Base_matrix_with_column_compression::insert_boundary(const Boundary_type& boundary, + dimension_type dim) +{ + // handles a dimension which is not actually stored. + // TODO: verify if this is not a problem for the cohomology algorithm and if yes, + // change how Column_dimension_option is choosen. + if (dim == -1) dim = boundary.size() == 0 ? 0 : boundary.size() - 1; + + if constexpr (Master_matrix::Option_list::has_row_access && !Master_matrix::Option_list::has_removable_rows) { + if (boundary.begin() != boundary.end()) { + index pivot; + if constexpr (Master_matrix::Option_list::is_z2) { + pivot = *std::prev(boundary.end()); + } else { + pivot = std::prev(boundary.end())->first; + } + if (ra_opt::rows_->size() <= pivot) ra_opt::rows_->resize(pivot + 1); + } + } + + if (repToColumn_.size() == nextColumnIndex_) { + // could perhaps be avoided, if find_set returns something special when it does not find + columnClasses_.link(nextColumnIndex_, nextColumnIndex_); + if constexpr (Master_matrix::Option_list::has_row_access) { + repToColumn_.push_back( + columnPool_.construct(nextColumnIndex_, boundary, dim, ra_opt::rows_, colSettings_)); + } else { + repToColumn_.push_back(columnPool_.construct(boundary, dim, colSettings_)); + } + } else { + if constexpr (Master_matrix::Option_list::has_row_access) { + repToColumn_[nextColumnIndex_] = + columnPool_.construct(nextColumnIndex_, boundary, dim, ra_opt::rows_, colSettings_); + } else { + repToColumn_[nextColumnIndex_] = columnPool_.construct(boundary, dim, colSettings_); + } + } + _insert_column(nextColumnIndex_); + + nextColumnIndex_++; +} + +template +inline const typename Base_matrix_with_column_compression::Column_type& +Base_matrix_with_column_compression::get_column(index columnIndex) +{ + auto col = repToColumn_[columnClasses_.find_set(columnIndex)]; + if (col == nullptr) return empty_column_; + return *col; +} + +template +inline const typename Base_matrix_with_column_compression::Row_type& +Base_matrix_with_column_compression::get_row(index rowIndex) const +{ + static_assert(Master_matrix::Option_list::has_row_access, "Row access has to be enabled for this method."); + + return ra_opt::get_row(rowIndex); +} + +template +inline void Base_matrix_with_column_compression::erase_empty_row(index rowIndex) +{ + if constexpr (Master_matrix::Option_list::has_row_access && Master_matrix::Option_list::has_removable_rows) { + ra_opt::erase_empty_row(rowIndex); + } +} + +template +inline typename Base_matrix_with_column_compression::index +Base_matrix_with_column_compression::get_number_of_columns() const +{ + return nextColumnIndex_; +} + +template +template +inline void Base_matrix_with_column_compression::add_to(const Cell_range_or_column_index& sourceColumn, + index targetColumnIndex) +{ + // handle case where targetRep == sourceRep? + index targetRep = columnClasses_.find_set(targetColumnIndex); + Column_type& target = *repToColumn_[targetRep]; + columnToRep_.erase(target); + if constexpr (std::is_integral_v) { + target += get_column(sourceColumn); + } else { + target += sourceColumn; + } + _insert_column(targetRep); +} + +template +template +inline void Base_matrix_with_column_compression::multiply_target_and_add_to( + const Cell_range_or_column_index& sourceColumn, const Field_element_type& coefficient, index targetColumnIndex) +{ + // handle case where targetRep == sourceRep? + index targetRep = columnClasses_.find_set(targetColumnIndex); + Column_type& target = *repToColumn_[targetRep]; + columnToRep_.erase(target); + if constexpr (std::is_integral_v) { + target.multiply_target_and_add(coefficient, get_column(sourceColumn)); + } else { + target.multiply_target_and_add(coefficient, sourceColumn); + } + _insert_column(targetRep); +} + +template +template +inline void Base_matrix_with_column_compression::multiply_source_and_add_to( + const Field_element_type& coefficient, const Cell_range_or_column_index& sourceColumn, index targetColumnIndex) +{ + // handle case where targetRep == sourceRep? + index targetRep = columnClasses_.find_set(targetColumnIndex); + Column_type& target = *repToColumn_[targetRep]; + columnToRep_.erase(target); + if constexpr (std::is_integral_v) { + target.multiply_source_and_add(get_column(sourceColumn), coefficient); + } else { + target.multiply_source_and_add(sourceColumn, coefficient); + } + _insert_column(targetRep); +} + +template +inline bool Base_matrix_with_column_compression::is_zero_cell(index columnIndex, index rowIndex) +{ + auto col = repToColumn_[columnClasses_.find_set(columnIndex)]; + if (col == nullptr) return true; + return !col->is_non_zero(rowIndex); +} + +template +inline bool Base_matrix_with_column_compression::is_zero_column(index columnIndex) +{ + auto col = repToColumn_[columnClasses_.find_set(columnIndex)]; + if (col == nullptr) return true; + return col->is_empty(); +} + +template +inline Base_matrix_with_column_compression& +Base_matrix_with_column_compression::operator=(const Base_matrix_with_column_compression& other) +{ + for (auto col : repToColumn_) { + if (col != nullptr) { + columnPool_.destroy(col); + col = nullptr; + } + } + ra_opt::operator=(other); + columnClasses_ = other.columnClasses_; + columnToRep_.reserve(other.columnToRep_.size()); + repToColumn_.resize(other.repToColumn_.size(), nullptr); + nextColumnIndex_ = 0; + colSettings_ = other.colSettings_; + for (const Column_type* col : other.repToColumn_) { + if constexpr (Master_matrix::Option_list::has_row_access) { + repToColumn_[nextColumnIndex_] = + columnPool_.construct(*col, col->get_column_index(), ra_opt::rows_, colSettings_); + } else { + repToColumn_[nextColumnIndex_] = columnPool_.construct(*col, colSettings_); + } + columnToRep_.insert(columnToRep_.end(), *repToColumn_[nextColumnIndex_]); + repToColumn_[nextColumnIndex_]->set_rep(nextColumnIndex_); + nextColumnIndex_++; + } + return *this; +} + +template +inline void Base_matrix_with_column_compression::print() +{ + std::cout << "Compressed_matrix:\n"; + for (Column_type& col : columnToRep_) { + for (auto e : col->get_content(nextColumnIndex_)) { + if (e == 0u) + std::cout << "- "; + else + std::cout << e << " "; + } + std::cout << "("; + for (index i = 0; i < nextColumnIndex_; ++i) { + if (columnClasses_.find_set(i) == col.get_rep()) std::cout << i << " "; + } + std::cout << ")\n"; + } + std::cout << "\n"; + std::cout << "Row Matrix:\n"; + for (index i = 0; i < ra_opt::rows_->size(); ++i) { + const Row_type& row = ra_opt::rows_[i]; + for (const auto& cell : row) { + std::cout << cell.get_column_index() << " "; + } + std::cout << "(" << i << ")\n"; + } + std::cout << "\n"; +} + +template +inline void Base_matrix_with_column_compression::_insert_column(index columnIndex) +{ + Column_type& col = *repToColumn_[columnIndex]; + + if (col.is_empty()) { + columnPool_.destroy(&col); // delete curr_col; + repToColumn_[columnIndex] = nullptr; + return; + } + + col.set_rep(columnIndex); + auto res = columnToRep_.insert(col); + if (res.first->get_rep() != columnIndex) { //if true, then redundant column + _insert_double_column(columnIndex, res.first); + } +} + +template +inline void Base_matrix_with_column_compression::_insert_double_column( + index columnIndex, typename col_dict_type::iterator& doubleIt) +{ + index doubleRep = doubleIt->get_rep(); + columnClasses_.link(columnIndex, doubleRep); // both should be representatives + index newRep = columnClasses_.find_set(columnIndex); + + columnPool_.destroy(repToColumn_[columnIndex]); + repToColumn_[columnIndex] = nullptr; + + if (newRep == columnIndex) { + std::swap(repToColumn_[doubleRep], repToColumn_[columnIndex]); + doubleIt->set_rep(columnIndex); + } +} + +} // namespace persistence_matrix +} // namespace Gudhi + +#endif // PM_BASE_MATRIX_COMPRESSION_H diff --git a/src/Persistence_matrix/include/gudhi/Persistence_matrix/base_pairing.h b/src/Persistence_matrix/include/gudhi/Persistence_matrix/base_pairing.h new file mode 100644 index 0000000000..8cda78dc62 --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Persistence_matrix/base_pairing.h @@ -0,0 +1,232 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file base_pairing.h + * @author Hannah Schreiber + * @brief Contains the @ref Base_pairing class and @ref Dummy_base_pairing structure. + */ + +#ifndef PM_BASE_PAIRING_H +#define PM_BASE_PAIRING_H + +#include //std::swap & std::move +#include +#include +#include + +namespace Gudhi { +namespace persistence_matrix { + +/** + * @ingroup persistence_matrix + * + * @brief Empty structure. + * Inheritated instead of @ref Base_pairing, when the computation of the barcode was not enabled or if the pairing + * is already managed by the vine update classes. + */ +struct Dummy_base_pairing { + friend void swap([[maybe_unused]] Dummy_base_pairing& d1, [[maybe_unused]] Dummy_base_pairing& d2) {} +}; + +/** + * @class Base_pairing base_pairing.h gudhi/Persistence_matrix/base_pairing.h + * @ingroup persistence_matrix + * + * @brief Class managing the barcode for @ref Boundary_matrix if the option was enabled. + * + * @tparam Master_matrix An instanciation of @ref Matrix from which all types and options are deduced. + */ +template +class Base_pairing +{ + public: + using Bar = typename Master_matrix::Bar; /**< Bar type. */ + using barcode_type = typename Master_matrix::barcode_type; /**< Barcode type. */ + using matrix_type = typename Master_matrix::column_container_type; /**< Column container type. */ + using index = typename Master_matrix::index; /**< Container index type. */ + using dimension_type = typename Master_matrix::dimension_type; /**< Dimension value type. */ + + /** + * @brief Default constructor. + */ + Base_pairing(); + /** + * @brief Copy constructor. + * + * @param matrixToCopy Matrix to copy. + */ + Base_pairing(const Base_pairing& matrixToCopy); + /** + * @brief Move constructor. + * + * @param other Matrix to move. + */ + Base_pairing(Base_pairing&& other) noexcept; + + /** + * @brief Reduces the matrix stored in @ref Boundary_matrix and computes the corresponding barcode. + * + * @warning The barcode will not be recomputed if the matrix is modified later after calling this method + * for the first time. So call it only once the matrix is finalized. This behaviour could be changed in the future, + * if the need is mentioned. + * + * @return Const reference to the barcode. + */ + const barcode_type& get_current_barcode(); + + /** + * @brief Assign operator. + */ + Base_pairing& operator=(Base_pairing other); + /** + * @brief Swap operator. + */ + friend void swap(Base_pairing& pairing1, Base_pairing& pairing2) { + pairing1.barcode_.swap(pairing2.barcode_); + pairing1.deathToBar_.swap(pairing2.deathToBar_); + std::swap(pairing1.isReduced_, pairing2.isReduced_); + } + + protected: + using pos_index = typename Master_matrix::pos_index; + using dictionnary_type = typename Master_matrix::bar_dictionnary_type; + using base_matrix = typename Master_matrix::Boundary_matrix_type; + + barcode_type barcode_; /**< Bar container. */ + dictionnary_type deathToBar_; /**< Map from death index to bar index. */ + bool isReduced_; /**< True if `_reduce()` was called. */ + + void _reduce(); + void _remove_last(pos_index columnIndex); + + //access to inheritating Boundary_matrix class + constexpr base_matrix* _matrix() { return static_cast(this); } + constexpr const base_matrix* _matrix() const { return static_cast(this); } +}; + +template +inline Base_pairing::Base_pairing() : isReduced_(false) +{} + +template +inline Base_pairing::Base_pairing(const Base_pairing& matrixToCopy) + : barcode_(matrixToCopy.barcode_), deathToBar_(matrixToCopy.deathToBar_), isReduced_(matrixToCopy.isReduced_) +{} + +template +inline Base_pairing::Base_pairing(Base_pairing&& other) noexcept + : barcode_(std::move(other.barcode_)), + deathToBar_(std::move(other.deathToBar_)), + isReduced_(std::move(other.isReduced_)) +{} + +template +inline const typename Base_pairing::barcode_type& Base_pairing::get_current_barcode() +{ + if (!isReduced_) _reduce(); + return barcode_; +} + +template +inline void Base_pairing::_reduce() +{ + using id_index = typename Master_matrix::index; + std::unordered_map pivotsToColumn(_matrix()->get_number_of_columns()); + + auto dim = _matrix()->get_max_dimension(); + std::vector > columnsByDim(dim + 1); + for (unsigned int i = 0; i < _matrix()->get_number_of_columns(); i++) { + columnsByDim[dim - _matrix()->get_column_dimension(i)].push_back(i); + } + + for (const auto& cols : columnsByDim) { + for (index i : cols) { + auto& curr = _matrix()->get_column(i); + if (curr.is_empty()) { + if (pivotsToColumn.find(i) == pivotsToColumn.end()) { + barcode_.emplace_back(dim, i, -1); + } + } else { + id_index pivot = curr.get_pivot(); + + while (pivot != static_cast(-1) && pivotsToColumn.find(pivot) != pivotsToColumn.end()) { + if constexpr (Master_matrix::Option_list::is_z2) { + curr += _matrix()->get_column(pivotsToColumn.at(pivot)); + } else { + auto& toadd = _matrix()->get_column(pivotsToColumn.at(pivot)); + typename Master_matrix::element_type coef = toadd.get_pivot_value(); + auto& operators = _matrix()->colSettings_->operators; + coef = operators.get_inverse(coef); + operators.multiply_inplace(coef, operators.get_characteristic() - curr.get_pivot_value()); + curr.multiply_source_and_add(toadd, coef); + } + + pivot = curr.get_pivot(); + } + + if (pivot != static_cast(-1)) { + pivotsToColumn.emplace(pivot, i); + _matrix()->get_column(pivot).clear(); + barcode_.emplace_back(dim - 1, pivot, i); + } else { + curr.clear(); + barcode_.emplace_back(dim, i, -1); + } + } + } + --dim; + } + + if constexpr (Master_matrix::Option_list::has_removable_columns) { + // sort barcode by birth such that a removal is trivial + std::sort(barcode_.begin(), barcode_.end(), [](const Bar& b1, const Bar& b2) { return b1.birth < b2.birth; }); + // map can only be constructed once barcode is sorted + for (index i = 0; i < barcode_.size(); ++i) { + auto d = barcode_[i].death; + if (d != static_cast(-1)) { + deathToBar_.emplace(d, i); + } + } + } + + isReduced_ = true; +} + +template +inline void Base_pairing::_remove_last(pos_index columnIndex) +{ + static_assert(Master_matrix::Option_list::has_removable_columns, "remove_last not available."); + + if (isReduced_) { + auto it = deathToBar_.find(columnIndex); + + if (it == deathToBar_.end()) { // birth + barcode_.pop_back(); // sorted by birth and columnIndex has to be the heighest one + } else { // death + barcode_[it->second].death = -1; + deathToBar_.erase(it); + }; + } +} + +template +inline Base_pairing& Base_pairing::operator=(Base_pairing other) +{ + barcode_.swap(other.barcode_); + deathToBar_.swap(other.deathToBar_); + std::swap(isReduced_, other.isReduced_); + return *this; +} + +} // namespace persistence_matrix +} // namespace Gudhi + +#endif // PM_BASE_PAIRING_H diff --git a/src/Persistence_matrix/include/gudhi/Persistence_matrix/base_swap.h b/src/Persistence_matrix/include/gudhi/Persistence_matrix/base_swap.h new file mode 100644 index 0000000000..2b2ae5cda9 --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Persistence_matrix/base_swap.h @@ -0,0 +1,209 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022-23 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file base_swap.h + * @author Hannah Schreiber + * @brief Contains the @ref Base_swap class and @ref Dummy_base_swap structure. + */ + +#ifndef PM_BASE_SWAP_H +#define PM_BASE_SWAP_H + +#include //std::swap, std::move & std::exchange +#include //std::max + +namespace Gudhi { +namespace persistence_matrix { + +/** + * @ingroup persistence_matrix + * + * @brief Empty structure. + * Inheritated instead of @ref Base_swap, when the column and row swaps are not enabled. + */ +struct Dummy_base_swap { + friend void swap([[maybe_unused]] Dummy_base_swap& d1, [[maybe_unused]] Dummy_base_swap& d2) {} + + Dummy_base_swap([[maybe_unused]] unsigned int numberOfColumns = 0) {} +}; + +/** + * @class Base_swap base_swap.h gudhi/Persistence_matrix/base_swap.h + * @ingroup persistence_matrix + * + * @brief Class managing the column and row swaps in @ref Base_matrix and @ref Boundary_matrix. + * + * @tparam Master_matrix An instanciation of @ref Matrix from which all types and options are deduced. + * @tparam Base_matrix Either @ref Base_matrix or @ref Boundary_matrix. + */ +template +class Base_swap { + public: + using matrix_type = typename Master_matrix::column_container_type; /**< Column container type. */ + using index = typename Master_matrix::index; /**< Container index type. */ + using id_index = typename Master_matrix::id_index; /**< @ref IDIdx index type. */ + + /** + * @brief Default constructor. + */ + Base_swap(); + /** + * @brief As default constructor, but reserves spaces for @p numberOfColumns columns. + * + * @param numberOfColumns Number of columns to reserve space for. + */ + Base_swap(unsigned int numberOfColumns); + /** + * @brief Copy constructor. + * + * @param matrixToCopy Matrix to copy. + */ + Base_swap(const Base_swap& matrixToCopy); + /** + * @brief Move constructor. + * + * @param other Matrix to move. + */ + Base_swap(Base_swap&& other) noexcept; + + /** + * @brief Swaps the two columns at given indices in the column container. Does not updates the column index value, + * potentially stored in the cells. This will be done when calling `_orderRows()`. + * + * @param columnIndex1 First @ref MatIdx column index. + * @param columnIndex2 Second @ref MatIdx column index. + */ + void swap_columns(index columnIndex1, index columnIndex2); + /** + * @brief Swaps the two rows at the given indices, but in a lazy manner. That is, the swap is registered but not + * executed. The reordering will be done when calling `_orderRows()`. + * + * @param rowIndex1 First row index. + * @param rowIndex2 Second row index. + */ + void swap_rows(id_index rowIndex1, id_index rowIndex2); + + /** + * @brief Assign operator. + */ + Base_swap& operator=(Base_swap other); + /** + * @brief Swap operator. + */ + friend void swap(Base_swap& base1, Base_swap& base2) { + base1.indexToRow_.swap(base2.indexToRow_); + base1.rowToIndex_.swap(base2.rowToIndex_); + std::swap(base1.rowSwapped_, base2.rowSwapped_); + } + + protected: + using index_dictionnary_type = typename Master_matrix::template dictionnary_type; + using row_dictionnary_type = typename Master_matrix::template dictionnary_type; + + index_dictionnary_type indexToRow_; /**< Map from row index to actual index in row container. */ + row_dictionnary_type rowToIndex_; /**< Map from index in row container to "public" row index. */ + bool rowSwapped_; /**< True if any rows were swapped since last call to `_orderRows()`. */ + + void _orderRows(); + + //access to inheritating matrix class + constexpr Base_matrix* _matrix() { return static_cast(this); } + constexpr const Base_matrix* _matrix() const { return static_cast(this); } +}; + +template +inline Base_swap::Base_swap() : rowSwapped_(false) {} + +template +inline Base_swap::Base_swap(unsigned int numberOfColumns) + : indexToRow_(numberOfColumns), rowToIndex_(numberOfColumns), rowSwapped_(false) { + for (index i = 0; i < numberOfColumns; i++) { + indexToRow_[i] = i; + rowToIndex_[i] = i; + } +} + +template +inline Base_swap::Base_swap(const Base_swap& matrixToCopy) + : indexToRow_(matrixToCopy.indexToRow_), + rowToIndex_(matrixToCopy.rowToIndex_), + rowSwapped_(matrixToCopy.rowSwapped_) {} + +template +inline Base_swap::Base_swap(Base_swap&& other) noexcept + : indexToRow_(std::move(other.indexToRow_)), + rowToIndex_(std::move(other.rowToIndex_)), + rowSwapped_(std::exchange(other.rowSwapped_, 0)) {} + +template +inline void Base_swap::swap_columns(index columnIndex1, index columnIndex2) { + swap(_matrix()->matrix_.at(columnIndex1), _matrix()->matrix_.at(columnIndex2)); + if constexpr (Master_matrix::Option_list::has_row_access) rowSwapped_ = true; //to update column index in cells. +} + +template +inline void Base_swap::swap_rows(id_index rowIndex1, id_index rowIndex2) { + rowSwapped_ = true; + + if constexpr (Master_matrix::Option_list::has_map_column_container) { + auto it1 = indexToRow_.find(rowIndex1); + auto it2 = indexToRow_.find(rowIndex2); + + if (it1 == indexToRow_.end() && it2 == indexToRow_.end()) return; + + if (it1 == indexToRow_.end()) { + indexToRow_.emplace(rowIndex1, it2->second); + rowToIndex_.at(it2->second) = rowIndex1; + indexToRow_.erase(it2->second); + return; + } + + if (it2 == indexToRow_.end()) { + indexToRow_.emplace(rowIndex2, it1->second); + rowToIndex_.at(it1->second) = rowIndex2; + indexToRow_.erase(it1); + return; + } + + std::swap(rowToIndex_.at(it1->second), rowToIndex_.at(it2->second)); + std::swap(it1->second, it2->second); + } else { + for (auto i = indexToRow_.size(); i <= std::max(rowIndex1, rowIndex2); ++i) indexToRow_.push_back(i); + + std::swap(rowToIndex_[indexToRow_[rowIndex1]], rowToIndex_[indexToRow_[rowIndex2]]); + std::swap(indexToRow_[rowIndex1], indexToRow_[rowIndex2]); + } +} + +template +inline Base_swap& Base_swap::operator=(Base_swap other) { + indexToRow_.swap(other.indexToRow_); + rowToIndex_.swap(other.rowToIndex_); + std::swap(rowSwapped_, other.rowSwapped_); + return *this; +} + +template +inline void Base_swap::_orderRows() { + for (unsigned int i = 0; i < _matrix()->get_number_of_columns(); i++) { + _matrix()->matrix_.at(i).reorder(rowToIndex_, i); + } + for (index i = 0; i < _matrix()->get_number_of_columns(); i++) { + indexToRow_[i] = i; + rowToIndex_[i] = i; + } + rowSwapped_ = false; +} + +} // namespace persistence_matrix +} // namespace Gudhi + +#endif // PM_BASE_SWAP_H diff --git a/src/Persistence_matrix/include/gudhi/Persistence_matrix/boundary_matrix.h b/src/Persistence_matrix/include/gudhi/Persistence_matrix/boundary_matrix.h new file mode 100644 index 0000000000..a617c3981e --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Persistence_matrix/boundary_matrix.h @@ -0,0 +1,822 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file boundary_matrix.h + * @author Hannah Schreiber + * @brief Contains the @ref Boundary_matrix class. + */ + +#ifndef PM_BOUNDARY_MATRIX_H +#define PM_BOUNDARY_MATRIX_H + +#include +#include //print() only +#include +#include //std::swap, std::move & std::exchange + +namespace Gudhi { +namespace persistence_matrix { + +// TODO: factorize/inheritate/compose with @ref basematrix "base matrix"? +/** + * @class Boundary_matrix boundary_matrix.h gudhi/Persistence_matrix/boundary_matrix.h + * @ingroup persistence_matrix + * + * @brief %Matrix structure to store the ordered @ref boundarymatrix "boundary matrix" \f$ R \f$ of a filtered complex + * in order to compute its persistent homology. Provides an access to its columns and rows as well as the possibility + * to remove the last faces of the filtration while maintaining a valid barcode. + * + * @tparam Master_matrix An instanciation of @ref Matrix from which all types and options are deduced. + */ +template +class Boundary_matrix : public Master_matrix::Matrix_dimension_option, + public Master_matrix::template Base_swap_option >, + public Master_matrix::Base_pairing_option, + public Master_matrix::Matrix_row_access_option +{ + public: + using index = typename Master_matrix::index; /**< Container index type. */ + using id_index = typename Master_matrix::id_index; /**< @ref IDIdx index type. */ + using dimension_type = typename Master_matrix::dimension_type; /**< Dimension value type. */ + /** + * @brief Field operators class. Necessary only if @ref PersistenceMatrixOptions::is_z2 is false. + */ + using Field_operators = typename Master_matrix::Field_operators; + using Field_element_type = typename Master_matrix::element_type; /**< Type of an field element. */ + using Column_type = typename Master_matrix::Column_type; /**< Column type. */ + using boundary_type = typename Master_matrix::boundary_type; /**< Type of an input column. */ + using Row_type = typename Master_matrix::Row_type; /**< Row type, + only necessary with row access option. */ + using Cell_constructor = typename Master_matrix::Cell_constructor; /**< Factory of @ref Cell classes. */ + using Column_settings = typename Master_matrix::Column_settings; /**< Structure giving access to the columns to + necessary external classes. */ + + /** + * @brief Constructs an empty matrix. + * + * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all + * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + */ + Boundary_matrix(Column_settings* colSettings); + /** + * @brief Constructs a new matrix from the given ranges of @ref Matrix::cell_rep_type. Each range corresponds to + * a column (the order of the ranges are preserved). The content of the ranges is assumed to be sorted by increasing + * IDs. The IDs of the simplices are also assumed to be consecutifs, ordered by filtration value, starting with 0. + * + * @tparam Boundary_type Range type for @ref Matrix::cell_rep_type ranges. + * Assumed to have a begin(), end() and size() method. + * @param orderedBoundaries Range of boundaries: @p orderedBoundaries is interpreted as a boundary matrix of a + * filtered **simplicial** complex, whose boundaries are ordered by filtration order. + * Therefore, `orderedBoundaries[i]` should store the boundary of the \f$ i^{th} \f$ simplex in the filtration, + * as an ordered list of indices of its facets (again those indices correspond to their respective position + * in the matrix). That is why the indices of the simplices are assumed to be consecutifs and starting with 0 + * (an empty boundary is interpreted as a vertex boundary and not as a non existing simplex). + * All dimensions up to the maximal dimension of interest have to be present. If only a higher dimension is of + * interest and not everything should be stored, then use the @ref insert_boundary method instead + * (after creating the matrix with the + * @ref Boundary_matrix(unsigned int numberOfColumns, Column_settings* colSettings) + * constructor preferably). + * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all + * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + */ + template + Boundary_matrix(const std::vector& orderedBoundaries, + Column_settings* colSettings); + /** + * @brief Constructs a new empty matrix and reserves space for the given number of columns. + * + * @param numberOfColumns Number of columns to reserve space for. + * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all + * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + */ + Boundary_matrix(unsigned int numberOfColumns, Column_settings* colSettings); + /** + * @brief Copy constructor. If @p colSettings is not a null pointer, its value is kept + * instead of the one in the copied matrix. + * + * @param matrixToCopy Matrix to copy. + * @param colSettings Either a pointer to an existing setting structure for the columns or a null pointer. + * The structure should contain all the necessary external classes specifically necessary for the choosen column type, + * such as custom allocators. If null pointer, the pointer stored in @p matrixToCopy is used instead. + */ + Boundary_matrix(const Boundary_matrix& matrixToCopy, + Column_settings* colSettings = nullptr); + /** + * @brief Move constructor. + * + * @param other Matrix to move. + */ + Boundary_matrix(Boundary_matrix&& other) noexcept; + + /** + * @brief Inserts at the end of the matrix a new ordered column corresponding to the given boundary. + * This means that it is assumed that this method is called on boundaries in the order of the filtration. + * It also assumes that the faces in the given boundary are identified by their relative position in the filtration, + * starting at 0. If it is not the case, use the other + * @ref insert_boundary(id_index faceIndex, const Boundary_type& boundary, dimension_type dim) "insert_boundary" + * instead by indicating the face ID used in the boundaries when the face is inserted. + * + * Different to the constructor, the boundaries do not have to come from a simplicial complex, but also from + * a more general cell complex. This includes cubical complexes or Morse complexes for example. + * + * At the insertion, the boundary will be copied as is. The column will only be reduced later when the barcode + * is requested in order to apply some optimisations with the additional knowledge. Hence, the barcode will also + * not be updated, so call @ref Base_pairing::get_current_barcode "get_current_barcode" only when the matrix is + * complete. + * + * @tparam Boundary_type Range of @ref Matrix::cell_rep_type. Assumed to have a begin(), end() and size() method. + * @param boundary Boundary generating the new column. The content should be ordered by ID. + * @param dim Dimension of the face whose boundary is given. If the complex is simplicial, + * this parameter can be omitted as it can be deduced from the size of the boundary. + * @return The @ref MatIdx index of the inserted boundary. + */ + template + index insert_boundary(const Boundary_type& boundary, dimension_type dim = -1); + /** + * @brief It does the same as the other version, but allows the boundary faces to be identified without restrictions + * except that all IDs have to be strictly increasing in the order of filtration. Note that you should avoid then + * to use the other insertion method to avoid overwriting IDs. + * + * As a face has to be inserted before one of its cofaces in a valid filtration (recall that it is assumed that + * the faces are inserted by order of filtration), it is sufficient to indicate the ID of the face being inserted. + * + * @tparam Boundary_type Range of @ref Matrix::cell_rep_type. Assumed to have a begin(), end() and size() method. + * @param faceIndex @ref IDIdx index to use to indentify the new face. + * @param boundary Boundary generating the new column. The indices of the boundary have to correspond to the + * @p faceIndex values of precedent calls of the method for the corresponding faces and should be ordered in + * increasing order. + * @param dim Dimension of the face whose boundary is given. If the complex is simplicial, + * this parameter can be omitted as it can be deduced from the size of the boundary. + * @return The @ref MatIdx index of the inserted boundary. + */ + template + index insert_boundary(id_index faceIndex, const Boundary_type& boundary, dimension_type dim = -1); + /** + * @brief Returns the column at the given @ref MatIdx index. + * The type of the column depends on the choosen options, see @ref PersistenceMatrixOptions::column_type. + * + * Note that before returning the column, all column cells can eventually be reordered, if lazy swaps occurred. + * It is therefore recommended to avoid calling @ref get_column between column or row swaps, otherwise the benefits + * of the the lazyness is lost. + * + * @param columnIndex @ref MatIdx index of the column to return. + * @return Reference to the column. + */ + Column_type& get_column(index columnIndex); + /** + * @brief Only available if @ref PersistenceMatrixOptions::has_row_access is true. + * Returns the row at the given @ref rowindex "row index" of the matrix. + * The type of the row depends on the choosen options, see @ref PersistenceMatrixOptions::has_intrusive_rows. + * + * Note that before returning the row, all column cells can eventually be reordered, if lazy swaps occurred. + * It is therefore recommended to avoid calling @ref get_row between column or row swaps, otherwise the benefits + * of the the lazyness is lost. + * + * @param rowIndex @ref rowindex "Row index" of the row to return. + * @return Reference to the row. + */ + Row_type& get_row(index rowIndex); + /** + * @brief Only available if @ref PersistenceMatrixOptions::has_removable_columns is true. + * Removes the last face in the filtration from the matrix and updates the barcode if this one was already computed. + * + * @return The pivot of the removed face. + */ + index remove_last(); + /** + * @brief If @ref PersistenceMatrixOptions::has_row_access and @ref PersistenceMatrixOptions::has_removable_rows + * are true: assumes that the row is empty and removes it. If @ref PersistenceMatrixOptions::has_map_column_container + * and @ref PersistenceMatrixOptions::has_column_and_row_swaps are true: cleans up maps used for the lazy row swaps. + * Otherwise, does nothing. + * + * @warning The removed rows are always assumed to be empty. If it is not the case, the deleted row cells are not + * removed from their columns. And in the case of intrusive rows, this will generate a segmentation fault when + * the column cells are destroyed later. The row access is just meant as a "read only" access to the rows and the + * @ref erase_empty_row method just as a way to specify that a row is empty and can therefore be removed from + * dictionnaries. This allows to avoid testing the emptiness of a row at each column cell removal, what can be + * quite frequent. + * + * @param rowIndex @ref rowindex "Row index" of the empty row. + */ + void erase_empty_row(index rowIndex); + + /** + * @brief Returns the current number of columns in the matrix. + * + * @return The number of columns. + */ + index get_number_of_columns() const; + + /** + * @brief Returns the dimension of the given column. + * + * @param columnIndex @ref MatIdx index of the column representing the face. + * @return Dimension of the face. + */ + dimension_type get_column_dimension(index columnIndex) const; + + /** + * @brief Adds column at @p sourceColumnIndex onto the column at @p targetColumnIndex in the matrix. + * + * @warning They will be no verification to ensure that the addition makes sense for the validity of a + * boundary matrix of a filtered complex. For example, a right-to-left addition could corrupt the computation + * of the barcode if done blindly. So should be used with care. + * + * @param sourceColumnIndex @ref MatIdx index of the source column. + * @param targetColumnIndex @ref MatIdx index of the target column. + */ + void add_to(index sourceColumnIndex, index targetColumnIndex); + /** + * @brief Multiplies the target column with the coefficiant and then adds the source column to it. + * That is: `targetColumn = (targetColumn * coefficient) + sourceColumn`. + * + * @warning They will be no verification to ensure that the addition makes sense for the validity of a + * boundary matrix of a filtered complex. For example, a right-to-left addition could corrupt the computation + * of the barcode if done blindly. So should be used with care. + * + * @param sourceColumnIndex @ref MatIdx index of the source column. + * @param coefficient Value to multiply. + * @param targetColumnIndex @ref MatIdx index of the target column. + */ + void multiply_target_and_add_to(index sourceColumnIndex, + const Field_element_type& coefficient, + index targetColumnIndex); + /** + * @brief Multiplies the source column with the coefficiant before adding it to the target column. + * That is: `targetColumn += (coefficient * sourceColumn)`. The source column will **not** be modified. + * + * @warning They will be no verification to ensure that the addition makes sense for the validity of a + * boundary matrix of a filtered complex. For example, a right-to-left addition could corrupt the computation + * of the barcode if done blindly. So should be used with care. + * + * @param coefficient Value to multiply. + * @param sourceColumnIndex @ref MatIdx index of the source column. + * @param targetColumnIndex @ref MatIdx index of the target column. + */ + void multiply_source_and_add_to(const Field_element_type& coefficient, + index sourceColumnIndex, + index targetColumnIndex); + + /** + * @brief Zeroes the cell at the given coordinates. + * + * @warning They will be no verification to ensure that the zeroing makes sense for the validity of a + * boundary matrix of a filtered complex. So should be used while knowing what one is doing. + * + * @param columnIndex @ref MatIdx index of the column of the cell. + * @param rowIndex @ref rowindex "Row index" of the row of the cell. + */ + void zero_cell(index columnIndex, index rowIndex); + /** + * @brief Zeroes the column at the given index. + * + * @warning They will be no verification to ensure that the zeroing makes sense for the validity of a + * boundary matrix of a filtered complex. So should be used while knowing what one is doing. + * + * @param columnIndex @ref MatIdx index of the column to zero. + */ + void zero_column(index columnIndex); + /** + * @brief Indicates if the cell at given coordinates has value zero. + * + * @param columnIndex @ref MatIdx index of the column of the cell. + * @param rowIndex @ref rowindex "Row index" of the row of the cell. + * @return true If the cell has value zero. + * @return false Otherwise. + */ + bool is_zero_cell(index columnIndex, index rowIndex) const; + /** + * @brief Indicates if the column at given index has value zero. + * + * @param columnIndex @ref MatIdx index of the column. + * @return true If the column has value zero. + * @return false Otherwise. + */ + bool is_zero_column(index columnIndex); + + /** + * @brief Returns the pivot of the given column. + * + * @param columnIndex @ref MatIdx index of the column. + * @return Pivot of the coluimn at @p columnIndex. + */ + index get_pivot(index columnIndex); + + /** + * @brief Resets the matrix to an empty matrix. + * + * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all + * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + */ + void reset(Column_settings* colSettings) { + matrix_.clear(); + nextInsertIndex_ = 0; + colSettings_ = colSettings; + } + + /** + * @brief Assign operator. + */ + Boundary_matrix& operator=(const Boundary_matrix& other); + /** + * @brief Swap operator. + */ + friend void swap(Boundary_matrix& matrix1, Boundary_matrix& matrix2) { + swap(static_cast(matrix1), + static_cast(matrix2)); + swap(static_cast >&>(matrix1), + static_cast >&>(matrix2)); + swap(static_cast(matrix1), + static_cast(matrix2)); + matrix1.matrix_.swap(matrix2.matrix_); + std::swap(matrix1.nextInsertIndex_, matrix2.nextInsertIndex_); + std::swap(matrix1.colSettings_, matrix2.colSettings_); + + if constexpr (Master_matrix::Option_list::has_row_access) { + swap(static_cast(matrix1), + static_cast(matrix2)); + } + } + + void print(); // for debug + + private: + using dim_opt = typename Master_matrix::Matrix_dimension_option; + using swap_opt = typename Master_matrix::template Base_swap_option >; + using pair_opt = typename Master_matrix::Base_pairing_option; + using ra_opt = typename Master_matrix::Matrix_row_access_option; + using matrix_type = typename Master_matrix::column_container_type; + + friend swap_opt; + friend pair_opt; + + matrix_type matrix_; /**< Column container. */ + index nextInsertIndex_; /**< Next unused column index. */ + Column_settings* colSettings_; /**< Cell factory. */ + + static constexpr bool activeDimOption = + Master_matrix::Option_list::has_matrix_maximal_dimension_access || Master_matrix::maxDimensionIsNeeded; + static constexpr bool activeSwapOption = + Master_matrix::Option_list::has_column_and_row_swaps || Master_matrix::Option_list::has_vine_update; + static constexpr bool activePairingOption = Master_matrix::Option_list::has_column_pairings && + !Master_matrix::Option_list::has_vine_update && + !Master_matrix::Option_list::can_retrieve_representative_cycles; + + void _orderRowsIfNecessary(); + const Column_type& _get_column(index columnIndex) const; + Column_type& _get_column(index columnIndex); + index _get_real_row_index(index rowIndex) const; + template + void _container_insert(const Container_type& column, index pos, dimension_type dim); + void _container_insert(const Column_type& column, [[maybe_unused]] index pos = 0); +}; + +template +inline Boundary_matrix::Boundary_matrix(Column_settings* colSettings) + : dim_opt(-1), + swap_opt(), + pair_opt(), + ra_opt(), + nextInsertIndex_(0), + colSettings_(colSettings) +{} + +template +template +inline Boundary_matrix::Boundary_matrix(const std::vector& orderedBoundaries, + Column_settings* colSettings) + : dim_opt(-1), + swap_opt(orderedBoundaries.size()), + pair_opt(), + ra_opt(orderedBoundaries.size()), + nextInsertIndex_(orderedBoundaries.size()), + colSettings_(colSettings) +{ + matrix_.reserve(orderedBoundaries.size()); + + for (index i = 0; i < orderedBoundaries.size(); i++) { + _container_insert(orderedBoundaries[i], i, orderedBoundaries[i].size() == 0 ? 0 : orderedBoundaries[i].size() - 1); + } +} + +template +inline Boundary_matrix::Boundary_matrix(unsigned int numberOfColumns, + Column_settings* colSettings) + : dim_opt(-1), + swap_opt(numberOfColumns), + pair_opt(), + ra_opt(numberOfColumns), + matrix_(!Master_matrix::Option_list::has_map_column_container && Master_matrix::Option_list::has_row_access + ? 0 + : numberOfColumns), + nextInsertIndex_(0), + colSettings_(colSettings) +{ + if constexpr (!Master_matrix::Option_list::has_map_column_container && Master_matrix::Option_list::has_row_access) + matrix_.reserve(numberOfColumns); +} + +template +inline Boundary_matrix::Boundary_matrix(const Boundary_matrix& matrixToCopy, + Column_settings* colSettings) + : dim_opt(static_cast(matrixToCopy)), + swap_opt(static_cast(matrixToCopy)), + pair_opt(static_cast(matrixToCopy)), + ra_opt(static_cast(matrixToCopy)), + nextInsertIndex_(matrixToCopy.nextInsertIndex_), + colSettings_(colSettings == nullptr ? matrixToCopy.colSettings_ : colSettings) +{ + matrix_.reserve(matrixToCopy.matrix_.size()); + for (const auto& cont : matrixToCopy.matrix_){ + if constexpr (Master_matrix::Option_list::has_map_column_container){ + _container_insert(cont.second, cont.first); + } else { + _container_insert(cont); + } + } +} + +template +inline Boundary_matrix::Boundary_matrix(Boundary_matrix&& other) noexcept + : dim_opt(std::move(static_cast(other))), + swap_opt(std::move(static_cast(other))), + pair_opt(std::move(static_cast(other))), + ra_opt(std::move(static_cast(other))), + matrix_(std::move(other.matrix_)), + nextInsertIndex_(std::exchange(other.nextInsertIndex_, 0)), + colSettings_(std::exchange(other.colSettings_, nullptr)) +{} + +template +template +inline typename Boundary_matrix::index Boundary_matrix::insert_boundary( + const Boundary_type& boundary, dimension_type dim) +{ + return insert_boundary(nextInsertIndex_, boundary, dim); +} + +template +template +inline typename Boundary_matrix::index Boundary_matrix::insert_boundary( + id_index faceIndex, const Boundary_type& boundary, dimension_type dim) +{ + if (dim == -1) dim = boundary.size() == 0 ? 0 : boundary.size() - 1; + + _orderRowsIfNecessary(); + + //updates container sizes + if constexpr (Master_matrix::Option_list::has_row_access && !Master_matrix::Option_list::has_removable_rows) { + id_index pivot; + if constexpr (Master_matrix::Option_list::is_z2) { + pivot = *std::prev(boundary.end()); + } else { + pivot = std::prev(boundary.end())->first; + } + //row container + if (ra_opt::rows_->size() <= pivot) ra_opt::rows_->resize(pivot + 1); + } + + //row swap map containers + if constexpr (Master_matrix::Option_list::has_map_column_container) { + if constexpr (activeSwapOption) { + swap_opt::indexToRow_.emplace(faceIndex, faceIndex); + swap_opt::rowToIndex_.emplace(faceIndex, faceIndex); + } + } else { + if constexpr (activeSwapOption) { + for (index i = swap_opt::indexToRow_.size(); i <= faceIndex; ++i) { + swap_opt::indexToRow_.push_back(i); + swap_opt::rowToIndex_.push_back(i); + } + } + } + + _container_insert(boundary, nextInsertIndex_, dim); + + return nextInsertIndex_++; +} + +template +inline typename Boundary_matrix::Column_type& Boundary_matrix::get_column( + index columnIndex) +{ + _orderRowsIfNecessary(); + + return _get_column(columnIndex); +} + +template +inline typename Boundary_matrix::Row_type& Boundary_matrix::get_row(index rowIndex) +{ + static_assert(Master_matrix::Option_list::has_row_access, "'get_row' is not implemented for the chosen options."); + + _orderRowsIfNecessary(); + + return ra_opt::get_row(rowIndex); +} + +template +inline typename Boundary_matrix::index Boundary_matrix::remove_last() +{ + static_assert(Master_matrix::Option_list::has_removable_columns, + "'remove_last' is not implemented for the chosen options."); + + if (nextInsertIndex_ == 0) return -1; // empty matrix + --nextInsertIndex_; + + //updates dimension max + if constexpr (activeDimOption) { + dim_opt::update_down(matrix_.at(nextInsertIndex_).get_dimension()); + } + + //computes pivot and removes column from matrix_ + id_index pivot; + if constexpr (Master_matrix::Option_list::has_map_column_container) { + auto it = matrix_.find(nextInsertIndex_); + pivot = it->second.get_pivot(); + if constexpr (activeSwapOption) { + // if the removed column is positive, the pivot won't change value + if (swap_opt::rowSwapped_ && pivot != static_cast(-1)) { + swap_opt::_orderRows(); + pivot = it->second.get_pivot(); + } + } + matrix_.erase(it); + } else { + pivot = matrix_[nextInsertIndex_].get_pivot(); + if constexpr (activeSwapOption) { + // if the removed column is positive, the pivot won't change value + if (swap_opt::rowSwapped_ && pivot != static_cast(-1)) { + swap_opt::_orderRows(); + pivot = matrix_[nextInsertIndex_].get_pivot(); + } + } + if constexpr (Master_matrix::Option_list::has_row_access) { + GUDHI_CHECK(nextInsertIndex_ == matrix_.size() - 1, + std::logic_error("Boundary_matrix::remove_last - Indexation problem.")); + matrix_.pop_back(); + } else { + matrix_[nextInsertIndex_].clear(); + } + } + + erase_empty_row(nextInsertIndex_); // maximal, so empty + + //updates barcode + if constexpr (activePairingOption) { + pair_opt::_remove_last(nextInsertIndex_); + } + + return pivot; +} + +template +inline void Boundary_matrix::erase_empty_row(index rowIndex) +{ + //computes real row index and erases it if necessary from the row swap map containers + id_index rowID = rowIndex; + if constexpr (activeSwapOption) { + if constexpr (Master_matrix::Option_list::has_map_column_container) { + auto it = swap_opt::indexToRow_.find(rowIndex); + rowID = it->second; + swap_opt::rowToIndex_.erase(rowID); + swap_opt::indexToRow_.erase(it); + } else { + rowID = swap_opt::indexToRow_[rowIndex]; + } + } + + if constexpr (Master_matrix::Option_list::has_row_access && Master_matrix::Option_list::has_removable_rows) { + ra_opt::erase_empty_row(rowID); + } +} + +template +inline typename Boundary_matrix::index Boundary_matrix::get_number_of_columns() const +{ + if constexpr (Master_matrix::Option_list::has_map_column_container) { + return matrix_.size(); + } else { + return nextInsertIndex_; // matrix could have been resized much bigger while insert + } +} + +template +inline typename Boundary_matrix::dimension_type Boundary_matrix::get_column_dimension( + index columnIndex) const +{ + return _get_column(columnIndex).get_dimension(); +} + +template +inline void Boundary_matrix::add_to(index sourceColumnIndex, index targetColumnIndex) +{ + _get_column(targetColumnIndex) += _get_column(sourceColumnIndex); +} + +template +inline void Boundary_matrix::multiply_target_and_add_to(index sourceColumnIndex, + const Field_element_type& coefficient, + index targetColumnIndex) +{ + _get_column(targetColumnIndex).multiply_target_and_add(coefficient, _get_column(sourceColumnIndex)); +} + +template +inline void Boundary_matrix::multiply_source_and_add_to(const Field_element_type& coefficient, + index sourceColumnIndex, + index targetColumnIndex) +{ + _get_column(targetColumnIndex).multiply_source_and_add(_get_column(sourceColumnIndex), coefficient); +} + +template +inline void Boundary_matrix::zero_cell(index columnIndex, index rowIndex) +{ + _get_column(columnIndex).clear(_get_real_row_index(rowIndex)); +} + +template +inline void Boundary_matrix::zero_column(index columnIndex) +{ + _get_column(columnIndex).clear(); +} + +template +inline bool Boundary_matrix::is_zero_cell(index columnIndex, index rowIndex) const +{ + return !(_get_column(columnIndex).is_non_zero(_get_real_row_index(rowIndex))); +} + +template +inline bool Boundary_matrix::is_zero_column(index columnIndex) +{ + return _get_column(columnIndex).is_empty(); +} + +template +inline typename Boundary_matrix::index Boundary_matrix::get_pivot(index columnIndex) +{ + _orderRowsIfNecessary(); + + return _get_column(columnIndex).get_pivot(); +} + +template +inline Boundary_matrix& Boundary_matrix::operator=(const Boundary_matrix& other) +{ + dim_opt::operator=(other); + swap_opt::operator=(other); + pair_opt::operator=(other); + ra_opt::operator=(other); + + matrix_.clear(); + nextInsertIndex_ = other.nextInsertIndex_; + colSettings_ = other.colSettings_; + + matrix_.reserve(other.matrix_.size()); + for (const auto& cont : other.matrix_){ + if constexpr (Master_matrix::Option_list::has_map_column_container){ + _container_insert(cont.second, cont.first); + } else { + _container_insert(cont); + } + } + + return *this; +} + +template +inline void Boundary_matrix::print() +{ + if constexpr (activeSwapOption) { + if (swap_opt::rowSwapped_) swap_opt::_orderRows(); + } + std::cout << "Boundary_matrix:\n"; + for (index i = 0; i < nextInsertIndex_; ++i) { + Column_type& col = matrix_[i]; + for (auto e : col.get_content(nextInsertIndex_)) { + if (e == 0u) + std::cout << "- "; + else + std::cout << e << " "; + } + std::cout << "\n"; + } + std::cout << "\n"; + if constexpr (Master_matrix::Option_list::has_row_access) { + std::cout << "Row Matrix:\n"; + for (id_index i = 0; i < nextInsertIndex_; ++i) { + const auto& row = ra_opt::rows_[i]; + for (const auto& cell : row) { + std::cout << cell.get_column_index() << " "; + } + std::cout << "(" << i << ")\n"; + } + std::cout << "\n"; + } +} + +template +inline void Boundary_matrix::_orderRowsIfNecessary() +{ + if constexpr (activeSwapOption) { + if (swap_opt::rowSwapped_) swap_opt::_orderRows(); + } +} + +template +inline const typename Boundary_matrix::Column_type& Boundary_matrix::_get_column( + index columnIndex) const +{ + if constexpr (Master_matrix::Option_list::has_map_column_container) { + return matrix_.at(columnIndex); + } else { + return matrix_[columnIndex]; + } +} + +template +inline typename Boundary_matrix::Column_type& Boundary_matrix::_get_column( + index columnIndex) +{ + if constexpr (Master_matrix::Option_list::has_map_column_container) { + return matrix_.at(columnIndex); + } else { + return matrix_[columnIndex]; + } +} + +template +inline typename Boundary_matrix::index Boundary_matrix::_get_real_row_index( + index rowIndex) const +{ + if constexpr (Master_matrix::Option_list::has_column_and_row_swaps || Master_matrix::Option_list::has_vine_update) { + if constexpr (Master_matrix::Option_list::has_map_column_container) { + return swap_opt::indexToRow_.at(rowIndex); + } else { + return swap_opt::indexToRow_[rowIndex]; + } + } else { + return rowIndex; + } +} + +template +template +inline void Boundary_matrix::_container_insert(const Container_type& column, + index pos, + dimension_type dim) +{ + if constexpr (Master_matrix::Option_list::has_map_column_container) { + if constexpr (Master_matrix::Option_list::has_row_access) { + matrix_.try_emplace(pos, Column_type(pos, column, dim, ra_opt::rows_, colSettings_)); + } else { + matrix_.try_emplace(pos, Column_type(column, dim, colSettings_)); + } + } else { + if constexpr (Master_matrix::Option_list::has_row_access) { + matrix_.emplace_back(pos, column, dim, ra_opt::rows_, colSettings_); + } else { + if (matrix_.size() <= pos) { + matrix_.emplace_back(column, dim, colSettings_); + } else { + matrix_[pos] = Column_type(column, dim, colSettings_); + } + } + } + if constexpr (activeDimOption) { + dim_opt::update_up(dim); + } +} + +template +inline void Boundary_matrix::_container_insert(const Column_type& column, [[maybe_unused]] index pos) +{ + if constexpr (Master_matrix::Option_list::has_map_column_container) { + if constexpr (Master_matrix::Option_list::has_row_access) { + matrix_.try_emplace(pos, Column_type(column, column.get_column_index(), ra_opt::rows_, colSettings_)); + } else { + matrix_.try_emplace(pos, Column_type(column, colSettings_)); + } + } else { + if constexpr (Master_matrix::Option_list::has_row_access) { + matrix_.emplace_back(column, column.get_column_index(), ra_opt::rows_, colSettings_); + } else { + matrix_.emplace_back(column, colSettings_); + } + } +} + +} // namespace persistence_matrix +} // namespace Gudhi + +#endif // PM_BOUNDARY_MATRIX_H diff --git a/src/Persistence_matrix/include/gudhi/Persistence_matrix/chain_matrix.h b/src/Persistence_matrix/include/gudhi/Persistence_matrix/chain_matrix.h new file mode 100644 index 0000000000..e89ce87366 --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Persistence_matrix/chain_matrix.h @@ -0,0 +1,1351 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file chain_matrix.h + * @author Hannah Schreiber + * @brief Contains the @ref Chain_matrix class. + */ + +#ifndef PM_CHAIN_MATRIX_H +#define PM_CHAIN_MATRIX_H + +#include //print() only +#include +#include +#include +#include +#include //std::swap, std::move & std::exchange +#include //std::sort + +#include //friend + +namespace Gudhi { +namespace persistence_matrix { + +/** + * @class Chain_matrix chain_matrix.h gudhi/Persistence_matrix/chain_matrix.h + * @ingroup persistence_matrix + * + * @brief %Matrix structure storing a compatible base of a filtered chain complex. See @cite zigzag. + * The base is constructed from the boundaries of the faces in the complex. Allows the persistent homology to be + * computed, as well as representative cycles. Supports vineyards (see @cite vineyards) and the removal + * of maximal faces while maintaining a valid barcode. Provides an access to its columns and rows. + * + * @tparam Master_matrix An instanciation of @ref Matrix from which all types and options are deduced. + */ +template +class Chain_matrix : public Master_matrix::Matrix_dimension_option, + public Master_matrix::Chain_pairing_option, + public Master_matrix::Chain_vine_swap_option, + public Master_matrix::Chain_representative_cycles_option, + public Master_matrix::Matrix_row_access_option +{ + public: + /** + * @brief Field operators class. Necessary only if @ref PersistenceMatrixOptions::is_z2 is false. + */ + using Field_operators = typename Master_matrix::Field_operators; + using Field_element_type = typename Master_matrix::element_type; /**< Type of an field element. */ + using Column_type = typename Master_matrix::Column_type; /**< Column type. */ + using Row_type = typename Master_matrix::Row_type; /**< Row type, + only necessary with row access option. */ + using Cell = typename Master_matrix::Cell_type; /**< @ref Cell "Matrix cell" type. */ + using Cell_constructor = typename Master_matrix::Cell_constructor; /**< Factory of @ref Cell classes. */ + using Column_settings = typename Master_matrix::Column_settings; /**< Structure giving access to the columns to + necessary external classes. */ + using boundary_type = typename Master_matrix::boundary_type; /**< Type of an input column. */ + using cell_rep_type = typename Master_matrix::cell_rep_type; /**< %Cell content representative. */ + using index = typename Master_matrix::index; /**< @ref MatIdx index type. */ + using id_index = typename Master_matrix::id_index; /**< @ref IDIdx index type. */ + using pos_index = typename Master_matrix::pos_index; /**< @ref PosIdx index type. */ + using dimension_type = typename Master_matrix::dimension_type; /**< Dimension value type. */ + + /** + * @brief Constructs an empty matrix. Only available if @ref PersistenceMatrixOptions::has_column_pairings is + * true or @ref PersistenceMatrixOptions::has_vine_update is false. Otherwise, birth and death comparators have + * to be provided. + * + * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all + * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + */ + Chain_matrix(Column_settings* colSettings); + /** + * @brief Constructs a new matrix from the given ranges of @ref Matrix::cell_rep_type. Each range corresponds to a + * column (the order of the ranges are preserved). The content of the ranges is assumed to be sorted by increasing + * IDs. The IDs of the simplices are also assumed to be consecutifs, ordered by filtration value, starting with 0. + * Only available if @ref PersistenceMatrixOptions::has_column_pairings is true or + * @ref PersistenceMatrixOptions::has_vine_update is false. Otherwise, birth and death + * comparators have to be provided. + * + * @tparam Boundary_type Range type for @ref Matrix::cell_rep_type ranges. + * Assumed to have a begin(), end() and size() method. + * @param orderedBoundaries Range of boundaries: @p orderedBoundaries is interpreted as a boundary matrix of a + * filtered **simplicial** complex, whose boundaries are ordered by filtration order. + * Therefore, `orderedBoundaries[i]` should store the boundary of the \f$ i^{th} \f$ simplex in the filtration, + * as an ordered list of indices of its facets (again those indices correspond to their respective position + * in the matrix). That is why the indices of the simplices are assumed to be consecutifs and starting with 0 + * (an empty boundary is interpreted as a vertex boundary and not as a non existing simplex). + * All dimensions up to the maximal dimension of interest have to be present. If only a higher dimension is of + * interest and not everything should be stored, then use the @ref insert_boundary method instead + * (after creating the matrix with the + * @ref Chain_matrix(unsigned int numberOfColumns, Column_settings* colSettings) + * constructor preferably). + * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all + * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + */ + template + Chain_matrix(const std::vector& orderedBoundaries, + Column_settings* colSettings); + /** + * @brief Constructs a new empty matrix and reserves space for the given number of columns. Only available + * if @ref PersistenceMatrixOptions::has_column_pairings is true or @ref PersistenceMatrixOptions::has_vine_update + * is false. Otherwise, birth and death comparators have to be provided. + * + * @param numberOfColumns Number of columns to reserve space for. + * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all + * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + */ + Chain_matrix(unsigned int numberOfColumns, Column_settings* colSettings); + /** + * @brief Constructs an empty matrix and stores the given comparators. + * + * @warning If @ref PersistenceMatrixOptions::has_vine_update is false, the comparators are not used. + * And if @ref PersistenceMatrixOptions::has_vine_update is true, but + * @ref PersistenceMatrixOptions::has_column_pairings is also true, the comparators are ignored and + * the current barcode is used to compare birth and deaths. Therefore it is useless to provide them in those cases. + * + * @tparam BirthComparatorFunction Type of the birth comparator: (@ref pos_index, @ref pos_index) -> bool + * @tparam DeathComparatorFunction Type of the death comparator: (@ref pos_index, @ref pos_index) -> bool + * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all + * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * @param birthComparator Method taking two @ref PosIdx indices as input and returning true if and only if + * the birth associated to the first position is strictly less than birth associated to + * the second one with respect to some self defined order. It is used while swapping two unpaired or + * two negative columns. + * @param deathComparator Method taking two @ref PosIdx indices as input and returning true if and only if + * the death associated to the first position is strictly less than death associated to + * the second one with respect to some self defined order. It is used while swapping two positive but paired + * columns. + */ + template + Chain_matrix(Column_settings* colSettings, + const BirthComparatorFunction& birthComparator, + const DeathComparatorFunction& deathComparator); + /** + * @brief Constructs a new matrix from the given ranges of @ref Matrix::cell_rep_type. Each range corresponds to a + * column (the order of the ranges are preserved). The content of the ranges is assumed to be sorted by increasing + * IDs. The IDs of the simplices are also assumed to be consecutifs, ordered by filtration value, starting with 0. + * + * @warning If @ref PersistenceMatrixOptions::has_vine_update is false, the comparators are not used. + * And if @ref PersistenceMatrixOptions::has_vine_update is true, but + * @ref PersistenceMatrixOptions::has_column_pairings is also true, the comparators are ignored and + * the current barcode is used to compare birth and deaths. Therefore it is useless to provide them in those cases. + * + * @tparam BirthComparatorFunction Type of the birth comparator: (@ref pos_index, @ref pos_index) -> bool + * @tparam DeathComparatorFunction Type of the death comparator: (@ref pos_index, @ref pos_index) -> bool + * @tparam Boundary_type Range type for @ref Matrix::cell_rep_type ranges. + * Assumed to have a begin(), end() and size() method. + * @param orderedBoundaries Range of boundaries: @p orderedBoundaries is interpreted as a boundary matrix of a + * filtered **simplicial** complex, whose boundaries are ordered by filtration order. + * Therefore, `orderedBoundaries[i]` should store the boundary of the \f$ i^{th} \f$ simplex in the filtration, + * as an ordered list of indices of its facets (again those indices correspond to their respective position + * in the matrix). That is why the indices of the simplices are assumed to be consecutifs and starting with 0 + * (an empty boundary is interpreted as a vertex boundary and not as a non existing simplex). + * All dimensions up to the maximal dimension of interest have to be present. If only a higher dimension is of + * interest and not everything should be stored, then use the @ref insert_boundary method instead + * (after creating the matrix with the @ref Chain_matrix(unsigned int, Column_settings*, + * const BirthComparatorFunction&, const DeathComparatorFunction&) constructor preferably). + * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all + * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * @param birthComparator Method taking two @ref PosIdx indices as input and returning true if and only if + * the birth associated to the first position is strictly less than birth associated to + * the second one with respect to some self defined order. It is used while swapping two unpaired or + * two negative columns. + * @param deathComparator Method taking two @ref PosIdx indices as input and returning true if and only if + * the death associated to the first position is strictly less than death associated to + * the second one with respect to some self defined order. It is used while swapping two positive but paired + * columns. + */ + template + Chain_matrix(const std::vector& orderedBoundaries, + Column_settings* colSettings, + const BirthComparatorFunction& birthComparator, + const DeathComparatorFunction& deathComparator); + /** + * @brief Constructs a new empty matrix and reserves space for the given number of columns. + * + * @warning If @ref PersistenceMatrixOptions::has_vine_update is false, the comparators are not used. + * And if @ref PersistenceMatrixOptions::has_vine_update is true, but + * @ref PersistenceMatrixOptions::has_column_pairings is also true, the comparators are ignored and + * the current barcode is used to compare birth and deaths. Therefore it is useless to provide them in those cases. + * + * @tparam BirthComparatorFunction Type of the birth comparator: (@ref pos_index, @ref pos_index) -> bool + * @tparam DeathComparatorFunction Type of the death comparator: (@ref pos_index, @ref pos_index) -> bool + * @param numberOfColumns Number of columns to reserve space for. + * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all + * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * @param birthComparator Method taking two @ref PosIdx indices as input and returning true if and only if + * the birth associated to the first position is strictly less than birth associated to + * the second one with respect to some self defined order. It is used while swapping two unpaired or + * two negative columns. + * @param deathComparator Method taking two @ref PosIdx indices as input and returning true if and only if + * the death associated to the first position is strictly less than death associated to + * the second one with respect to some self defined order. It is used while swapping two positive but paired + * columns. + */ + template + Chain_matrix(unsigned int numberOfColumns, + Column_settings* colSettings, + const BirthComparatorFunction& birthComparator, + const DeathComparatorFunction& deathComparator); + /** + * @brief Copy constructor. If @p colSettings is not a null pointer, its value is kept + * instead of the one in the copied matrix. + * + * @param matrixToCopy Matrix to copy. + * @param colSettings Either a pointer to an existing setting structure for the columns or a null pointer. + * The structure should contain all the necessary external classes specifically necessary for the choosen column type, + * such as custom allocators. If null pointer, the pointer stored in @p matrixToCopy is used instead. + */ + Chain_matrix(const Chain_matrix& matrixToCopy, + Column_settings* colSettings = nullptr); + /** + * @brief Move constructor. + * + * @param other Matrix to move. + */ + Chain_matrix(Chain_matrix&& other) noexcept; + + /** + * @brief Inserts at the end of the matrix a new ordered column corresponding to the given boundary. + * This means that it is assumed that this method is called on boundaries in the order of the filtration. + * It also assumes that the faces in the given boundary are identified by their relative position in the filtration, + * starting at 0. If it is not the case, use the other + * @ref insert_boundary(id_index faceIndex, const Boundary_type& boundary, dimension_type dim) "insert_boundary" + * instead by indicating the face ID used in the boundaries when the face is inserted. + * + * Different to the constructor, the boundaries do not have to come from a simplicial complex, but also from + * a more general cell complex. This includes cubical complexes or Morse complexes for example. + * + * When inserted, the given boundary is reduced and from the reduction process, the column is deduced in the form of: + * `IDIdx + linear combination of older column IDIdxs`. If the barcode is stored, it will be updated. + * + * @tparam Boundary_type Range of @ref Matrix::cell_rep_type. Assumed to have a begin(), end() and size() method. + * @param boundary Boundary generating the new column. The content should be ordered by ID. + * @param dim Dimension of the face whose boundary is given. If the complex is simplicial, + * this parameter can be omitted as it can be deduced from the size of the boundary. + * @return The @ref MatIdx indices of the unpaired chains used to reduce the boundary. + */ + template + std::vector insert_boundary(const Boundary_type& boundary, dimension_type dim = -1); + /** + * @brief It does the same as the other version, but allows the boundary faces to be identified without restrictions + * except that all IDs have to be strictly increasing in the order of filtration. Note that you should avoid then + * to use the other insertion method to avoid overwriting IDs. + * + * As a face has to be inserted before one of its cofaces in a valid filtration (recall that it is assumed that + * the faces are inserted by order of filtration), it is sufficient to indicate the ID of the face being inserted. + * + * @tparam Boundary_type Range of @ref Matrix::cell_rep_type. Assumed to have a begin(), end() and size() method. + * @param faceID @ref IDIdx index to use to indentify the new face. + * @param boundary Boundary generating the new column. The indices of the boundary have to correspond to the + * @p faceID values of precedent calls of the method for the corresponding faces and should be ordered in + * increasing order. + * @param dim Dimension of the face whose boundary is given. If the complex is simplicial, + * this parameter can be omitted as it can be deduced from the size of the boundary. + * @return The @ref MatIdx index of the inserted boundary. + */ + template + std::vector insert_boundary(id_index faceID, const Boundary_type& boundary, dimension_type dim = -1); + /** + * @brief Returns the column at the given @ref MatIdx index. + * The type of the column depends on the choosen options, see @ref PersistenceMatrixOptions::column_type. + * + * @param columnIndex @ref MatIdx index of the column to return. + * @return Reference to the column. + */ + Column_type& get_column(index columnIndex); + /** + * @brief Returns the column at the given @ref MatIdx index. + * The type of the column depends on the choosen options, see @ref PersistenceMatrixOptions::column_type. + * + * @param columnIndex @ref MatIdx index of the column to return. + * @return Const reference to the column. + */ + const Column_type& get_column(index columnIndex) const; + /** + * @brief Only available if @ref PersistenceMatrixOptions::has_removable_columns and + * @ref PersistenceMatrixOptions::has_vine_update are true, as well as, + * @ref PersistenceMatrixOptions::has_map_column_container and @ref PersistenceMatrixOptions::has_column_pairings. + * Assumes that the face is maximal in the current complex and removes it such that the matrix remains consistent + * (i.e., the matrix is still a compatible bases of the chain complex in the sense of @cite zigzag). + * The maximality of the face is not verified. + * Also updates the barcode if it is stored. + * + * Note that using the other version of the method could perform better depending on how the data is + * maintained on the side of the user, that is, if providing the second parameter is easy. + * + * See also @ref remove_last. + * + * @param faceID @ref IDIdx index of the face to remove + */ + void remove_maximal_face(id_index faceID); + /** + * @brief Only available if @ref PersistenceMatrixOptions::has_removable_columns, + * @ref PersistenceMatrixOptions::has_vine_update and @ref PersistenceMatrixOptions::has_map_column_container + * are true. + * Assumes that the face is maximal in the current complex and removes it such that the matrix remains consistent + * (i.e., it is still a compatible bases of the chain complex in the sense of @cite zigzag). + * The maximality of the face is not verified. + * Also updates the barcode if it is stored. + * + * To maintain the compatibility, vine swaps are done to move the face up to the end of the filtration. Once at + * the end, the removal is trivial. But for @ref chainmatrix "chain matrices", swaps do not actually swap the position + * of the column every time, so the faces appearing after @p faceID in the filtration have to be searched first within + * the matrix. If the user has an easy access to the @ref IDIdx of the faces in the order of filtration, passing them + * by argument with @p columnsToSwap allows to skip a linear search process. Typically, if the user knows that the + * face he wants to remove is already the last face of the filtration, calling + * @ref remove_maximal_face(id_index faceIndex, const std::vector& columnsToSwap) + * "remove_maximal_face(faceID, {})" will be faster than @ref remove_last(). + * + * See also @ref remove_last. + * + * @param faceID @ref IDIdx index of the face to remove + * @param columnsToSwap Vector of @ref IDIdx indices of the faces coming after @p faceID in the filtration. + */ + void remove_maximal_face(id_index faceID, const std::vector& columnsToSwap); + /** + * @brief Only available if @ref PersistenceMatrixOptions::has_removable_columns is true and, + * if @ref PersistenceMatrixOptions::has_map_column_container is true or + * @ref PersistenceMatrixOptions::has_vine_update is false. + * Removes the last face in the filtration from the matrix and updates the barcode if it is stored. + * + * See also @ref remove_maximal_face. + * + * @warning If @ref PersistenceMatrixOptions::has_vine_update is true, the last face does not have to + * be at the end of the matrix container and therefore has to be searched first. In this case, if the user + * already knows the @ref IDIdx of the last face, calling + * @ref remove_maximal_face(id_index faceIndex, const std::vector& columnsToSwap) + * "remove_maximal_face(faceID, {})" instead allows to skip the search. + */ + void remove_last(); + + /** + * @brief Returns the current number of columns in the matrix. + * + * @return The number of columns. + */ + index get_number_of_columns() const; + + /** + * @brief Returns the dimension of the given column. + * + * @param columnIndex @ref MatIdx index of the column representing the face. + * @return Dimension of the face. + */ + dimension_type get_column_dimension(index columnIndex) const; + + /** + * @brief Adds column at @p sourceColumnIndex onto the column at @p targetColumnIndex in the matrix. + * + * @warning They will be no verification to ensure that the addition makes sense for the validity of a + * @ref chainmatrix "chain matrix". For example, a right-to-left addition could corrupt the computation + * of the barcode if done blindly. So should be used with care. + * + * @param sourceColumnIndex @ref MatIdx index of the source column. + * @param targetColumnIndex @ref MatIdx index of the target column. + */ + void add_to(index sourceColumnIndex, index targetColumnIndex); + /** + * @brief Multiplies the target column with the coefficiant and then adds the source column to it. + * That is: `targetColumn = (targetColumn * coefficient) + sourceColumn`. + * + * @warning They will be no verification to ensure that the addition makes sense for the validity of a + * @ref chainmatrix "chain matrix". For example, a right-to-left addition could corrupt the computation + * of the barcode if done blindly. So should be used with care. + * + * @param sourceColumnIndex @ref MatIdx index of the source column. + * @param coefficient Value to multiply. + * @param targetColumnIndex @ref MatIdx index of the target column. + */ + void multiply_target_and_add_to(index sourceColumnIndex, + const Field_element_type& coefficient, + index targetColumnIndex); + /** + * @brief Multiplies the source column with the coefficiant before adding it to the target column. + * That is: `targetColumn += (coefficient * sourceColumn)`. The source column will **not** be modified. + * + * @warning They will be no verification to ensure that the addition makes sense for the validity of a + * @ref chainmatrix "chain matrix". For example, a right-to-left addition could corrupt the computation + * of the barcode if done blindly. So should be used with care. + * + * @param coefficient Value to multiply. + * @param sourceColumnIndex @ref MatIdx index of the source column. + * @param targetColumnIndex @ref MatIdx index of the target column. + */ + void multiply_source_and_add_to(const Field_element_type& coefficient, + index sourceColumnIndex, + index targetColumnIndex); + + /** + * @brief Indicates if the cell at given coordinates has value zero. + * + * @param columnIndex @ref MatIdx index of the column of the cell. + * @param rowIndex @ref rowindex "Row index" of the row of the cell. + * @return true If the cell has value zero. + * @return false Otherwise. + */ + bool is_zero_cell(index columnIndex, id_index rowIndex) const; + /** + * @brief Indicates if the column at given index has value zero. Note that if the matrix is valid, this method + * should always return false. + * + * @param columnIndex @ref MatIdx index of the column. + * @return true If the column has value zero. + * @return false Otherwise. + */ + bool is_zero_column(index columnIndex); + + /** + * @brief Returns the column with given @ref rowindex "row index" as pivot. Assumes that the pivot exists. + * + * @param faceID @ref rowindex "Row index" of the pivot. + * @return @ref MatIdx index of the column with the given pivot. + */ + index get_column_with_pivot(id_index faceID) const; + /** + * @brief Returns the @ref rowindex "row index" of the pivot of the given column. + * + * @param columnIndex @ref MatIdx index of the column + * @return The @ref rowindex "row index" of the pivot. + */ + id_index get_pivot(index columnIndex); + + /** + * @brief Resets the matrix to an empty matrix. + * + * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all + * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + */ + void reset(Column_settings* colSettings) { + matrix_.clear(); + pivotToColumnIndex_.clear(); + nextIndex_ = 0; + colSettings_ = colSettings; + } + + /** + * @brief Assign operator. + */ + Chain_matrix& operator=(const Chain_matrix& other); + /** + * @brief Swap operator. + */ + friend void swap(Chain_matrix& matrix1, Chain_matrix& matrix2) { + swap(static_cast(matrix1), + static_cast(matrix2)); + swap(static_cast(matrix1), + static_cast(matrix2)); + swap(static_cast(matrix1), + static_cast(matrix2)); + swap(static_cast(matrix1), + static_cast(matrix2)); + matrix1.matrix_.swap(matrix2.matrix_); + matrix1.pivotToColumnIndex_.swap(matrix2.pivotToColumnIndex_); + std::swap(matrix1.nextIndex_, matrix2.nextIndex_); + std::swap(matrix1.colSettings_, matrix2.colSettings_); + + if constexpr (Master_matrix::Option_list::has_row_access) { + swap(static_cast(matrix1), + static_cast(matrix2)); + } + } + + void print() const; // for debug + + friend class Id_to_index_overlay, Master_matrix>; + + private: + using dim_opt = typename Master_matrix::Matrix_dimension_option; + using swap_opt = typename Master_matrix::Chain_vine_swap_option; + using pair_opt = typename Master_matrix::Chain_pairing_option; + using rep_opt = typename Master_matrix::Chain_representative_cycles_option; + using ra_opt = typename Master_matrix::Matrix_row_access_option; + using matrix_type = typename Master_matrix::column_container_type; + using dictionnary_type = typename Master_matrix::template dictionnary_type; + using barcode_type = typename Master_matrix::barcode_type; + using bar_dictionnary_type = typename Master_matrix::bar_dictionnary_type; + using tmp_column_type = typename std::conditional< + Master_matrix::Option_list::is_z2, + std::set, + std::map + >::type; + + matrix_type matrix_; /**< Column container. */ + dictionnary_type pivotToColumnIndex_; /**< Map from @ref IDIdx to @ref MatIdx index. */ + index nextIndex_; /**< Next unused column index. */ + Column_settings* colSettings_; /**< Cell factory. */ + + template + std::vector _reduce_boundary(id_index faceID, const Boundary_type& boundary, dimension_type dim); + void _reduce_by_G(tmp_column_type& column, std::vector& chainsInH, index currentPivot); + void _reduce_by_F(tmp_column_type& column, std::vector& chainsInF, index currentPivot); + void _build_from_H(id_index faceID, tmp_column_type& column, std::vector& chainsInH); + void _update_largest_death_in_F(const std::vector& chainsInF); + void _insert_chain(const tmp_column_type& column, dimension_type dimension); + void _insert_chain(const tmp_column_type& column, dimension_type dimension, index pair); + void _add_to(const Column_type& column, tmp_column_type& set, unsigned int coef); + template + void _add_to(Column_type& target, F&& addition); + void _remove_last(index lastIndex); + void _update_barcode(pos_index birth); + void _add_bar(dimension_type dim); + template + void _container_insert(const Container_type& column, index pos, dimension_type dim); + void _container_insert(const Column_type& column, [[maybe_unused]] index pos = 0); + + constexpr barcode_type& _barcode(); + constexpr bar_dictionnary_type& _indexToBar(); + constexpr pos_index& _nextPosition(); +}; + +template +inline Chain_matrix::Chain_matrix(Column_settings* colSettings) + : dim_opt(-1), + pair_opt(), + swap_opt(), + rep_opt(), + ra_opt(), + nextIndex_(0), + colSettings_(colSettings) +{} + +template +template +inline Chain_matrix::Chain_matrix(const std::vector& orderedBoundaries, + Column_settings* colSettings) + : dim_opt(-1), + pair_opt(), + swap_opt(), + rep_opt(), + ra_opt(orderedBoundaries.size()), + nextIndex_(0), + colSettings_(colSettings) +{ + matrix_.reserve(orderedBoundaries.size()); + if constexpr (Master_matrix::Option_list::has_map_column_container) { + pivotToColumnIndex_.reserve(orderedBoundaries.size()); + } else { + pivotToColumnIndex_.resize(orderedBoundaries.size(), -1); + } + + for (const Boundary_type& b : orderedBoundaries) { + insert_boundary(b); + } +} + +template +inline Chain_matrix::Chain_matrix(unsigned int numberOfColumns, + Column_settings* colSettings) + : dim_opt(-1), + pair_opt(), + swap_opt(), + rep_opt(), + ra_opt(numberOfColumns), + nextIndex_(0), + colSettings_(colSettings) +{ + matrix_.reserve(numberOfColumns); + if constexpr (Master_matrix::Option_list::has_map_column_container) { + pivotToColumnIndex_.reserve(numberOfColumns); + } else { + pivotToColumnIndex_.resize(numberOfColumns, -1); + } +} + +template +template +inline Chain_matrix::Chain_matrix(Column_settings* colSettings, + const BirthComparatorFunction& birthComparator, + const DeathComparatorFunction& deathComparator) + : dim_opt(-1), + pair_opt(), + swap_opt(birthComparator, deathComparator), + rep_opt(), + ra_opt(), + nextIndex_(0), + colSettings_(colSettings) +{} + +template +template +inline Chain_matrix::Chain_matrix(const std::vector& orderedBoundaries, + Column_settings* colSettings, + const BirthComparatorFunction& birthComparator, + const DeathComparatorFunction& deathComparator) + : dim_opt(-1), + pair_opt(), + swap_opt(birthComparator, deathComparator), + rep_opt(), + ra_opt(orderedBoundaries.size()), + nextIndex_(0), + colSettings_(colSettings) +{ + matrix_.reserve(orderedBoundaries.size()); + if constexpr (Master_matrix::Option_list::has_map_column_container) { + pivotToColumnIndex_.reserve(orderedBoundaries.size()); + } else { + pivotToColumnIndex_.resize(orderedBoundaries.size(), -1); + } + for (const Boundary_type& b : orderedBoundaries) { + insert_boundary(b); + } +} + +template +template +inline Chain_matrix::Chain_matrix(unsigned int numberOfColumns, + Column_settings* colSettings, + const BirthComparatorFunction& birthComparator, + const DeathComparatorFunction& deathComparator) + : dim_opt(-1), + pair_opt(), + swap_opt(birthComparator, deathComparator), + rep_opt(), + ra_opt(numberOfColumns), + nextIndex_(0), + colSettings_(colSettings) +{ + matrix_.reserve(numberOfColumns); + if constexpr (Master_matrix::Option_list::has_map_column_container) { + pivotToColumnIndex_.reserve(numberOfColumns); + } else { + pivotToColumnIndex_.resize(numberOfColumns, -1); + } +} + +template +inline Chain_matrix::Chain_matrix(const Chain_matrix& matrixToCopy, + Column_settings* colSettings) + : dim_opt(static_cast(matrixToCopy)), + pair_opt(static_cast(matrixToCopy)), + swap_opt(static_cast(matrixToCopy)), + rep_opt(static_cast(matrixToCopy)), + ra_opt(static_cast(matrixToCopy)), + pivotToColumnIndex_(matrixToCopy.pivotToColumnIndex_), + nextIndex_(matrixToCopy.nextIndex_), + colSettings_(colSettings == nullptr ? matrixToCopy.colSettings_ : colSettings) +{ + matrix_.reserve(matrixToCopy.matrix_.size()); + for (const auto& cont : matrixToCopy.matrix_){ + if constexpr (Master_matrix::Option_list::has_map_column_container){ + _container_insert(cont.second, cont.first); + } else { + _container_insert(cont); + } + } +} + +template +inline Chain_matrix::Chain_matrix(Chain_matrix&& other) noexcept + : dim_opt(std::move(static_cast(other))), + pair_opt(std::move(static_cast(other))), + swap_opt(std::move(static_cast(other))), + rep_opt(std::move(static_cast(other))), + ra_opt(std::move(static_cast(other))), + matrix_(std::move(other.matrix_)), + pivotToColumnIndex_(std::move(other.pivotToColumnIndex_)), + nextIndex_(std::exchange(other.nextIndex_, 0)), + colSettings_(std::exchange(other.colSettings_, nullptr)) +{} + +template +template +inline std::vector Chain_matrix::insert_boundary( + const Boundary_type& boundary, dimension_type dim) +{ + return insert_boundary(nextIndex_, boundary, dim); +} + +template +template +inline std::vector Chain_matrix::insert_boundary( + id_index faceID, const Boundary_type& boundary, dimension_type dim) +{ + if constexpr (!Master_matrix::Option_list::has_map_column_container) { + if (pivotToColumnIndex_.size() <= faceID) { + pivotToColumnIndex_.resize(faceID * 2 + 1, -1); + } + } + + if constexpr (Master_matrix::Option_list::has_vine_update && Master_matrix::Option_list::has_column_pairings) { + if constexpr (Master_matrix::Option_list::has_map_column_container) { + swap_opt::CP::pivotToPosition_.try_emplace(faceID, _nextPosition()); + } else { + if (swap_opt::CP::pivotToPosition_.size() <= faceID) + swap_opt::CP::pivotToPosition_.resize(pivotToColumnIndex_.size(), -1); + swap_opt::CP::pivotToPosition_[faceID] = _nextPosition(); + } + } + + if constexpr (Master_matrix::Option_list::has_matrix_maximal_dimension_access) { + dim_opt::update_up(dim == static_cast(-1) ? (boundary.size() == 0 ? 0 : boundary.size() - 1) : dim); + } + + return _reduce_boundary(faceID, boundary, dim); +} + +template +inline typename Chain_matrix::Column_type& Chain_matrix::get_column(index columnIndex) +{ + if constexpr (Master_matrix::Option_list::has_map_column_container) { + return matrix_.at(columnIndex); + } else { + return matrix_[columnIndex]; + } +} + +template +inline const typename Chain_matrix::Column_type& Chain_matrix::get_column( + index columnIndex) const +{ + if constexpr (Master_matrix::Option_list::has_map_column_container) { + return matrix_.at(columnIndex); + } else { + return matrix_[columnIndex]; + } +} + +template +inline void Chain_matrix::remove_maximal_face(id_index faceID) +{ + static_assert(Master_matrix::Option_list::has_removable_columns, + "'remove_maximal_face' is not implemented for the chosen options."); + static_assert(Master_matrix::Option_list::has_map_column_container && + Master_matrix::Option_list::has_vine_update && + Master_matrix::Option_list::has_column_pairings, + "'remove_maximal_face' is not implemented for the chosen options."); + + // TODO: find simple test to verify that col at columnIndex is maximal even without row access. + + const auto& pivotToPosition = swap_opt::CP::pivotToPosition_; + auto it = pivotToPosition.find(faceID); + if (it == pivotToPosition.end()) return; // face does not exists. TODO: put an assert instead? + pos_index startPos = it->second; + index startIndex = pivotToColumnIndex_.at(faceID); + + if (startPos != _nextPosition() - 1) { + std::vector colToSwap; + colToSwap.reserve(matrix_.size()); + + for (auto& p : pivotToPosition) { + if (p.second > startPos) colToSwap.push_back(pivotToColumnIndex_.at(p.first)); + } + std::sort(colToSwap.begin(), colToSwap.end(), [&](index c1, index c2) { + return pivotToPosition.at(get_pivot(c1)) < pivotToPosition.at(get_pivot(c2)); + }); + + for (index i : colToSwap) { + startIndex = swap_opt::vine_swap(startIndex, i); + } + } + + _remove_last(startIndex); +} + +template +inline void Chain_matrix::remove_maximal_face(id_index faceID, + const std::vector& columnsToSwap) +{ + static_assert(Master_matrix::Option_list::has_removable_columns, + "'remove_maximal_face' is not implemented for the chosen options."); + static_assert(Master_matrix::Option_list::has_map_column_container && Master_matrix::Option_list::has_vine_update, + "'remove_maximal_face' is not implemented for the chosen options."); + + // TODO: find simple test to verify that col at columnIndex is maximal even without row access. + + index startIndex = pivotToColumnIndex_.at(faceID); + + for (id_index i : columnsToSwap) { + startIndex = swap_opt::vine_swap(startIndex, pivotToColumnIndex_.at(i)); + } + + _remove_last(startIndex); +} + +template +inline void Chain_matrix::remove_last() +{ + static_assert(Master_matrix::Option_list::has_removable_columns, + "'remove_last' is not implemented for the chosen options."); + static_assert(Master_matrix::Option_list::has_map_column_container || !Master_matrix::Option_list::has_vine_update, + "'remove_last' is not implemented for the chosen options."); + + if (nextIndex_ == 0 || matrix_.empty()) return; // empty matrix + + if constexpr (Master_matrix::Option_list::has_vine_update) { + // carefull: linear because of the search of the last index. It is better to keep track of the @ref IDIdx index + // of the last column while performing swaps (or the @ref MatIdx with the return values of `vine_swap` + get_pivot) + // and then call `remove_maximal_face` with it and an empty `columnsToSwap`. + + id_index pivot = 0; + index colIndex = 0; + for (auto& p : pivotToColumnIndex_) { + if (p.first > pivot) { // pivots have to be strictly increasing in order of filtration + pivot = p.first; + colIndex = p.second; + } + } + _remove_last(colIndex); + } else { + _remove_last(nextIndex_ - 1); + } +} + +template +inline typename Chain_matrix::index Chain_matrix::get_number_of_columns() const +{ + if constexpr (Master_matrix::Option_list::has_map_column_container) { + return matrix_.size(); + } else { + return nextIndex_; // matrix could have been resized much bigger while insert + } +} + +template +inline typename Chain_matrix::dimension_type Chain_matrix::get_column_dimension( + index columnIndex) const +{ + return get_column(columnIndex).get_dimension(); +} + +template +inline void Chain_matrix::add_to(index sourceColumnIndex, index targetColumnIndex) +{ + auto& col = get_column(targetColumnIndex); + _add_to(col, [&]() { col += get_column(sourceColumnIndex); }); +} + +template +inline void Chain_matrix::multiply_target_and_add_to(index sourceColumnIndex, + const Field_element_type& coefficient, + index targetColumnIndex) +{ + auto& col = get_column(targetColumnIndex); + _add_to(col, [&]() { col.multiply_target_and_add(coefficient, get_column(sourceColumnIndex)); }); +} + +template +inline void Chain_matrix::multiply_source_and_add_to(const Field_element_type& coefficient, + index sourceColumnIndex, + index targetColumnIndex) +{ + auto& col = get_column(targetColumnIndex); + _add_to(col, [&]() { col.multiply_source_and_add(get_column(sourceColumnIndex), coefficient); }); +} + +template +inline bool Chain_matrix::is_zero_cell(index columnIndex, id_index rowIndex) const +{ + return !get_column(columnIndex).is_non_zero(rowIndex); +} + +template +inline bool Chain_matrix::is_zero_column(index columnIndex) +{ + return get_column(columnIndex).is_empty(); +} + +template +inline typename Chain_matrix::index Chain_matrix::get_column_with_pivot( + id_index faceID) const +{ + if constexpr (Master_matrix::Option_list::has_map_column_container) { + return pivotToColumnIndex_.at(faceID); + } else { + return pivotToColumnIndex_[faceID]; + } +} + +template +inline typename Chain_matrix::id_index Chain_matrix::get_pivot(index columnIndex) +{ + return get_column(columnIndex).get_pivot(); +} + +template +inline Chain_matrix& Chain_matrix::operator=(const Chain_matrix& other) +{ + dim_opt::operator=(other); + swap_opt::operator=(other); + pair_opt::operator=(other); + rep_opt::operator=(other); + matrix_.clear(); + pivotToColumnIndex_ = other.pivotToColumnIndex_; + nextIndex_ = other.nextIndex_; + colSettings_ = other.colSettings_; + + matrix_.reserve(other.matrix_.size()); + for (const auto& cont : other.matrix_){ + if constexpr (Master_matrix::Option_list::has_map_column_container){ + _container_insert(cont.second, cont.first); + } else { + _container_insert(cont); + } + } + + return *this; +} + +template +inline void Chain_matrix::print() const +{ + std::cout << "Column Matrix:\n"; + if constexpr (!Master_matrix::Option_list::has_map_column_container) { + for (id_index i = 0; i < pivotToColumnIndex_.size() && pivotToColumnIndex_[i] != static_cast(-1); ++i) { + index pos = pivotToColumnIndex_[i]; + const Column_type& col = matrix_[pos]; + for (const auto& cell : col) { + std::cout << cell.get_row_index() << " "; + } + std::cout << "(" << i << ", " << pos << ")\n"; + } + if constexpr (Master_matrix::Option_list::has_row_access) { + std::cout << "\n"; + std::cout << "Row Matrix:\n"; + for (id_index i = 0; i < pivotToColumnIndex_.size() && pivotToColumnIndex_[i] != static_cast(-1); ++i) { + index pos = pivotToColumnIndex_[i]; + const Row_type& row = ra_opt::get_row(pos); + for (const auto& cell : row) { + std::cout << cell.get_column_index() << " "; + } + std::cout << "(" << i << ", " << pos << ")\n"; + } + } + } else { + for (const auto& p : pivotToColumnIndex_) { + const Column_type& col = matrix_.at(p.second); + for (const auto& cell : col) { + std::cout << cell.get_row_index() << " "; + } + std::cout << "(" << p.first << ", " << p.second << ")\n"; + } + if constexpr (Master_matrix::Option_list::has_row_access) { + std::cout << "\n"; + std::cout << "Row Matrix:\n"; + for (const auto& p : pivotToColumnIndex_) { + const Row_type& row = ra_opt::get_row(p.first); + for (const auto& cell : row) { + std::cout << cell.get_column_index() << " "; + } + std::cout << "(" << p.first << ", " << p.second << ")\n"; + } + } + } + std::cout << "\n"; +} + +template +template +inline std::vector Chain_matrix::_reduce_boundary( + id_index faceID, const Boundary_type& boundary, dimension_type dim) +{ + tmp_column_type column(boundary.begin(), boundary.end()); + if (dim == static_cast(-1)) dim = boundary.begin() == boundary.end() ? 0 : boundary.size() - 1; + std::vector chainsInH; // for corresponding indices in H (paired columns) + std::vector chainsInF; // for corresponding indices in F (unpaired, essential columns) + + auto get_last = [&column]() { + if constexpr (Master_matrix::Option_list::is_z2) + return *(column.rbegin()); + else + return column.rbegin()->first; + }; + + if (boundary.begin() == boundary.end()) { + if constexpr (Master_matrix::Option_list::is_z2) + column.insert(faceID); + else + column.emplace(faceID, 1); + _insert_chain(column, dim); + return chainsInF; + } + + index currentIndex = get_column_with_pivot(get_last()); + + while (get_column(currentIndex).is_paired()) { + _reduce_by_G(column, chainsInH, currentIndex); + + if (column.empty()) { + // produce the sum of all col_h in chains_in_H + _build_from_H(faceID, column, chainsInH); + // create a new cycle (in F) sigma - \sum col_h + _insert_chain(column, dim); + return chainsInF; + } + + currentIndex = get_column_with_pivot(get_last()); + } + + while (!column.empty()) { + currentIndex = get_column_with_pivot(get_last()); + + if (!get_column(currentIndex).is_paired()) { + // only fills currentEssentialCycleIndices if Z2 coefficients, so chainsInF remains empty + _reduce_by_F(column, chainsInF, currentIndex); + } else { + _reduce_by_G(column, chainsInH, currentIndex); + } + } + + _update_largest_death_in_F(chainsInF); + + // Compute the new column zzsh + \sum col_h, for col_h in chains_in_H + _build_from_H(faceID, column, chainsInH); + + // Create and insert (\sum col_h) + sigma (in H, paired with chain_fp) in matrix_ + if constexpr (Master_matrix::Option_list::is_z2) + _insert_chain(column, dim, chainsInF[0]); + else + _insert_chain(column, dim, chainsInF[0].first); + + return chainsInF; +} + +template +inline void Chain_matrix::_reduce_by_G(tmp_column_type& column, + std::vector& chainsInH, + index currentIndex) +{ + Column_type& col = get_column(currentIndex); + if constexpr (Master_matrix::Option_list::is_z2) { + _add_to(col, column, 1u); // Reduce with the column col_g + chainsInH.push_back(col.get_paired_chain_index()); // keep the col_h with which col_g is paired + } else { + Field_element_type coef = col.get_pivot_value(); + auto& operators = colSettings_->operators; + coef = operators.get_inverse(coef); + operators.multiply_inplace(coef, operators.get_characteristic() - column.rbegin()->second); + + _add_to(col, column, coef); // Reduce with the column col_g + chainsInH.emplace_back(col.get_paired_chain_index(), coef); // keep the col_h with which col_g is paired + } +} + +template +inline void Chain_matrix::_reduce_by_F(tmp_column_type& column, + std::vector& chainsInF, + index currentIndex) +{ + Column_type& col = get_column(currentIndex); + if constexpr (Master_matrix::Option_list::is_z2) { + _add_to(col, column, 1u); // Reduce with the column col_g + chainsInF.push_back(currentIndex); + } else { + Field_element_type coef = col.get_pivot_value(); + auto& operators = colSettings_->operators; + coef = operators.get_inverse(coef); + operators.multiply_inplace(coef, operators.get_characteristic() - column.rbegin()->second); + + _add_to(col, column, coef); // Reduce with the column col_g + chainsInF.emplace_back(currentIndex, operators.get_characteristic() - coef); + } +} + +template +inline void Chain_matrix::_build_from_H(id_index faceID, + tmp_column_type& column, + std::vector& chainsInH) +{ + if constexpr (Master_matrix::Option_list::is_z2) { + column.insert(faceID); + for (index idx_h : chainsInH) { + _add_to(get_column(idx_h), column, 1u); + } + } else { + column.emplace(faceID, 1); + for (std::pair& idx_h : chainsInH) { + _add_to(get_column(idx_h.first), column, idx_h.second); + } + } +} + +template +inline void Chain_matrix::_update_largest_death_in_F(const std::vector& chainsInF) +{ + if constexpr (Master_matrix::Option_list::is_z2) { + index toUpdate = chainsInF[0]; + for (auto other_col_it = chainsInF.begin() + 1; other_col_it != chainsInF.end(); ++other_col_it) { + add_to(*other_col_it, toUpdate); + } + } else { + index toUpdate = chainsInF[0].first; + get_column(toUpdate) *= chainsInF[0].second; + for (auto other_col_it = chainsInF.begin() + 1; other_col_it != chainsInF.end(); ++other_col_it) { + multiply_source_and_add_to(other_col_it->second, other_col_it->first, toUpdate); + } + } +} + +template +inline void Chain_matrix::_insert_chain(const tmp_column_type& column, dimension_type dimension) +{ + _container_insert(column, nextIndex_, dimension); + _add_bar(dimension); + + ++nextIndex_; +} + +template +inline void Chain_matrix::_insert_chain(const tmp_column_type& column, + dimension_type dimension, + index pair) +{ + // true when no vine updates and if nextIndex_ is updated in remove_last for special case of no vines + // because then @ref PosIdx == @ref MatIdx + pos_index pairPos = pair; + + _container_insert(column, nextIndex_, dimension); + + get_column(nextIndex_).assign_paired_chain(pair); + auto& pairCol = get_column(pair); + pairCol.assign_paired_chain(nextIndex_); + + if constexpr (Master_matrix::Option_list::has_column_pairings && Master_matrix::Option_list::has_vine_update) { + pairPos = swap_opt::CP::pivotToPosition_[pairCol.get_pivot()]; + } + + _update_barcode(pairPos); + + ++nextIndex_; +} + +template +inline void Chain_matrix::_add_to(const Column_type& column, + tmp_column_type& set, + [[maybe_unused]] unsigned int coef) +{ + if constexpr (Master_matrix::Option_list::is_z2) { + std::pair::iterator, bool> res_insert; + for (const Cell& cell : column) { + res_insert = set.insert(cell.get_row_index()); + if (!res_insert.second) { + set.erase(res_insert.first); + } + } + } else { + auto& operators = colSettings_->operators; + for (const Cell& cell : column) { + auto res = set.emplace(cell.get_row_index(), cell.get_element()); + if (res.second){ + operators.multiply_inplace(res.first->second, coef); + } else { + operators.multiply_and_add_inplace_back(cell.get_element(), coef, res.first->second); + if (res.first->second == Field_operators::get_additive_identity()) { + set.erase(res.first); + } + } + } + } +} + +template +template +inline void Chain_matrix::_add_to(Column_type& target, F&& addition) +{ + auto pivot = target.get_pivot(); + addition(); + + if (pivot != target.get_pivot()) { + if constexpr (Master_matrix::Option_list::has_map_column_container) { + std::swap(pivotToColumnIndex_.at(pivot), pivotToColumnIndex_.at(target.get_pivot())); + } else { + std::swap(pivotToColumnIndex_[pivot], pivotToColumnIndex_[target.get_pivot()]); + } + } +} + +template +inline void Chain_matrix::_remove_last(index lastIndex) +{ + static_assert(Master_matrix::Option_list::has_removable_columns, + "'_remove_last' is not implemented for the chosen options."); + static_assert(Master_matrix::Option_list::has_map_column_container || !Master_matrix::Option_list::has_vine_update, + "'_remove_last' is not implemented for the chosen options."); + + id_index pivot; + + if constexpr (Master_matrix::Option_list::has_map_column_container) { + auto itToErase = matrix_.find(lastIndex); + Column_type& colToErase = itToErase->second; + pivot = colToErase.get_pivot(); + + if constexpr (Master_matrix::Option_list::has_matrix_maximal_dimension_access) { + dim_opt::update_down(colToErase.get_dimension()); + } + + if (colToErase.is_paired()) matrix_.at(colToErase.get_paired_chain_index()).unassign_paired_chain(); + pivotToColumnIndex_.erase(pivot); + matrix_.erase(itToErase); + } else { + GUDHI_CHECK(lastIndex == nextIndex_ - 1 && nextIndex_ == matrix_.size(), + std::logic_error("Chain_matrix::_remove_last - Indexation problem.")); + + Column_type& colToErase = matrix_[lastIndex]; + pivot = colToErase.get_pivot(); + + if constexpr (Master_matrix::Option_list::has_matrix_maximal_dimension_access) { + dim_opt::update_down(colToErase.get_dimension()); + } + + if (colToErase.is_paired()) matrix_.at(colToErase.get_paired_chain_index()).unassign_paired_chain(); + pivotToColumnIndex_[pivot] = -1; + matrix_.pop_back(); + // TODO: resize matrix_ when a lot is removed? Could be not the best strategy if user inserts a lot back afterwards. + } + + if constexpr (!Master_matrix::Option_list::has_vine_update) { + --nextIndex_; // should not be updated when there are vine updates, as possibly lastIndex != nextIndex - 1 + } + + if constexpr (Master_matrix::Option_list::has_column_pairings) { + auto it = _indexToBar().find(--_nextPosition()); + typename barcode_type::iterator bar = it->second; + + if (bar->death == static_cast(-1)) + _barcode().erase(bar); + else + bar->death = -1; + + _indexToBar().erase(it); + if constexpr (Master_matrix::Option_list::has_vine_update) swap_opt::CP::pivotToPosition_.erase(pivot); + } + + if constexpr (Master_matrix::Option_list::has_row_access) { + GUDHI_CHECK( + ra_opt::get_row(pivot).size() == 0, + std::invalid_argument( + "Chain_matrix::_remove_last - Column asked to be removed does not corresponds to a maximal simplex.")); + if constexpr (Master_matrix::Option_list::has_removable_rows) { + ra_opt::erase_empty_row(pivot); + } + } +} + +template +inline void Chain_matrix::_update_barcode(pos_index birth) +{ + if constexpr (Master_matrix::Option_list::has_column_pairings) { + if constexpr (Master_matrix::Option_list::has_removable_columns) { + auto& barIt = _indexToBar().at(birth); + barIt->death = _nextPosition(); + _indexToBar().try_emplace(_nextPosition(), barIt); // list so iterators are stable + } else { + _barcode()[_indexToBar()[birth]].death = _nextPosition(); + _indexToBar().push_back(_indexToBar()[birth]); + } + ++_nextPosition(); + } +} + +template +inline void Chain_matrix::_add_bar(dimension_type dim) +{ + if constexpr (Master_matrix::Option_list::has_column_pairings) { + _barcode().emplace_back(dim, _nextPosition(), -1); + if constexpr (Master_matrix::Option_list::has_removable_columns) { + _indexToBar().try_emplace(_nextPosition(), --_barcode().end()); + } else { + _indexToBar().push_back(_barcode().size() - 1); + } + ++_nextPosition(); + } +} + +template +template +inline void Chain_matrix::_container_insert(const Container_type& column, + index pos, + dimension_type dim) +{ + id_index pivot; + if constexpr (Master_matrix::Option_list::is_z2) { + pivot = *(column.rbegin()); + } else { + pivot = column.rbegin()->first; + } + if constexpr (Master_matrix::Option_list::has_map_column_container) { + pivotToColumnIndex_.try_emplace(pivot, pos); + if constexpr (Master_matrix::Option_list::has_row_access) { + matrix_.try_emplace(pos, Column_type(pos, column, dim, ra_opt::rows_, colSettings_)); + } else { + matrix_.try_emplace(pos, Column_type(column, dim, colSettings_)); + } + } else { + if constexpr (Master_matrix::Option_list::has_row_access) { + matrix_.emplace_back(pos, column, dim, ra_opt::rows_, colSettings_); + } else { + matrix_.emplace_back(column, dim, colSettings_); + } + pivotToColumnIndex_[pivot] = pos; + } +} + +template +inline void Chain_matrix::_container_insert(const Column_type& column, [[maybe_unused]] index pos) +{ + if constexpr (Master_matrix::Option_list::has_map_column_container) { + if constexpr (Master_matrix::Option_list::has_row_access) { + matrix_.try_emplace(pos, Column_type(column, column.get_column_index(), ra_opt::rows_, colSettings_)); + } else { + matrix_.try_emplace(pos, Column_type(column, colSettings_)); + } + } else { + if constexpr (Master_matrix::Option_list::has_row_access) { + matrix_.emplace_back(column, column.get_column_index(), ra_opt::rows_, colSettings_); + } else { + matrix_.emplace_back(column, colSettings_); + } + } +} + +template +inline constexpr typename Chain_matrix::barcode_type& Chain_matrix::_barcode() +{ + if constexpr (Master_matrix::Option_list::has_vine_update) + return swap_opt::template Chain_pairing::barcode_; + else + return pair_opt::barcode_; +} + +template +inline constexpr typename Chain_matrix::bar_dictionnary_type& +Chain_matrix::_indexToBar() +{ + if constexpr (Master_matrix::Option_list::has_vine_update) + return swap_opt::template Chain_pairing::indexToBar_; + else + return pair_opt::indexToBar_; +} + +template +inline constexpr typename Chain_matrix::pos_index& Chain_matrix::_nextPosition() +{ + if constexpr (Master_matrix::Option_list::has_vine_update) + return swap_opt::template Chain_pairing::nextPosition_; + else + return pair_opt::nextPosition_; +} + +} // namespace persistence_matrix +} // namespace Gudhi + +#endif // PM_CHAIN_MATRIX_H diff --git a/src/Persistence_matrix/include/gudhi/Persistence_matrix/chain_pairing.h b/src/Persistence_matrix/include/gudhi/Persistence_matrix/chain_pairing.h new file mode 100644 index 0000000000..50d0c64867 --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Persistence_matrix/chain_pairing.h @@ -0,0 +1,134 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file chain_pairing.h + * @author Hannah Schreiber + * @brief Contains the @ref Chain_pairing class and @ref Dummy_chain_pairing structure. + */ + +#ifndef PM_CHAIN_PAIRING_H +#define PM_CHAIN_PAIRING_H + +#include //std::move + +namespace Gudhi { +namespace persistence_matrix { + +/** + * @ingroup persistence_matrix + * + * @brief Empty structure. + * Inheritated instead of @ref Chain_pairing, when the computation of the barcode was not enabled or if the pairing + * is already managed by the vine update classes. + */ +struct Dummy_chain_pairing { + friend void swap([[maybe_unused]] Dummy_chain_pairing& d1, [[maybe_unused]] Dummy_chain_pairing& d2) {} +}; + +/** + * @class Chain_pairing chain_pairing.h gudhi/Persistence_matrix/chain_pairing.h + * @ingroup persistence_matrix + * + * @brief Class managing the barcode for @ref Chain_matrix if the option was enabled. + * + * @tparam Master_matrix An instanciation of @ref Matrix from which all types and options are deduced. + */ +template +class Chain_pairing +{ + public: + using barcode_type = typename Master_matrix::barcode_type; /**< Barcode type. */ + using dimension_type = typename Master_matrix::dimension_type; /**< Dimension value type. */ + + /** + * @brief Default constructor. + */ + Chain_pairing(); + /** + * @brief Copy constructor. + * + * @param matrixToCopy Matrix to copy. + */ + Chain_pairing(const Chain_pairing& matrixToCopy); + /** + * @brief Move constructor. + * + * @param other Matrix to move. + */ + Chain_pairing(Chain_pairing&& other) noexcept; + + /** + * @brief Returns the current barcode which is maintained at any insertion, removal or vine swap. + * + * @return Const reference to the barcode. + */ + const barcode_type& get_current_barcode() const; + + /** + * @brief Assign operator. + */ + Chain_pairing& operator=(Chain_pairing other); + /** + * @brief Swap operator. + */ + friend void swap(Chain_pairing& pairing1, Chain_pairing& pairing2) { + pairing1.barcode_.swap(pairing2.barcode_); + pairing1.indexToBar_.swap(pairing2.indexToBar_); + std::swap(pairing1.nextPosition_, pairing2.nextPosition_); + } + + protected: + using dictionnary_type = typename Master_matrix::bar_dictionnary_type; + using pos_index = typename Master_matrix::pos_index; + + barcode_type barcode_; /**< Bar container. */ + dictionnary_type indexToBar_; /**< Map from @ref MatIdx index to bar index. */ + pos_index nextPosition_; /**< Next relative position in the filtration. */ +}; + +template +inline Chain_pairing::Chain_pairing() : nextPosition_(0) +{} + +template +inline Chain_pairing::Chain_pairing(const Chain_pairing& matrixToCopy) + : barcode_(matrixToCopy.barcode_), + indexToBar_(matrixToCopy.indexToBar_), + nextPosition_(matrixToCopy.nextPosition_) +{} + +template +inline Chain_pairing::Chain_pairing(Chain_pairing&& other) noexcept + : barcode_(std::move(other.barcode_)), + indexToBar_(std::move(other.indexToBar_)), + nextPosition_(std::exchange(other.nextPosition_, 0)) +{} + +template +inline const typename Chain_pairing::barcode_type& Chain_pairing::get_current_barcode() + const +{ + return barcode_; +} + +template +inline Chain_pairing& Chain_pairing::operator=(Chain_pairing other) +{ + barcode_.swap(other.barcode_); + indexToBar_.swap(other.indexToBar_); + std::swap(nextPosition_, other.nextPosition_); + return *this; +} + +} // namespace persistence_matrix +} // namespace Gudhi + +#endif // PM_CHAIN_PAIRING_H diff --git a/src/Persistence_matrix/include/gudhi/Persistence_matrix/chain_rep_cycles.h b/src/Persistence_matrix/include/gudhi/Persistence_matrix/chain_rep_cycles.h new file mode 100644 index 0000000000..e23c356254 --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Persistence_matrix/chain_rep_cycles.h @@ -0,0 +1,188 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file chain_rep_cycles.h + * @author Hannah Schreiber + * @brief Contains the @ref Chain_representative_cycles class and @ref Dummy_chain_representative_cycles structure. + */ + +#ifndef PM_CHAIN_REP_CYCLES_H +#define PM_CHAIN_REP_CYCLES_H + +#include //std::move +#include //std::sort +#include + +namespace Gudhi { +namespace persistence_matrix { + +/** + * @ingroup persistence_matrix + * + * @brief Empty structure. + * Inheritated instead of @ref Chain_representative_cycles, when the computation of the representative cycles + * were not enabled. + */ +struct Dummy_chain_representative_cycles { + friend void swap([[maybe_unused]] Dummy_chain_representative_cycles& d1, + [[maybe_unused]] Dummy_chain_representative_cycles& d2) {} +}; + +// TODO: add coefficients ? Only Z2 token into account for now. +/** + * @class Chain_representative_cycles chain_rep_cycles.h gudhi/Persistence_matrix/chain_rep_cycles.h + * @ingroup persistence_matrix + * + * @brief Class managing the representative cycles for @ref Chain_matrix if the option was enabled. + * + * @tparam Master_matrix An instanciation of @ref Matrix from which all types and options are deduced. + */ +template +class Chain_representative_cycles +{ + public: + using Bar = typename Master_matrix::Bar; /**< Bar type. */ + using cycle_type = typename Master_matrix::cycle_type; /**< Cycle type. */ + using matrix_type = typename Master_matrix::column_container_type; /**< Column container type. */ + + /** + * @brief Default constructor. + */ + Chain_representative_cycles(); + /** + * @brief Copy constructor. + * + * @param matrixToCopy Matrix to copy. + */ + Chain_representative_cycles(const Chain_representative_cycles& matrixToCopy); + /** + * @brief Move constructor. + * + * @param other Matrix to move. + */ + Chain_representative_cycles(Chain_representative_cycles&& other) noexcept; + + /** + * @brief Computes the current representative cycles of the matrix. + */ + void update_representative_cycles(); + + /** + * @brief Returns the current representative cycles. If the matrix is modified later after the first call, + * @ref update_representative_cycles has to be called to update the returned cycles. + * + * @return A const reference to a vector of @ref Matrix::cycle_type containing all representative cycles. + */ + const std::vector& get_representative_cycles(); + /** + * @brief Returns the representative cycle corresponding to the given bar. + * If the matrix is modified later after the first call, + * @ref update_representative_cycles has to be called to update the returned cycles. + * + * @param bar Bar corresponding to the wanted representative cycle. + * @return A const reference to the representative cycle. + */ + const cycle_type& get_representative_cycle(const Bar& bar); + + /** + * @brief Assign operator. + */ + Chain_representative_cycles& operator=(Chain_representative_cycles other); + /** + * @brief Swap operator. + */ + friend void swap(Chain_representative_cycles& base1, Chain_representative_cycles& base2) { + base1.representativeCycles_.swap(base2.representativeCycles_); + base1.birthToCycle_.swap(base2.birthToCycle_); + } + + private: + using chain_matrix = typename Master_matrix::Chain_matrix_type; + + std::vector representativeCycles_; /**< Cycle container. */ + std::vector birthToCycle_; /**< Map from birth index to cycle index. */ + + //access to inheritating Chain_matrix class + constexpr chain_matrix* _matrix() { return static_cast(this); } + constexpr const chain_matrix* _matrix() const { return static_cast(this); } +}; + +template +inline Chain_representative_cycles::Chain_representative_cycles() +{} + +template +inline Chain_representative_cycles::Chain_representative_cycles( + const Chain_representative_cycles& matrixToCopy) + : representativeCycles_(matrixToCopy.representativeCycles_), birthToCycle_(matrixToCopy.birthToCycle_) +{} + +template +inline Chain_representative_cycles::Chain_representative_cycles( + Chain_representative_cycles&& other) noexcept + : representativeCycles_(std::move(other.representativeCycles_)), birthToCycle_(std::move(other.birthToCycle_)) +{} + +template +inline void Chain_representative_cycles::update_representative_cycles() +{ + birthToCycle_.clear(); + birthToCycle_.resize(_matrix()->get_number_of_columns(), -1); + representativeCycles_.clear(); + + // for birthToCycle_, assumes that @ref PosIdx == @ref IDIdx, ie pivot == birth index... which is not true with vineyards + // TODO: with vineyard, there is a @ref IDIdx --> @ref PosIdx map stored. somehow get access to it here + for (typename Master_matrix::id_index i = 0; i < _matrix()->get_number_of_columns(); i++) { + auto& col = _matrix()->get_column(_matrix()->get_column_with_pivot(i)); + if (!col.is_paired() || i < col.get_paired_chain_index()) { + cycle_type cycle; + for (auto& c : col) { + cycle.push_back(c.get_row_index()); + } + if constexpr (std::is_same_v || + std::is_same_v) + std::sort(cycle.begin(), cycle.end()); + representativeCycles_.push_back(cycle); + birthToCycle_[i] = representativeCycles_.size() - 1; + } + } +} + +template +inline const std::vector::cycle_type>& +Chain_representative_cycles::get_representative_cycles() +{ + if (representativeCycles_.empty()) update_representative_cycles(); + return representativeCycles_; +} + +template +inline const typename Chain_representative_cycles::cycle_type& +Chain_representative_cycles::get_representative_cycle(const Bar& bar) +{ + if (representativeCycles_.empty()) update_representative_cycles(); + return representativeCycles_[birthToCycle_[bar.birth]]; +} + +template +inline Chain_representative_cycles& Chain_representative_cycles::operator=( + Chain_representative_cycles other) +{ + representativeCycles_.swap(other.representativeCycles_); + birthToCycle_.swap(other.birthToCycle_); + return *this; +} + +} // namespace persistence_matrix +} // namespace Gudhi + +#endif // PM_CHAIN_REP_CYCLES_H diff --git a/src/Persistence_matrix/include/gudhi/Persistence_matrix/chain_vine_swap.h b/src/Persistence_matrix/include/gudhi/Persistence_matrix/chain_vine_swap.h new file mode 100644 index 0000000000..f2f3c4bc13 --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Persistence_matrix/chain_vine_swap.h @@ -0,0 +1,611 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file chain_vine_swap.h + * @author Hannah Schreiber + * @brief Contains the @ref Chain_barcode_swap and @ref Chain_vine_swap classes, as well as the + * @ref Dummy_chain_vine_swap and @ref Dummy_chain_vine_pairing structures. + */ + +#ifndef PM_CHAIN_VINE_SWAP_H +#define PM_CHAIN_VINE_SWAP_H + +#include //std::swap & std::move +#include +#include //std::function +#include //std::invalid_argument + +#include "chain_pairing.h" + +namespace Gudhi { +namespace persistence_matrix { + +/** + * @ingroup persistence_matrix + * + * @brief Default death comparator. Simply assumes that two positive paired columns are never swapped. Which is true + * for the use case in zigzag persistence for example. + * + * @param columnIndex1 First column index. + * @param columnIndex2 Second column index. + * @return false + */ +constexpr bool _no_G_death_comparator([[maybe_unused]] unsigned int columnIndex1, + [[maybe_unused]] unsigned int columnIndex2) +{ + return false; +} + +/** + * @ingroup persistence_matrix + * + * @brief Empty structure. + * Inheritated instead of @ref Chain_vine_swap, when vine swappes are not enabled. + */ +struct Dummy_chain_vine_swap { + friend void swap([[maybe_unused]] Dummy_chain_vine_swap& d1, [[maybe_unused]] Dummy_chain_vine_swap& d2) {} + + Dummy_chain_vine_swap() {} + template + Dummy_chain_vine_swap([[maybe_unused]] const BirthComparatorFunction& birthComparator, + [[maybe_unused]] const DeathComparatorFunction& deathComparator) {} +}; + +/** + * @ingroup persistence_matrix + * + * @brief Empty structure. + * Inheritated instead of @ref Chain_barcode_swap, when the barcode is not stored. + */ +struct Dummy_chain_vine_pairing { + friend void swap([[maybe_unused]] Dummy_chain_vine_pairing& d1, [[maybe_unused]] Dummy_chain_vine_pairing& d2) {} +}; + +/** + * @ingroup persistence_matrix + * + * @brief Class managing the barcode for @ref Chain_vine_swap. + * + * @tparam Master_matrix An instanciation of @ref Matrix from which all types and options are deduced. + */ +template +class Chain_barcode_swap : public Chain_pairing +{ + public: + using id_index = typename Master_matrix::id_index; /**< @ref IDIdx index type. */ + using pos_index = typename Master_matrix::pos_index; /**< @ref PosIdx index type. */ + using CP = Chain_pairing; + + /** + * @brief Default constructor. + */ + Chain_barcode_swap(){}; + /** + * @brief Copy constructor. + * + * @param toCopy Matrix to copy. + */ + Chain_barcode_swap(const Chain_barcode_swap& toCopy) + : CP(static_cast(toCopy)), pivotToPosition_(toCopy.pivotToPosition_){}; + /** + * @brief Move constructor. + * + * @param other Matrix to move. + */ + Chain_barcode_swap(Chain_barcode_swap&& other) + : CP(std::move(static_cast(other))), pivotToPosition_(std::move(other.pivotToPosition_)){}; + + protected: + using dictionnary_type = typename Master_matrix::template dictionnary_type; + + dictionnary_type pivotToPosition_; // necessary to keep track of the barcode changes + + void swap_positions(id_index pivot1, id_index pivot2) { + if constexpr (Master_matrix::Option_list::has_map_column_container) { + std::swap(pivotToPosition_.at(pivot1), pivotToPosition_.at(pivot2)); + } else { + std::swap(pivotToPosition_[pivot1], pivotToPosition_[pivot2]); + } + } + + bool is_negative_in_pair(id_index pivot) const { + pos_index pos = _get_pivot_position(pivot); + return death(pivot) == pos; + } + + void positive_transpose(id_index pivot1, id_index pivot2) { + pos_index pos1 = _get_pivot_position(pivot1); + pos_index pos2 = _get_pivot_position(pivot2); + + _birth(pos1) = pos2; + _birth(pos2) = pos1; + std::swap(CP::indexToBar_.at(pos1), CP::indexToBar_.at(pos2)); + } + + void negative_transpose(id_index pivot1, id_index pivot2) { + pos_index pos1 = _get_pivot_position(pivot1); + pos_index pos2 = _get_pivot_position(pivot2); + + _death(pos1) = pos2; + _death(pos2) = pos1; + std::swap(CP::indexToBar_.at(pos1), CP::indexToBar_.at(pos2)); + } + + void positive_negative_transpose(id_index pivot1, id_index pivot2) { + pos_index pos1 = _get_pivot_position(pivot1); + pos_index pos2 = _get_pivot_position(pivot2); + + _birth(pos1) = pos2; + _death(pos2) = pos1; + std::swap(CP::indexToBar_.at(pos1), CP::indexToBar_.at(pos2)); + } + + void negative_positive_transpose(id_index pivot1, id_index pivot2) { + pos_index pos1 = _get_pivot_position(pivot1); + pos_index pos2 = _get_pivot_position(pivot2); + + _death(pos1) = pos2; + _birth(pos2) = pos1; + std::swap(CP::indexToBar_.at(pos1), CP::indexToBar_.at(pos2)); + } + + bool are_adjacent(id_index pivot1, id_index pivot2) const { + pos_index pos1 = _get_pivot_position(pivot1); + pos_index pos2 = _get_pivot_position(pivot2); + return pos1 < pos2 ? (pos2 - pos1) == 1 : (pos1 - pos2) == 1; + } + + Chain_barcode_swap& operator=(Chain_barcode_swap other) { + Chain_pairing::operator=(other); + pivotToPosition_.swap(other.pivotToPosition_); + } + friend void swap(Chain_barcode_swap& swap1, Chain_barcode_swap& swap2) { + swap(static_cast&>(swap1), static_cast&>(swap2)); + swap1.pivotToPosition_.swap(swap2.pivotToPosition_); + } + + pos_index death(id_index pivot) const { + pos_index simplexIndex = _get_pivot_position(pivot); + + if constexpr (Master_matrix::Option_list::has_removable_columns) { + return CP::indexToBar_.at(simplexIndex)->death; + } else { + return CP::barcode_.at(CP::indexToBar_.at(simplexIndex)).death; + } + } + + pos_index birth(id_index pivot) const { + pos_index simplexIndex = _get_pivot_position(pivot); + + if constexpr (Master_matrix::Option_list::has_removable_columns) { + return CP::indexToBar_.at(simplexIndex)->birth; + } else { + return CP::barcode_.at(CP::indexToBar_.at(simplexIndex)).birth; + } + } + + private: + pos_index _get_pivot_position(id_index pivot) const { + if constexpr (Master_matrix::Option_list::has_map_column_container) { + return pivotToPosition_.at( + pivot); // quite often called, make public and pass position instead of pivot to avoid find() everytime? + } else { + return pivotToPosition_[pivot]; + } + } + + pos_index& _death(pos_index simplexIndex) { + if constexpr (Master_matrix::Option_list::has_removable_columns) { + return CP::indexToBar_.at(simplexIndex)->death; + } else { + return CP::barcode_.at(CP::indexToBar_.at(simplexIndex)).death; + } + } + + pos_index& _birth(pos_index simplexIndex) { + if constexpr (Master_matrix::Option_list::has_removable_columns) { + return CP::indexToBar_.at(simplexIndex)->birth; + } else { + return CP::barcode_.at(CP::indexToBar_.at(simplexIndex)).birth; + } + } +}; + +/** + * @class Chain_vine_swap chain_vine_swap.h gudhi/Persistence_matrix/chain_vine_swap.h + * @ingroup persistence_matrix + * + * @brief Class managing the vine swaps for @ref Chain_matrix. + * + * @tparam Master_matrix An instanciation of @ref Matrix from which all types and options are deduced. + */ +template +class Chain_vine_swap : public std::conditional, + Dummy_chain_vine_pairing + >::type +{ + public: + using index = typename Master_matrix::index; /**< @ref MatIdx index type. */ + using id_index = typename Master_matrix::id_index; /**< @ref IDIdx index type. */ + using pos_index = typename Master_matrix::pos_index; /**< @ref PosIdx index type. */ + using matrix_type = typename Master_matrix::column_container_type; /**< Column container type. */ + using Column_type = typename Master_matrix::Column_type; /**< Column type. */ + typedef bool (*EventCompFuncPointer)(pos_index, pos_index); /**< Pointer type for birth/death comparators. */ + + /** + * @brief Default constructor. Only available if @ref PersistenceMatrixOptions::has_column_pairings is true. + */ + Chain_vine_swap(); + /** + * @brief Constructor storing the given comparators. + * + * @param birthComparator Method taking two @ref PosIdx indices as input and returning true if and only if + * the birth associated to the first position is strictly less than birth associated to + * the second one with respect to some self defined order. It is used while swapping two unpaired or + * two negative columns. + * @param deathComparator Method taking two @ref PosIdx indices as input and returning true if and only if + * the death associated to the first position is strictly less than death associated to + * the second one with respect to some self defined order. It is used while swapping two positive but paired + * columns. Default value: @ref _no_G_death_comparator. + */ + Chain_vine_swap(std::function birthComparator, + std::function deathComparator = _no_G_death_comparator); + /** + * @brief Copy constructor. + * + * @param matrixToCopy Matrix to copy. + */ + Chain_vine_swap(const Chain_vine_swap& matrixToCopy); + /** + * @brief Move constructor. + * + * @param other Matrix to move. + */ + Chain_vine_swap(Chain_vine_swap&& other) noexcept; + + /** + * @brief Does the same than @ref vine_swap, but assumes that the swap is non trivial and + * therefore skips a part of the case study. + * + * @param columnIndex1 @ref MatIdx index of the first face. + * @param columnIndex2 @ref MatIdx index of the second face. + * @return Let \f$ pos1 \f$ be the @ref PosIdx index of @p columnIndex1 and \f$ pos2 \f$ be the @ref PosIdx index of + * @p columnIndex2. The method returns the @ref MatIdx of the column which has now, after the swap, the @ref PosIdx + * \f$ max(pos1, pos2) \f$. + */ + index vine_swap_with_z_eq_1_case(index columnIndex1, index columnIndex2); + /** + * @brief Does a vine swap between two faces which are consecutives in the filtration. Roughly, if \f$ F \f$ is + * the current filtration represented by the matrix, the method modifies the matrix such that the new state + * corresponds to a valid state for the filtration \f$ F' \f$ equal to \f$ F \f$ but with the two given faces + * at swapped positions. Of course, the two faces should not have a face/coface relation which each other ; + * \f$ F' \f$ has to be a valid filtration. + * See @cite vineyards for more information about vine and vineyards. + * + * @param columnIndex1 @ref MatIdx index of the first face. + * @param columnIndex2 @ref MatIdx index of the second face. It is assumed that the @ref PosIdx of both only differs + * by one if the barcode is maintained. + * @return Let \f$ pos1 \f$ be the @ref PosIdx index of @p columnIndex1 and \f$ pos2 \f$ be the @ref PosIdx index of + * @p columnIndex2. The method returns the @ref MatIdx of the column which has now, after the swap, the @ref PosIdx + * \f$ max(pos1, pos2) \f$. + */ + index vine_swap(index columnIndex1, index columnIndex2); + + /** + * @brief Assign operator. + */ + Chain_vine_swap& operator=(Chain_vine_swap other); + /** + * @brief Swap operator. + */ + friend void swap(Chain_vine_swap& swap1, Chain_vine_swap& swap2) { + if constexpr (Master_matrix::Option_list::has_column_pairings) { + swap(static_cast&>(swap1), + static_cast&>(swap2)); + } + std::swap(swap1.birthComp_, swap2.birthComp_); + std::swap(swap1.deathComp_, swap2.deathComp_); + } + + protected: + using CP = typename std::conditional, + Dummy_chain_vine_pairing + >::type; + + private: + using chain_matrix = typename Master_matrix::Chain_matrix_type; + + std::function birthComp_; /**< for F x F & H x H. */ + std::function deathComp_; /**< for G x G. */ + + bool _is_negative_in_pair(index columnIndex); + + index _positive_vine_swap(index columnIndex1, index columnIndex2); + index _positive_negative_vine_swap(index columnIndex1, index columnIndex2); + index _negative_positive_vine_swap(index columnIndex1, index columnIndex2); + index _negative_vine_swap(index columnIndex1, index columnIndex2); + + constexpr chain_matrix* _matrix() { return static_cast(this); } + constexpr const chain_matrix* _matrix() const { return static_cast(this); } +}; + +template +inline Chain_vine_swap::Chain_vine_swap() : CP(), birthComp_(), deathComp_() +{ + static_assert(Master_matrix::Option_list::has_column_pairings, + "If barcode is not stored, at least a birth comparator has to be specified."); +} + +template +inline Chain_vine_swap::Chain_vine_swap(std::function birthComparator, + std::function deathComparator) + : CP(), birthComp_(std::move(birthComparator)), deathComp_(std::move(deathComparator)) +{} + +template +inline Chain_vine_swap::Chain_vine_swap(const Chain_vine_swap& matrixToCopy) + : CP(static_cast(matrixToCopy)), + birthComp_(matrixToCopy.birthComp_), + deathComp_(matrixToCopy.deathComp_) +{} + +template +inline Chain_vine_swap::Chain_vine_swap(Chain_vine_swap&& other) noexcept + : CP(std::move(static_cast(other))), + birthComp_(std::move(other.birthComp_)), + deathComp_(std::move(other.deathComp_)) +{} + +template +inline typename Chain_vine_swap::index Chain_vine_swap::vine_swap_with_z_eq_1_case( + index columnIndex1, index columnIndex2) +{ + const bool col1IsNeg = _is_negative_in_pair(columnIndex1); + const bool col2IsNeg = _is_negative_in_pair(columnIndex2); + + if (col1IsNeg && col2IsNeg) return _negative_vine_swap(columnIndex1, columnIndex2); + + if (col1IsNeg) return _negative_positive_vine_swap(columnIndex1, columnIndex2); + + if (col2IsNeg) return _positive_negative_vine_swap(columnIndex1, columnIndex2); + + return _positive_vine_swap(columnIndex1, columnIndex2); +} + +template +inline typename Chain_vine_swap::index Chain_vine_swap::vine_swap(index columnIndex1, + index columnIndex2) +{ + if constexpr (Master_matrix::Option_list::has_column_pairings) { + GUDHI_CHECK(CP::are_adjacent(_matrix()->get_pivot(columnIndex1), _matrix()->get_pivot(columnIndex2)), + std::invalid_argument( + "Chain_vine_swap::vine_swap - Columns to be swaped need to be adjacent in the 'real' matrix.")); + } + + const bool col1IsNeg = _is_negative_in_pair(columnIndex1); + const bool col2IsNeg = _is_negative_in_pair(columnIndex2); + + if (col1IsNeg && col2IsNeg) { + if (_matrix()->is_zero_cell(columnIndex2, _matrix()->get_pivot(columnIndex1))) { + if constexpr (Master_matrix::Option_list::has_column_pairings) { + id_index pivot1 = _matrix()->get_pivot(columnIndex1); + id_index pivot2 = _matrix()->get_pivot(columnIndex2); + + CP::negative_transpose(pivot1, pivot2); + CP::swap_positions(pivot1, pivot2); + } + return columnIndex1; + } + return _negative_vine_swap(columnIndex1, columnIndex2); + } + + if (col1IsNeg) { + if (_matrix()->is_zero_cell(columnIndex2, _matrix()->get_pivot(columnIndex1))) { + if constexpr (Master_matrix::Option_list::has_column_pairings) { + id_index pivot1 = _matrix()->get_pivot(columnIndex1); + id_index pivot2 = _matrix()->get_pivot(columnIndex2); + + CP::negative_positive_transpose(pivot1, pivot2); + CP::swap_positions(pivot1, pivot2); + } + return columnIndex1; + } + return _negative_positive_vine_swap(columnIndex1, columnIndex2); + } + + if (col2IsNeg) { + if (_matrix()->is_zero_cell(columnIndex2, _matrix()->get_pivot(columnIndex1))) { + if constexpr (Master_matrix::Option_list::has_column_pairings) { + id_index pivot1 = _matrix()->get_pivot(columnIndex1); + id_index pivot2 = _matrix()->get_pivot(columnIndex2); + + CP::positive_negative_transpose(pivot1, pivot2); + CP::swap_positions(pivot1, pivot2); + } + return columnIndex1; + } + return _positive_negative_vine_swap(columnIndex1, columnIndex2); + } + + if (_matrix()->is_zero_cell(columnIndex2, _matrix()->get_pivot(columnIndex1))) { + if constexpr (Master_matrix::Option_list::has_column_pairings) { + id_index pivot1 = _matrix()->get_pivot(columnIndex1); + id_index pivot2 = _matrix()->get_pivot(columnIndex2); + + CP::positive_transpose(pivot1, pivot2); + CP::swap_positions(pivot1, pivot2); + } + return columnIndex1; + } + return _positive_vine_swap(columnIndex1, columnIndex2); +} + +template +inline Chain_vine_swap& Chain_vine_swap::operator=(Chain_vine_swap other) +{ + CP::operator=(other); + std::swap(birthComp_, other.birthComp_); + std::swap(deathComp_, other.deathComp_); + return *this; +} + +template +inline bool Chain_vine_swap::_is_negative_in_pair(index columnIndex) +{ + if constexpr (Master_matrix::Option_list::has_column_pairings) { + return CP::is_negative_in_pair(_matrix()->get_pivot(columnIndex)); + } else { + auto& col = _matrix()->get_column(columnIndex); + if (!col.is_paired()) return false; + return col.get_pivot() > _matrix()->get_pivot(col.get_paired_chain_index()); + } +} + +template +inline typename Chain_vine_swap::index Chain_vine_swap::_positive_vine_swap( + index columnIndex1, index columnIndex2) +{ + auto& col1 = _matrix()->get_column(columnIndex1); + auto& col2 = _matrix()->get_column(columnIndex2); + + if constexpr (Master_matrix::Option_list::has_column_pairings) { + CP::swap_positions(col1.get_pivot(), col2.get_pivot()); + } + // TODO: factorize the cases. But for debug it is much more easier to understand what is happening splitted like this + if (!col1.is_paired()) { // F x * + bool hasSmallerBirth; + if constexpr (Master_matrix::Option_list::has_column_pairings) { + // this order because position were swapped with CP::swap_positions + hasSmallerBirth = (CP::birth(col2.get_pivot()) < CP::birth(col1.get_pivot())); + } else { + hasSmallerBirth = birthComp_(columnIndex1, columnIndex2); + } + + if (!col2.is_paired() && hasSmallerBirth) { + _matrix()->add_to(columnIndex1, columnIndex2); + if constexpr (Master_matrix::Option_list::has_column_pairings) { + CP::positive_transpose(col1.get_pivot(), col2.get_pivot()); + } + return columnIndex1; + } + _matrix()->add_to(columnIndex2, columnIndex1); + + return columnIndex2; + } + + if (!col2.is_paired()) { // G x F + static_cast(this)->add_to(columnIndex1, columnIndex2); + if constexpr (Master_matrix::Option_list::has_column_pairings) { + CP::positive_transpose(col1.get_pivot(), col2.get_pivot()); + } + return columnIndex1; + } + + bool hasSmallerDeath; + if constexpr (Master_matrix::Option_list::has_column_pairings) { + // this order because position were swapped with CP::swap_positions + hasSmallerDeath = (CP::death(col2.get_pivot()) < CP::death(col1.get_pivot())); + } else { + hasSmallerDeath = deathComp_(columnIndex1, columnIndex2); + } + + // G x G + if (hasSmallerDeath) + { + _matrix()->add_to(col1.get_paired_chain_index(), col2.get_paired_chain_index()); + _matrix()->add_to(columnIndex1, columnIndex2); + if constexpr (Master_matrix::Option_list::has_column_pairings) { + CP::positive_transpose(col1.get_pivot(), col2.get_pivot()); + } + return columnIndex1; + } + + _matrix()->add_to(col2.get_paired_chain_index(), col1.get_paired_chain_index()); + _matrix()->add_to(columnIndex2, columnIndex1); + + return columnIndex2; +} + +template +inline typename Chain_vine_swap::index Chain_vine_swap::_positive_negative_vine_swap( + index columnIndex1, index columnIndex2) +{ + _matrix()->add_to(columnIndex1, columnIndex2); + + if constexpr (Master_matrix::Option_list::has_column_pairings) { + id_index pivot1 = _matrix()->get_pivot(columnIndex1); + id_index pivot2 = _matrix()->get_pivot(columnIndex2); + + CP::positive_negative_transpose(pivot1, pivot2); + CP::swap_positions(pivot1, pivot2); + } + + return columnIndex1; +} + +template +inline typename Chain_vine_swap::index Chain_vine_swap::_negative_positive_vine_swap( + index columnIndex1, index columnIndex2) +{ + _matrix()->add_to(columnIndex2, columnIndex1); + + if constexpr (Master_matrix::Option_list::has_column_pairings) { + CP::swap_positions(_matrix()->get_pivot(columnIndex1), _matrix()->get_pivot(columnIndex2)); + } + + return columnIndex2; +} + +template +inline typename Chain_vine_swap::index Chain_vine_swap::_negative_vine_swap( + index columnIndex1, index columnIndex2) +{ + auto& col1 = _matrix()->get_column(columnIndex1); + auto& col2 = _matrix()->get_column(columnIndex2); + + index pairedIndex1 = col1.get_paired_chain_index(); + index pairedIndex2 = col2.get_paired_chain_index(); + + bool hasSmallerBirth; + if constexpr (Master_matrix::Option_list::has_column_pairings) { + hasSmallerBirth = (CP::birth(col1.get_pivot()) < CP::birth(col2.get_pivot())); + } else { + hasSmallerBirth = birthComp_(pairedIndex1, pairedIndex2); + } + + if constexpr (Master_matrix::Option_list::has_column_pairings) { + CP::swap_positions(col1.get_pivot(), col2.get_pivot()); + } + + if (hasSmallerBirth) + { + _matrix()->add_to(pairedIndex1, pairedIndex2); + _matrix()->add_to(columnIndex1, columnIndex2); + + if constexpr (Master_matrix::Option_list::has_column_pairings) { + CP::negative_transpose(col1.get_pivot(), col2.get_pivot()); + } + + return columnIndex1; + } + + _matrix()->add_to(pairedIndex2, pairedIndex1); + _matrix()->add_to(columnIndex2, columnIndex1); + + return columnIndex2; +} + +} // namespace persistence_matrix +} // namespace Gudhi + +#endif // PM_CHAIN_VINE_SWAP_H diff --git a/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/cell_types.h b/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/cell_types.h new file mode 100644 index 0000000000..6dce1f749e --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/cell_types.h @@ -0,0 +1,325 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file cell_types.h + * @author Hannah Schreiber + * @brief Contains the @ref Cell, @ref Cell_column_index and @ref Cell_field_element classes, as well as the + * @ref Dummy_cell_column_index_mixin and @ref Dummy_cell_field_element_mixin structures. Also defines the + * std::hash method for @ref Cell. + */ + +#ifndef PM_MATRIX_CELL_H +#define PM_MATRIX_CELL_H + +#include //std::swap, std::exchange & std::move +#include //std::hash + +namespace Gudhi { +namespace persistence_matrix { + +/** + * @ingroup persistence_matrix + * + * @brief Empty structure. + * Inheritated instead of @ref Cell_column_index, when the row access is disabled. + */ +struct Dummy_cell_column_index_mixin +{ + Dummy_cell_column_index_mixin() {} + template + Dummy_cell_column_index_mixin([[maybe_unused]] index columnIndex) {} +}; + +/** + * @ingroup persistence_matrix + * + * @brief Empty structure. + * Inheritated instead of @ref Cell_field_element, when @ref PersistenceMatrixOptions::is_z2 is true. + */ +struct Dummy_cell_field_element_mixin +{ + Dummy_cell_field_element_mixin() {} + template + Dummy_cell_field_element_mixin([[maybe_unused]] Field_element_type t) {} +}; + +/** + * @ingroup persistence_matrix + * + * @brief Class managing the column index access of a cell. + * + * @tparam index @ref MatIdx index type. + */ +template +class Cell_column_index +{ + public: + /** + * @brief Default constructor. Sets to the column index to -1. + */ + Cell_column_index() : columnIndex_(-1){}; + /** + * @brief Stores the given column index. + * + * @param columnIndex Column index of the cell. + */ + Cell_column_index(index columnIndex) : columnIndex_(columnIndex){}; + /** + * @brief Copy constructor. + * + * @param cell Cell to copy. + */ + Cell_column_index(const Cell_column_index& cell) : columnIndex_(cell.columnIndex_){}; + /** + * @brief Move constructor. + * + * @param cell Cell to move. + */ + Cell_column_index(Cell_column_index&& cell) noexcept : columnIndex_(std::exchange(cell.columnIndex_, 0)){}; + + /** + * @brief Returns the @ref MatIdx column index stored in the cell. + * + * @return Column index of the cell. + */ + index get_column_index() const { return columnIndex_; }; + /** + * @brief Sets the column index to the given value. + * + * @param columnIndex Column index of the cell. + */ + void set_column_index(index columnIndex) { columnIndex_ = columnIndex; } + + /** + * @brief Assign operator. + */ + Cell_column_index& operator=(Cell_column_index other) { + std::swap(columnIndex_, other.columnIndex_); + return *this; + }; + + private: + index columnIndex_; /**< Column index. */ +}; + +/** + * @ingroup persistence_matrix + * + * @brief Class managing the value access of a cell. + * + * @tparam Field_element_type Type of a cell value. + */ +template +class Cell_field_element +{ + public: + /** + * @brief Default constructor. Sets to the element to 0. + */ + Cell_field_element() : element_(0){}; + /** + * @brief Stores the given element. + * + * @param columnIndex Value to store. + */ + Cell_field_element(Field_element_type element) : element_(element){}; + /** + * @brief Copy constructor. + * + * @param cell Cell to copy. + */ + Cell_field_element(const Cell_field_element& cell) : element_(cell.element_){}; + /** + * @brief Move constructor. + * + * @param cell Cell to move. + */ + Cell_field_element(Cell_field_element&& cell) noexcept : element_(std::move(cell.element_)){}; + + /** + * @brief Returns the value stored in the cell. + * + * @return Reference to the value of the cell. + */ + Field_element_type& get_element() { return element_; }; + /** + * @brief Returns the value stored in the cell. + * + * @return Const reference to the value of the cell. + */ + const Field_element_type& get_element() const { return element_; }; + /** + * @brief Sets the value. + * + * @param element Value to store. + */ + void set_element(const Field_element_type& element) { element_ = element; } + + /** + * @brief Assign operator. + */ + Cell_field_element& operator=(Cell_field_element other) { + std::swap(element_, other.element_); + return *this; + }; + + private: + Field_element_type element_; /**< Value of the cell. */ +}; + +/** + * @class Cell cell_types.h gudhi/Persistence_matrix/columns/cell_types.h + * @ingroup persistence_matrix + * + * @brief %Matrix cell class. Stores by default only the row index it belongs to, but can also store its + * column index when the row access is enabled, as well as its value when they are different from only 0 and 1. + * Zero-valued cells are never explicited in the matrix. + * + * @tparam Master_matrix An instanciation of @ref Matrix from which all types and options are deduced. + */ +template +class Cell : public Master_matrix::Cell_column_index_option, + public Master_matrix::Cell_field_element_option, + public Master_matrix::row_hook_type, + public Master_matrix::column_hook_type +{ + private: + using col_opt = typename Master_matrix::Cell_column_index_option; + using field_opt = typename Master_matrix::Cell_field_element_option; + + public: + using Master = Master_matrix; /**< Access to options from outside. */ + using index = typename Master_matrix::index; /**< Column index type. */ + using id_index = typename Master_matrix::id_index; /**< Row index type. */ + using Field_element_type = typename Master_matrix::element_type; /**< Value type. */ + + /** + * @brief Constructs an cell with all attributes at default values. + */ + Cell(){}; + /** + * @brief Constructs a cell with given row index. Other possible attributes are set at default values. + * + * @param rowIndex @ref rowindex "Row index" of the cell. + */ + Cell(id_index rowIndex) : col_opt(), field_opt(), rowIndex_(rowIndex){}; + /** + * @brief Constructs a cell with given row and column index. Other possible attributes are set at default values. + * + * @param columnIndex Column index of the cell. + * @param rowIndex @ref rowindex "Row index" of the cell. + */ + Cell(index columnIndex, id_index rowIndex) : col_opt(columnIndex), field_opt(), rowIndex_(rowIndex){}; + /** + * @brief Copy constructor. + * + * @param cell Cell to copy. + */ + Cell(const Cell& cell) + : col_opt(static_cast(cell)), + field_opt(static_cast(cell)), + rowIndex_(cell.rowIndex_){}; + /** + * @brief Move constructor. + * + * @param cell Cell to move. + */ + Cell(Cell&& cell) noexcept + : col_opt(std::move(static_cast(cell))), + field_opt(std::move(static_cast(cell))), + rowIndex_(std::exchange(cell.rowIndex_, 0)){}; + + /** + * @brief Returns the row index stored in the cell. + * + * @return @ref rowindex "Row index" of the cell. + */ + id_index get_row_index() const { return rowIndex_; }; + /** + * @brief Sets the row index stored in the cell. + * + * @param rowIndex @ref rowindex "Row index" of the cell. + */ + void set_row_index(id_index rowIndex) { rowIndex_ = rowIndex; }; + + /** + * @brief Assign operator. + */ + Cell& operator=(Cell other) { + col_opt::operator=(other); + field_opt::operator=(other); + std::swap(rowIndex_, other.rowIndex_); + return *this; + }; + + /** + * @brief Strictly smaller than comparator. + * + * @param c1 First cell to compare. + * @param c2 Second cell to compare. + * @return true If the row index of the first cell is strictly smaller than the row index of the second cell. + * @return false Otherwise. + */ + friend bool operator<(const Cell& c1, const Cell& c2) { return c1.get_row_index() < c2.get_row_index(); } + /** + * @brief Equality comparator. + * + * @param c1 First cell to compare. + * @param c2 Second cell to compare. + * @return true If the row index of the first cell is equal to the row index of the second cell. + * @return false Otherwise. + */ + friend bool operator==(const Cell& c1, const Cell& c2) { return c1.get_row_index() == c2.get_row_index(); } + + /** + * @brief Converts the cell into a row index. + * + * @return The row index of the cell. + */ + operator id_index() const { return rowIndex_; } + /** + * @brief Converts the cell into a pair of row index and cell value. + * + * @return A std::pair with first element the row index and second element the value. + */ + operator std::pair() const { + if constexpr (Master_matrix::Option_list::is_z2) { + return {rowIndex_, 1}; + } else { + return {rowIndex_, field_opt::element_}; + } + } + + private: + id_index rowIndex_; /**< Row index of the cell. */ +}; + +} // namespace persistence_matrix +} // namespace Gudhi + +/** + * @ingroup persistence_matrix + * + * @brief Hash method for @ref Gudhi::persistence_matrix::Cell. + * + * The cells are differentiated by their row indices only. For exemple, two cells with the same row index + * but different column indices have the same hash value. + * + * @tparam Master_matrix Template parameter of @ref Gudhi::persistence_matrix::Cell. + */ +template +struct std::hash > { + size_t operator()(const Gudhi::persistence_matrix::Cell& cell) const { + return std::hash()(cell.get_row_index()); + } +}; + +#endif // PM_MATRIX_CELL_H diff --git a/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/chain_column_extra_properties.h b/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/chain_column_extra_properties.h new file mode 100644 index 0000000000..5ae038d997 --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/chain_column_extra_properties.h @@ -0,0 +1,149 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file chain_column_extra_properties.h + * @author Hannah Schreiber + * @brief Contains the @ref Chain_column_extra_properties class and @ref Dummy_chain_properties structure. + */ + +#ifndef PM_CHAIN_COLUMN_PROP_H +#define PM_CHAIN_COLUMN_PROP_H + +#include //std::swap + +namespace Gudhi { +namespace persistence_matrix { + +/** + * @ingroup persistence_matrix + * + * @brief Empty structure. + * Inheritated instead of @ref Chain_column_extra_properties, when the columns are not meant for + * @ref chainmatrix "chain matrices". + */ +struct Dummy_chain_properties +{ + Dummy_chain_properties([[maybe_unused]] int pivot = 0, [[maybe_unused]] int pair = 0) {} + + friend void swap([[maybe_unused]] Dummy_chain_properties& col1, [[maybe_unused]] Dummy_chain_properties& col2) {} +}; + +/** + * @class Chain_column_extra_properties chain_column_extra_properties.h \ + * gudhi/Persistence_matrix/columns/chain_column_extra_properties.h + * @ingroup persistence_matrix + * + * @brief Class managing the pivot and partitioning of columns in @ref Chain_matrix. + * + * The columns of a @ref chainmatrix "chain matrix" are partitioned in three sets: \f$ F \f$, \f$ G \f$ and \f$ H \f$ + * with a bijection between \f$ G \f$ and \f$ H \f$. If a column is in \f$ F \f$, the value of + * `Chain_column_extra_properties::pairedColumn_` is `-1`, while the value corresponds to the @ref MatIdx index of + * the image of the bijection if the column is in either \f$ G \f$ or \f$ H \f$. See @cite zigzag for + * more details. + * + * @tparam Master_matrix An instanciation of @ref Matrix from which all types and options are deduced. + */ +template +class Chain_column_extra_properties +{ + public: + using index = typename Master_matrix::index; /**< @ref MatIdx index type. */ + using id_index = typename Master_matrix::id_index; /**< @ref IDIdx index type. */ + + /** + * @brief Default constructor. Sets the pivot and pair to -1, which means "not existing". + */ + Chain_column_extra_properties() : pivot_(-1), pairedColumn_(-1) {} + /** + * @brief Constructor setting the pivot at the given value and the pair to -1 (i.e. not paired). + * + * @param pivot @ref rowindex "Row index" of the pivot. Corresponds to the @ref IDIdx index of the face represented + * by the column. + */ + Chain_column_extra_properties(id_index pivot) : pivot_(pivot), pairedColumn_(-1) {} + /** + * @brief Constructor setting the pivot and the pair at the given values. + * + * @param pivot @ref rowindex "Row index" of the pivot. Corresponds to the @ref IDIdx index of the face represented + * by the column. + * @param pair @ref MatIdx index of the pair of the column. + */ + Chain_column_extra_properties(id_index pivot, index pair) : pivot_(pivot), pairedColumn_(pair) {} + /** + * @brief Copy constructor. + * + * @param col Column to copy. + */ + Chain_column_extra_properties(const Chain_column_extra_properties& col) + : pivot_(col.pivot_), pairedColumn_(col.pairedColumn_) {} + /** + * @brief Move constructor. + * + * @param col Column to move. + */ + Chain_column_extra_properties(Chain_column_extra_properties&& col) + : pivot_(std::exchange(col.pivot_, -1)), pairedColumn_(std::exchange(col.pairedColumn_, -1)) {} + + /** + * @brief Returns -1 if the column is not paired, the @ref MatIdx of the pair otherwise. + * + * @return -1 if the column is not paired, the @ref MatIdx of the pair otherwise. + */ + index get_paired_chain_index() const { return pairedColumn_; } + /** + * @brief Indicates if the column is paired or not. + * + * @return true If the column is paired. + * @return false Otherwise. + */ + bool is_paired() const { return pairedColumn_ != static_cast(-1); } + /** + * @brief Sets the value of the pair. + * + * @param other_col @ref MatIdx of the pair column. + */ + void assign_paired_chain(index other_col) { pairedColumn_ = other_col; } + /** + * @brief Unpairs a column. + */ + void unassign_paired_chain() { pairedColumn_ = -1; }; + + /** + * @brief Assign operator. + */ + Chain_column_extra_properties& operator=(const Chain_column_extra_properties& other) { + pivot_ = other.pivot_; + pairedColumn_ = other.pairedColumn_; + return *this; + } + /** + * @brief Swap operator. + */ + friend void swap(Chain_column_extra_properties& col1, Chain_column_extra_properties& col2) { + std::swap(col1.pivot_, col2.pivot_); + std::swap(col1.pairedColumn_, col2.pairedColumn_); + } + + protected: + id_index get_pivot() const { return pivot_; } + void swap_pivots(Chain_column_extra_properties& other) { std::swap(pivot_, other.pivot_); } + + private: + id_index pivot_; /**< @ref IDIdx index associated to the chain */ + index pairedColumn_; /**< Represents the (F, G x H) partition of the columns. + -1 if in F, @ref MatIdx of image of bijection otherwise. + The pivot of a column in G will always be smaller than the pivot of its image in H. */ +}; + +} // namespace persistence_matrix +} // namespace Gudhi + +#endif // PM_CHAIN_COLUMN_PROP_H diff --git a/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/column_dimension_holder.h b/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/column_dimension_holder.h new file mode 100644 index 0000000000..9775f9d24c --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/column_dimension_holder.h @@ -0,0 +1,105 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file column_dimension_holder.h + * @author Hannah Schreiber + * @brief Contains the @ref Column_dimension_holder class and @ref Dummy_dimension_holder structure. + */ + +#ifndef PM_COLUMN_DIM_HOLDER_H +#define PM_COLUMN_DIM_HOLDER_H + +#include //std::swap + +namespace Gudhi { +namespace persistence_matrix { + +/** + * @ingroup persistence_matrix + * + * @brief Empty structure. + * Inheritated instead of @ref Column_dimension_holder, when the columns are not storing a dimension. + */ +struct Dummy_dimension_holder +{ + Dummy_dimension_holder() {} + template + Dummy_dimension_holder([[maybe_unused]] dimension_type dim) {} + + friend void swap([[maybe_unused]] Dummy_dimension_holder& col1, [[maybe_unused]] Dummy_dimension_holder& col2) {} +}; + +/** + * @class Column_dimension_holder column_dimension_holder.h gudhi/Persistence_matrix/columns/column_dimension_holder.h + * @ingroup persistence_matrix + * + * @brief Class managing the dimension access of a column. + * + * @tparam Master_matrix An instanciation of @ref Matrix from which all types and options are deduced. + */ +template +struct Column_dimension_holder +{ + using dimension_type = typename Master_matrix::dimension_type; /**< Dimension value type. */ + + /** + * @brief Default constructor. Sets the dimension to 0 for @ref boundarymatrix "boundary matrices" and to -1 for @ref chainmatrix "chain matrices". + */ + Column_dimension_holder() : dim_(Master_matrix::Option_list::is_of_boundary_type ? 0 : -1) {} + /** + * @brief Constructor setting the dimension to the given value. + * + * @param dim Dimension of the column. + */ + Column_dimension_holder(dimension_type dim) : dim_(dim) {} + /** + * @brief Copy constructor. + * + * @param col Column to copy. + */ + Column_dimension_holder(const Column_dimension_holder& col) : dim_(col.dim_) {} + /** + * @brief Move constructor. + * + * @param col Column to move. + */ + Column_dimension_holder(Column_dimension_holder&& col) : dim_(std::exchange(col.dim_, -1)) {} + + /** + * @brief Returns the dimension of the column. + * + * @return The dimension of the column. + */ + dimension_type get_dimension() const { return dim_; } + + /** + * @brief Assign operator. + */ + Column_dimension_holder& operator=(const Column_dimension_holder& other) { + dim_ = other.dim_; + return *this; + } + /** + * @brief Swap operator. + */ + friend void swap(Column_dimension_holder& col1, Column_dimension_holder& col2) { std::swap(col1.dim_, col2.dim_); } + + protected: + void swap_dimension(Column_dimension_holder& other) { std::swap(dim_, other.dim_); } + + private: + dimension_type dim_; /**< Dimension of the column. */ +}; + +} // namespace persistence_matrix +} // namespace Gudhi + +#endif // PM_COLUMN_DIM_HOLDER_H diff --git a/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/column_utilities.h b/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/column_utilities.h new file mode 100644 index 0000000000..642a9142a6 --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/column_utilities.h @@ -0,0 +1,217 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2024 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file column_utilities.h + * @author Hannah Schreiber + * @brief Contains helper methods for column addition and column hasher. + */ + +#ifndef PM_COLUMN_UTILITIES_H +#define PM_COLUMN_UTILITIES_H + +#include +#include + +#include + +namespace Gudhi { +namespace persistence_matrix { + +template +Cell* _get_cell(const Cell_iterator& itTarget) +{ + if constexpr (Cell::Master::Option_list::column_type == Column_types::INTRUSIVE_LIST || + Cell::Master::Option_list::column_type == Column_types::INTRUSIVE_SET) { + return &*itTarget; + } else { + return *itTarget; + } +} + +// works only for ordered columns +template +void _generic_merge_cell_to_column(Column_type& targetColumn, + Cell_iterator& itSource, + typename Column_type::Column_type::iterator& itTarget, + F1&& process_target, + F2&& process_source, + F3&& update_target1, + F4&& update_target2, + bool& pivotIsZeroed) +{ + typename Column_type::Cell* targetCell = _get_cell(itTarget); + + if (targetCell->get_row_index() < itSource->get_row_index()) { + process_target(targetCell); + ++itTarget; + } else if (targetCell->get_row_index() > itSource->get_row_index()) { + process_source(itSource, itTarget); + ++itSource; + } else { + if constexpr (Column_type::Master::Option_list::is_z2) { + //_multiply_*_and_add never enters here so not treated + if constexpr (Column_type::Master::isNonBasic && !Column_type::Master::Option_list::is_of_boundary_type) { + if (targetCell->get_row_index() == targetColumn.get_pivot()) pivotIsZeroed = true; + } + targetColumn._delete_cell(itTarget); + } else { + update_target1(targetCell->get_element(), itSource); + if (targetCell->get_element() == Column_type::Field_operators::get_additive_identity()) { + if constexpr (Column_type::Master::isNonBasic && !Column_type::Master::Option_list::is_of_boundary_type) { + if (targetCell->get_row_index() == targetColumn.get_pivot()) pivotIsZeroed = true; + } + targetColumn._delete_cell(itTarget); + } else { + update_target2(targetCell); + if constexpr (Column_type::Master::Option_list::has_row_access) + targetColumn.update_cell(*targetCell); + ++itTarget; + } + } + ++itSource; + } +} + +// works only for ordered columns +template +bool _generic_add_to_column(const Cell_range& source, + Column_type& targetColumn, + F1&& process_target, + F2&& process_source, + F3&& update_target1, + F4&& update_target2, + F5&& finish_target) +{ + bool pivotIsZeroed = false; + + auto& target = targetColumn.column_; + auto itTarget = target.begin(); + auto itSource = source.begin(); + while (itTarget != target.end() && itSource != source.end()) { + _generic_merge_cell_to_column(targetColumn, itSource, itTarget, + process_target, process_source, update_target1, update_target2, + pivotIsZeroed); + } + + finish_target(itTarget); + + while (itSource != source.end()) { + process_source(itSource, target.end()); + ++itSource; + } + + return pivotIsZeroed; +} + +template +bool _add_to_column(const Cell_range& source, Column_type& targetColumn) +{ + return _generic_add_to_column( + source, + targetColumn, [&]([[maybe_unused]] typename Column_type::Cell* cellTarget) {}, + [&](typename Cell_range::const_iterator& itSource, const typename Column_type::Column_type::iterator& itTarget) { + if constexpr (Column_type::Master::Option_list::is_z2) { + targetColumn._insert_cell(itSource->get_row_index(), itTarget); + } else { + targetColumn._insert_cell(itSource->get_element(), itSource->get_row_index(), itTarget); + } + }, + [&](typename Column_type::Field_element_type& targetElement, typename Cell_range::const_iterator& itSource) { + if constexpr (!Column_type::Master::Option_list::is_z2) + targetColumn.operators_->add_inplace(targetElement, itSource->get_element()); + }, + [&]([[maybe_unused]] typename Column_type::Cell* cellTarget) {}, + [&]([[maybe_unused]] typename Column_type::Column_type::iterator& itTarget) {}); +} + +template +bool _multiply_target_and_add_to_column(const typename Column_type::Field_element_type& val, + const Cell_range& source, + Column_type& targetColumn) +{ + if (val == 0u) { + if constexpr (Column_type::Master::isNonBasic && !Column_type::Master::Option_list::is_of_boundary_type) { + throw std::invalid_argument("A chain column should not be multiplied by 0."); + // this would not only mess up the base, but also the pivots stored. + } else { + targetColumn.clear(); + } + } + + return _generic_add_to_column( + source, + targetColumn, + [&](typename Column_type::Cell* cellTarget) { + targetColumn.operators_->multiply_inplace(cellTarget->get_element(), val); + // targetColumn.ra_opt::update_cell(*itTarget) produces an internal compiler error + // even though it works in _generic_add_to_column... Probably because of the lambda. + if constexpr (Column_type::Master::Option_list::has_row_access) + targetColumn.update_cell(*cellTarget); + }, + [&](typename Cell_range::const_iterator& itSource, const typename Column_type::Column_type::iterator& itTarget) { + targetColumn._insert_cell(itSource->get_element(), itSource->get_row_index(), itTarget); + }, + [&](typename Column_type::Field_element_type& targetElement, typename Cell_range::const_iterator& itSource) { + targetColumn.operators_->multiply_and_add_inplace_front(targetElement, val, itSource->get_element()); + }, + [&]([[maybe_unused]] typename Column_type::Cell* cellTarget) {}, + [&](typename Column_type::Column_type::iterator& itTarget) { + while (itTarget != targetColumn.column_.end()) { + typename Column_type::Cell* targetCell = _get_cell(itTarget); + targetColumn.operators_->multiply_inplace(targetCell->get_element(), val); + if constexpr (Column_type::Master::Option_list::has_row_access) + targetColumn.update_cell(*targetCell); + itTarget++; + } + }); +} + +template +bool _multiply_source_and_add_to_column(const typename Column_type::Field_element_type& val, const Cell_range& source, + Column_type& targetColumn) +{ + if (val == 0u) { + return false; + } + + return _generic_add_to_column( + source, + targetColumn, []([[maybe_unused]] typename Column_type::Cell* cellTarget) {}, + [&](typename Cell_range::const_iterator& itSource, const typename Column_type::Column_type::iterator& itTarget) { + typename Column_type::Cell* cell = + targetColumn._insert_cell(itSource->get_element(), itSource->get_row_index(), itTarget); + targetColumn.operators_->multiply_inplace(cell->get_element(), val); + if constexpr (Column_type::Master::Option_list::has_row_access) + targetColumn.update_cell(*cell); + }, + [&](typename Column_type::Field_element_type& targetElement, typename Cell_range::const_iterator& itSource) { + targetColumn.operators_->multiply_and_add_inplace_back(itSource->get_element(), val, targetElement); + }, + [&]([[maybe_unused]] typename Column_type::Cell* cellTarget) {}, + []([[maybe_unused]] typename Column_type::Column_type::iterator& itTarget) {}); +} + +// column has to be ordered (ie. not suited for unordered_map and heap) and contain the exact values +// (ie. not suited for vector and heap). A same colonne but ordered differently will have another hash value. +template +std::size_t hash_column(const Column_type& column) { + std::size_t seed = 0; + for (auto& cell : column) { + seed ^= std::hash()(cell.get_row_index() * static_cast(cell.get_element())) + + 0x9e3779b9 + (seed << 6) + (seed >> 2); + } + return seed; +} + +} // namespace persistence_matrix +} // namespace Gudhi + +#endif // PM_COLUMN_UTILITIES_H diff --git a/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/heap_column.h b/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/heap_column.h new file mode 100644 index 0000000000..7effba0bc2 --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/heap_column.h @@ -0,0 +1,1136 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file heap_column.h + * @author Hannah Schreiber + * @brief Contains the @ref Heap_column class. Also defines the std::hash method for @ref Heap_column. + */ + +#ifndef PM_HEAP_COLUMN_H +#define PM_HEAP_COLUMN_H + +#include +#include +#include +#include //binary_search +#include //std::swap, std::move & std::exchange + +#include + +#include + +namespace Gudhi { +namespace persistence_matrix { + +/** + * @class Heap_column heap_column.h gudhi/Persistence_matrix/columns/heap_column.h + * @ingroup persistence_matrix + * + * @brief Column class following the @ref PersistenceMatrixColumn concept. Not compatible with row access. + * + * Column based on a heap structure. The heap is represented as a vector sorted as a heap. The top of the heap is + * the cell with the biggest row index. The sum of two columns is lazy: the content of the source is simply inserted + * into the heap of the target. Therefore the underlying vector can contain several cells with the same row index. + * The real value of a cell at a row index corresponds to the sum in the coeffcient field of all values with same + * row index. Additionaly, the given cell range added into the heap does not need to be somehow ordered. + * + * @tparam Master_matrix An instanciation of @ref Matrix from which all types and options are deduced. + * @tparam Cell_constructor Factory of @ref Cell classes. + */ +template +class Heap_column : public Master_matrix::Column_dimension_option, public Master_matrix::Chain_column_option +{ + public: + using Master = Master_matrix; + using index = typename Master_matrix::index; + using id_index = typename Master_matrix::id_index; + using dimension_type = typename Master_matrix::dimension_type; + using Field_element_type = typename Master_matrix::element_type; + using Cell = typename Master_matrix::Cell_type; + using Column_settings = typename Master_matrix::Column_settings; + + private: + using Field_operators = typename Master_matrix::Field_operators; + using Column_type = std::vector; + using Cell_constructor = typename Master_matrix::Cell_constructor; + + public: + using iterator = boost::indirect_iterator; + using const_iterator = boost::indirect_iterator; + using reverse_iterator = boost::indirect_iterator; + using const_reverse_iterator = boost::indirect_iterator; + + Heap_column(Column_settings* colSettings = nullptr); + template + Heap_column(const Container_type& nonZeroRowIndices, Column_settings* colSettings); + template + Heap_column(const Container_type& nonZeroChainRowIndices, + dimension_type dimension, + Column_settings* colSettings); + Heap_column(const Heap_column& column, Column_settings* colSettings = nullptr); + Heap_column(Heap_column&& column) noexcept; + ~Heap_column(); + + // just for the sake of the interface + // row containers and column index are ignored as row access is not implemented for heap columns + template + Heap_column(index columnIndex, + const Container_type& nonZeroRowIndices, + Row_container_type* rowContainer, + Column_settings* colSettings); + template + Heap_column(index columnIndex, + const Container_type& nonZeroChainRowIndices, + dimension_type dimension, + Row_container_type* rowContainer, + Column_settings* colSettings); + template + Heap_column(const Heap_column& column, + index columnIndex, + Row_container_type* rowContainer, + Column_settings* colSettings = nullptr); + + std::vector get_content(int columnLength = -1) const; + bool is_non_zero(id_index rowIndex) const; + bool is_empty(); + std::size_t size() const; + + template + void reorder(const Map_type& valueMap, [[maybe_unused]] index columnIndex = -1); + void clear(); + void clear(id_index rowIndex); + + id_index get_pivot(); + Field_element_type get_pivot_value(); + + iterator begin() noexcept; + const_iterator begin() const noexcept; + iterator end() noexcept; + const_iterator end() const noexcept; + reverse_iterator rbegin() noexcept; + const_reverse_iterator rbegin() const noexcept; + reverse_iterator rend() noexcept; + const_reverse_iterator rend() const noexcept; + + template + Heap_column& operator+=(const Cell_range& column); + Heap_column& operator+=(Heap_column& column); + + Heap_column& operator*=(unsigned int v); + + // this = v * this + column + template + Heap_column& multiply_target_and_add(const Field_element_type& val, const Cell_range& column); + Heap_column& multiply_target_and_add(const Field_element_type& val, Heap_column& column); + // this = this + column * v + template + Heap_column& multiply_source_and_add(const Cell_range& column, const Field_element_type& val); + Heap_column& multiply_source_and_add(Heap_column& column, const Field_element_type& val); + + std::size_t compute_hash_value(); + + friend bool operator==(const Heap_column& c1, const Heap_column& c2) { + if (&c1 == &c2) return true; + + Heap_column cc1(c1), cc2(c2); + Cell* p1 = cc1._pop_pivot(); + Cell* p2 = cc2._pop_pivot(); + while (p1 != nullptr && p2 != nullptr) { + if (p1->get_row_index() != p2->get_row_index()) { + c1.cellPool_->destroy(p1); + c2.cellPool_->destroy(p2); + return false; + } + if constexpr (!Master_matrix::Option_list::is_z2){ + if (p1->get_element() != p2->get_element()) { + c1.cellPool_->destroy(p1); + c2.cellPool_->destroy(p2); + return false; + } + } + c1.cellPool_->destroy(p1); + c2.cellPool_->destroy(p2); + p1 = cc1._pop_pivot(); + p2 = cc2._pop_pivot(); + } + + if (p1 == nullptr && p2 == nullptr) return true; + if (p1 != nullptr) { + c1.cellPool_->destroy(p1); + return false; + } + c2.cellPool_->destroy(p2); + return false; + } + friend bool operator<(const Heap_column& c1, const Heap_column& c2) { + if (&c1 == &c2) return false; + + //lexicographical order but starting from last value and not first + Heap_column cc1(c1), cc2(c2); + Cell* p1 = cc1._pop_pivot(); + Cell* p2 = cc2._pop_pivot(); + while (p1 != nullptr && p2 != nullptr) { + if (p1->get_row_index() > p2->get_row_index()) { + c1.cellPool_->destroy(p1); + c2.cellPool_->destroy(p2); + return false; + } + if (p1->get_row_index() < p2->get_row_index()) { + c1.cellPool_->destroy(p1); + c2.cellPool_->destroy(p2); + return true; + } + if constexpr (!Master_matrix::Option_list::is_z2){ + if (p1->get_element() > p2->get_element()) { + c1.cellPool_->destroy(p1); + c2.cellPool_->destroy(p2); + return false; + } + if (p1->get_element() < p2->get_element()) { + c1.cellPool_->destroy(p1); + c2.cellPool_->destroy(p2); + return true; + } + } + c1.cellPool_->destroy(p1); + c2.cellPool_->destroy(p2); + p1 = cc1._pop_pivot(); + p2 = cc2._pop_pivot(); + } + + if (p2 == nullptr) { + c1.cellPool_->destroy(p1); + return false; + } + c2.cellPool_->destroy(p2); + return true; + } + + // Disabled with row access. + Heap_column& operator=(const Heap_column& other); + + friend void swap(Heap_column& col1, Heap_column& col2) { + swap(static_cast(col1), + static_cast(col2)); + swap(static_cast(col1), + static_cast(col2)); + col1.column_.swap(col2.column_); + std::swap(col1.insertsSinceLastPrune_, col2.insertsSinceLastPrune_); + std::swap(col1.operators_, col2.operators_); + std::swap(col1.cellPool_, col2.cellPool_); + } + + private: + using dim_opt = typename Master_matrix::Column_dimension_option; + using chain_opt = typename Master_matrix::Chain_column_option; + + struct { + bool operator()(const Cell* c1, const Cell* c2) const { return *c1 < *c2; } + } cellPointerComp_; + + Column_type column_; + unsigned int insertsSinceLastPrune_; + Field_operators* operators_; + Cell_constructor* cellPool_; + + void _prune(); + Cell* _pop_pivot(); + template + bool _add(const Cell_range& column); + template + bool _multiply_target_and_add(const Field_element_type& val, const Cell_range& column); + template + bool _multiply_source_and_add(const Cell_range& column, const Field_element_type& val); +}; + +template +inline Heap_column::Heap_column(Column_settings* colSettings) + : dim_opt(), chain_opt(), insertsSinceLastPrune_(0), operators_(nullptr), cellPool_(colSettings == nullptr ? nullptr : &(colSettings->cellConstructor)) +{ + if (colSettings == nullptr) return; //to allow default constructor which gives a dummy column + if constexpr (!Master_matrix::Option_list::is_z2){ + operators_ = &(colSettings->operators); + } +} + +template +template +inline Heap_column::Heap_column(const Container_type& nonZeroRowIndices, Column_settings* colSettings) + : dim_opt(nonZeroRowIndices.size() == 0 ? 0 : nonZeroRowIndices.size() - 1), + chain_opt(), + column_(nonZeroRowIndices.size(), nullptr), + insertsSinceLastPrune_(0), + operators_(nullptr), + cellPool_(&(colSettings->cellConstructor)) +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Constructor not available for chain columns, please specify the dimension of the chain."); + + if constexpr (!Master_matrix::Option_list::is_z2){ + operators_ = &(colSettings->operators); + } + + index i = 0; + if constexpr (Master_matrix::Option_list::is_z2) { + for (id_index id : nonZeroRowIndices) { + column_[i++] = cellPool_->construct(id); + } + } else { + for (const auto& p : nonZeroRowIndices) { + column_[i] = cellPool_->construct(p.first); + column_[i++]->set_element(operators_->get_value(p.second)); + } + } + std::make_heap(column_.begin(), column_.end(), cellPointerComp_); +} + +template +template +inline Heap_column::Heap_column(const Container_type& nonZeroRowIndices, + dimension_type dimension, + Column_settings* colSettings) + : dim_opt(dimension), + chain_opt([&] { + if constexpr (Master_matrix::Option_list::is_z2) { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + } else { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + } + }()), + column_(nonZeroRowIndices.size(), nullptr), + insertsSinceLastPrune_(0), + operators_(nullptr), + cellPool_(&(colSettings->cellConstructor)) +{ + index i = 0; + if constexpr (Master_matrix::Option_list::is_z2) { + for (id_index id : nonZeroRowIndices) { + column_[i++] = cellPool_->construct(id); + } + } else { + operators_ = &(colSettings->operators); + for (const auto& p : nonZeroRowIndices) { + column_[i] = cellPool_->construct(p.first); + column_[i++]->set_element(operators_->get_value(p.second)); + } + } + std::make_heap(column_.begin(), column_.end(), cellPointerComp_); +} + +template +inline Heap_column::Heap_column(const Heap_column& column, + Column_settings* colSettings) + : dim_opt(static_cast(column)), + chain_opt(static_cast(column)), + column_(column.column_.size(), nullptr), + insertsSinceLastPrune_(0), + operators_(colSettings == nullptr ? column.operators_ : nullptr), + cellPool_(colSettings == nullptr ? column.cellPool_ : &(colSettings->cellConstructor)) +{ + static_assert(!Master_matrix::Option_list::has_row_access, + "Simple copy constructor not available when row access option enabled. Please specify the new column " + "index and the row container."); + + if constexpr (!Master_matrix::Option_list::is_z2){ + if (colSettings != nullptr) operators_ = &(colSettings->operators); + } + + index i = 0; + for (const Cell* cell : column.column_) { + if constexpr (Master_matrix::Option_list::is_z2) { + column_[i++] = cellPool_->construct(cell->get_row_index()); + } else { + column_[i] = cellPool_->construct(cell->get_row_index()); + column_[i++]->set_element(cell->get_element()); + } + } + // column.column_ already ordered as a heap, so no need of make_heap. +} + +template +inline Heap_column::Heap_column(Heap_column&& column) noexcept + : dim_opt(std::move(static_cast(column))), + chain_opt(std::move(static_cast(column))), + column_(std::move(column.column_)), + insertsSinceLastPrune_(std::exchange(column.insertsSinceLastPrune_, 0)), + operators_(std::exchange(column.operators_, nullptr)), + cellPool_(std::exchange(column.cellPool_, nullptr)) +{} + +template +template +inline Heap_column::Heap_column(index columnIndex, + const Container_type& nonZeroRowIndices, + Row_container_type* rowContainer, + Column_settings* colSettings) + : dim_opt(nonZeroRowIndices.size() == 0 ? 0 : nonZeroRowIndices.size() - 1), + chain_opt([&] { + if constexpr (Master_matrix::Option_list::is_z2) { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + } else { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + } + }()), + column_(nonZeroRowIndices.size(), nullptr), + insertsSinceLastPrune_(0), + operators_(nullptr), + cellPool_(&(colSettings->cellConstructor)) +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Constructor not available for chain columns, please specify the dimension of the chain."); + + index i = 0; + if constexpr (Master_matrix::Option_list::is_z2) { + for (id_index id : nonZeroRowIndices) { + column_[i++] = cellPool_->construct(id); + } + } else { + operators_ = &(colSettings->operators); + for (const auto& p : nonZeroRowIndices) { + column_[i] = cellPool_->construct(p.first); + column_[i++]->set_element(operators_->get_value(p.second)); + } + } + std::make_heap(column_.begin(), column_.end(), cellPointerComp_); +} + +template +template +inline Heap_column::Heap_column( + index columnIndex, + const Container_type& nonZeroRowIndices, + dimension_type dimension, + Row_container_type* rowContainer, + Column_settings* colSettings) + : dim_opt(dimension), + chain_opt([&] { + if constexpr (Master_matrix::Option_list::is_z2) { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + } else { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + } + }()), + column_(nonZeroRowIndices.size(), nullptr), + insertsSinceLastPrune_(0), + operators_(nullptr), + cellPool_(&(colSettings->cellConstructor)) +{ + index i = 0; + if constexpr (Master_matrix::Option_list::is_z2) { + for (id_index id : nonZeroRowIndices) { + column_[i++] = cellPool_->construct(id); + } + } else { + operators_ = &(colSettings->operators); + for (const auto& p : nonZeroRowIndices) { + column_[i] = cellPool_->construct(p.first); + column_[i++]->set_element(operators_->get_value(p.second)); + } + } + std::make_heap(column_.begin(), column_.end(), cellPointerComp_); +} + +template +template +inline Heap_column::Heap_column(const Heap_column& column, + index columnIndex, + Row_container_type* rowContainer, + Column_settings* colSettings) + : dim_opt(static_cast(column)), + chain_opt(static_cast(column)), + column_(column.column_.size(), nullptr), + insertsSinceLastPrune_(0), + operators_(colSettings == nullptr ? column.operators_ : nullptr), + cellPool_(colSettings == nullptr ? column.cellPool_ : &(colSettings->cellConstructor)) +{ + if constexpr (!Master_matrix::Option_list::is_z2){ + if (colSettings != nullptr) operators_ = &(colSettings->operators); + } + + index i = 0; + for (const Cell* cell : column.column_) { + if constexpr (Master_matrix::Option_list::is_z2) { + column_[i++] = cellPool_->construct(cell->get_row_index()); + } else { + column_[i] = cellPool_->construct(cell->get_row_index()); + column_[i++]->set_element(cell->get_element()); + } + } + // column.column_ already ordered as a heap, so no need of make_heap. +} + +template +inline Heap_column::~Heap_column() +{ + for (auto* cell : column_) { + cellPool_->destroy(cell); + } +} + +template +inline std::vector::Field_element_type> +Heap_column::get_content(int columnLength) const +{ + bool pivotLength = (columnLength < 0); + if (columnLength < 0 && column_.size() > 0) + columnLength = column_.front()->get_row_index() + 1; + else if (columnLength < 0) + return std::vector(); + + std::vector container(columnLength, 0); + for (auto it = column_.begin(); it != column_.end(); ++it) { + if ((*it)->get_row_index() < static_cast(columnLength)) { + if constexpr (Master_matrix::Option_list::is_z2) { + container[(*it)->get_row_index()] = !container[(*it)->get_row_index()]; + } else { + operators_->add_inplace(container[(*it)->get_row_index()], (*it)->get_element()); + } + } + } + + if (pivotLength) { + while (!container.empty() && container.back() == 0u) container.pop_back(); + } + + return container; +} + +template +inline bool Heap_column::is_non_zero(id_index rowIndex) const +{ + Field_element_type c(0); + for (const Cell* cell : column_) { + if (cell->get_row_index() == rowIndex) { + if constexpr (Master_matrix::Option_list::is_z2) c = !c; + else operators_->add_inplace(c, cell->get_element()); + } + } + return c != Field_operators::get_additive_identity(); +} + +template +inline bool Heap_column::is_empty() +{ + Cell* pivot = _pop_pivot(); + if (pivot != nullptr) { + column_.push_back(pivot); + std::push_heap(column_.begin(), column_.end(), cellPointerComp_); + return false; + } + return true; +} + +template +inline std::size_t Heap_column::size() const +{ + return column_.size(); +} + +template +template +inline void Heap_column::reorder(const Map_type& valueMap, + [[maybe_unused]] index columnIndex) +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Method not available for chain columns."); + + Column_type tempCol; + Cell* pivot = _pop_pivot(); + while (pivot != nullptr) { + pivot->set_row_index(valueMap.at(pivot->get_row_index())); + tempCol.push_back(pivot); + pivot = _pop_pivot(); + } + column_.swap(tempCol); + std::make_heap(column_.begin(), column_.end(), cellPointerComp_); + + insertsSinceLastPrune_ = 0; +} + +template +inline void Heap_column::clear() +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Method not available for chain columns as a base element should not be empty."); + + for (auto* cell : column_) { + cellPool_->destroy(cell); + } + + column_.clear(); + insertsSinceLastPrune_ = 0; +} + +template +inline void Heap_column::clear(id_index rowIndex) +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Method not available for chain columns."); + + Column_type tempCol; + Cell* pivot = _pop_pivot(); + while (pivot != nullptr) { + if (pivot->get_row_index() != rowIndex){ + tempCol.push_back(pivot); + } else { + cellPool_->destroy(pivot); + } + pivot = _pop_pivot(); + } + column_.swap(tempCol); + + insertsSinceLastPrune_ = 0; +} + +template +inline typename Heap_column::id_index +Heap_column::get_pivot() +{ + static_assert(Master_matrix::isNonBasic, + "Method not available for base columns."); // could technically be, but is the notion usefull then? + + if constexpr (Master_matrix::Option_list::is_of_boundary_type) { + Cell* pivot = _pop_pivot(); + if (pivot != nullptr) { + column_.push_back(pivot); + std::push_heap(column_.begin(), column_.end(), cellPointerComp_); + return pivot->get_row_index(); + } + return -1; + } else { + return chain_opt::get_pivot(); + } +} + +template +inline typename Heap_column::Field_element_type +Heap_column::get_pivot_value() +{ + static_assert(Master_matrix::isNonBasic, + "Method not available for base columns."); // could technically be, but is the notion usefull then? + + if constexpr (Master_matrix::Option_list::is_z2) { + return 1; + } else { + if constexpr (Master_matrix::Option_list::is_of_boundary_type) { + Cell* pivot = _pop_pivot(); + if (pivot != nullptr) { + column_.push_back(pivot); + std::push_heap(column_.begin(), column_.end(), cellPointerComp_); + return pivot->get_element(); + } + return 0; + } else { + Field_element_type sum(0); + if (chain_opt::get_pivot() == static_cast(-1)) return sum; + for (const Cell* cell : column_) { + if (cell->get_row_index() == chain_opt::get_pivot()) operators_->add_inplace(sum, cell->get_element()); + } + return sum; // should not be 0 if properly used. + } + } +} + +template +inline typename Heap_column::iterator +Heap_column::begin() noexcept +{ + return column_.begin(); +} + +template +inline typename Heap_column::const_iterator +Heap_column::begin() const noexcept +{ + return column_.begin(); +} + +template +inline typename Heap_column::iterator +Heap_column::end() noexcept +{ + return column_.end(); +} + +template +inline typename Heap_column::const_iterator +Heap_column::end() const noexcept +{ + return column_.end(); +} + +template +inline typename Heap_column::reverse_iterator +Heap_column::rbegin() noexcept +{ + return column_.rbegin(); +} + +template +inline typename Heap_column::const_reverse_iterator +Heap_column::rbegin() const noexcept +{ + return column_.rbegin(); +} + +template +inline typename Heap_column::reverse_iterator +Heap_column::rend() noexcept +{ + return column_.rend(); +} + +template +inline typename Heap_column::const_reverse_iterator +Heap_column::rend() const noexcept +{ + return column_.rend(); +} + +template +template +inline Heap_column& Heap_column::operator+=(const Cell_range& column) +{ + static_assert((!Master_matrix::isNonBasic || std::is_same_v), + "For boundary columns, the range has to be a column of same type to help ensure the validity of the " + "base element."); // could be removed, if we give the responsability to the user. + static_assert((!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type), + "For chain columns, the given column cannot be constant."); + + _add(column); + + return *this; +} + +template +inline Heap_column& Heap_column::operator+=(Heap_column& column) +{ + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + // assumes that the addition never zeros out this column. + if (_add(column)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } else { + _add(column); + } + + return *this; +} + +template +inline Heap_column& Heap_column::operator*=(unsigned int v) +{ + if constexpr (Master_matrix::Option_list::is_z2) { + if (v % 2 == 0) { + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + throw std::invalid_argument("A chain column should not be multiplied by 0."); + } else { + clear(); + } + } + } else { + Field_element_type val = operators_->get_value(v); + + if (val == Field_operators::get_additive_identity()) { + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + throw std::invalid_argument("A chain column should not be multiplied by 0."); + } else { + clear(); + } + return *this; + } + + if (val == Field_operators::get_multiplicative_identity()) return *this; + + for (Cell* cell : column_) { + operators_->multiply_inplace(cell->get_element(), val); + } + } + + return *this; +} + +template +template +inline Heap_column& Heap_column::multiply_target_and_add( + const Field_element_type& val, const Cell_range& column) +{ + static_assert((!Master_matrix::isNonBasic || std::is_same_v), + "For boundary columns, the range has to be a column of same type to help ensure the validity of the " + "base element."); // could be removed, if we give the responsability to the user. + static_assert((!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type), + "For chain columns, the given column cannot be constant."); + + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + _add(column); + } else { + clear(); + _add(column); + } + } else { + _multiply_target_and_add(val, column); + } + + return *this; +} + +template +inline Heap_column& Heap_column::multiply_target_and_add( + const Field_element_type& val, Heap_column& column) +{ + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + // assumes that the addition never zeros out this column. + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + if (_add(column)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } else { + throw std::invalid_argument("A chain column should not be multiplied by 0."); + } + } else { + if (_multiply_target_and_add(val, column)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } + } else { + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + _add(column); + } else { + clear(); + _add(column); + } + } else { + _multiply_target_and_add(val, column); + } + } + + return *this; +} + +template +template +inline Heap_column& Heap_column::multiply_source_and_add( + const Cell_range& column, const Field_element_type& val) +{ + static_assert((!Master_matrix::isNonBasic || std::is_same_v), + "For boundary columns, the range has to be a column of same type to help ensure the validity of the " + "base element."); // could be removed, if we give the responsability to the user. + static_assert((!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type), + "For chain columns, the given column cannot be constant."); + + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + _add(column); + } + } else { + _multiply_source_and_add(column, val); + } + + return *this; +} + +template +inline Heap_column& Heap_column::multiply_source_and_add( + Heap_column& column, const Field_element_type& val) +{ + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + // assumes that the addition never zeros out this column. + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + if (_add(column)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } + } else { + if (_multiply_source_and_add(column, val)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } + } else { + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + _add(column); + } + } else { + _multiply_source_and_add(column, val); + } + } + + return *this; +} + +template +inline Heap_column& Heap_column::operator=(const Heap_column& other) +{ + static_assert(!Master_matrix::Option_list::has_row_access, "= assignement not enabled with row access option."); + + dim_opt::operator=(other); + chain_opt::operator=(other); + + while (column_.size() > other.column_.size()) { + if (column_.back() != nullptr) cellPool_->destroy(column_.back()); + column_.pop_back(); + } + + column_.resize(other.column_.size(), nullptr); + index i = 0; + for (const Cell* cell : other.column_) { + if (column_[i] != nullptr) { + cellPool_->destroy(column_[i]); + } + column_[i] = other.cellPool_->construct(cell->get_row_index()); + if constexpr (!Master_matrix::Option_list::is_z2) { + column_[i]->set_element(cell->get_element()); + } + ++i; + } + insertsSinceLastPrune_ = other.insertsSinceLastPrune_; + operators_ = other.operators_; + cellPool_ = other.cellPool_; + + return *this; +} + +template +inline std::size_t Heap_column::compute_hash_value() +{ + _prune(); + return hash_column(*this); +} + +template +inline void Heap_column::_prune() +{ + if (insertsSinceLastPrune_ == 0) return; + + Column_type tempCol; + Cell* pivot = _pop_pivot(); + while (pivot != nullptr) { + tempCol.push_back(pivot); + pivot = _pop_pivot(); + } + column_.swap(tempCol); + + insertsSinceLastPrune_ = 0; +} + +template +inline typename Heap_column::Cell* +Heap_column::_pop_pivot() +{ + if (column_.empty()) { + return nullptr; + } + + Cell* pivot = column_.front(); + std::pop_heap(column_.begin(), column_.end(), cellPointerComp_); + column_.pop_back(); + if constexpr (Master_matrix::Option_list::is_z2) { + while (!column_.empty() && column_.front()->get_row_index() == pivot->get_row_index()) { + std::pop_heap(column_.begin(), column_.end(), cellPointerComp_); + cellPool_->destroy(column_.back()); + column_.pop_back(); + + cellPool_->destroy(pivot); + if (column_.empty()) { + return nullptr; + } + pivot = column_.front(); + std::pop_heap(column_.begin(), column_.end(), cellPointerComp_); + column_.pop_back(); + } + } else { + while (!column_.empty() && column_.front()->get_row_index() == pivot->get_row_index()) { + operators_->add_inplace(pivot->get_element(), column_.front()->get_element()); + std::pop_heap(column_.begin(), column_.end(), cellPointerComp_); + cellPool_->destroy(column_.back()); + column_.pop_back(); + } + + if (pivot->get_element() == Field_operators::get_additive_identity()) { + cellPool_->destroy(pivot); + return _pop_pivot(); + } + } + + return pivot; +} + +template +template +inline bool Heap_column::_add(const Cell_range& column) +{ + if (column.begin() == column.end()) return false; + if (column_.empty()) { // chain should never enter here. + column_.resize(column.size()); + index i = 0; + for (const Cell& cell : column) { + column_[i] = cellPool_->construct(cell.get_row_index()); + if constexpr (!Master_matrix::Option_list::is_z2) { + column_[i]->set_element(cell.get_element()); + } + ++i; + } + insertsSinceLastPrune_ = column_.size(); + return true; + } + + Field_element_type pivotVal(1); + + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) + pivotVal = get_pivot_value(); + + for (const Cell& cell : column) { + ++insertsSinceLastPrune_; + if constexpr (Master_matrix::Option_list::is_z2) { + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + if (cell.get_row_index() == chain_opt::get_pivot()) pivotVal = !pivotVal; + } + column_.push_back(cellPool_->construct(cell.get_row_index())); + } else { + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + if (cell.get_row_index() == chain_opt::get_pivot()) operators_->add_inplace(pivotVal, cell.get_element()); + } + column_.push_back(cellPool_->construct(cell.get_row_index())); + column_.back()->set_element(cell.get_element()); + } + std::push_heap(column_.begin(), column_.end(), cellPointerComp_); + } + + if (2 * insertsSinceLastPrune_ > column_.size()) _prune(); + + if constexpr (Master_matrix::Option_list::is_z2) + return !pivotVal; + else + return pivotVal == Field_operators::get_additive_identity(); +} + +template +template +inline bool Heap_column::_multiply_target_and_add(const Field_element_type& val, + const Cell_range& column) +{ + if (val == 0u) { + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + throw std::invalid_argument("A chain column should not be multiplied by 0."); + // this would not only mess up the base, but also the pivots stored. + } else { + clear(); + } + } + if (column_.empty()) { // chain should never enter here. + column_.resize(column.size()); + index i = 0; + for (const Cell& cell : column) { + column_[i] = cellPool_->construct(cell.get_row_index()); + column_[i++]->set_element(cell.get_element()); + } + insertsSinceLastPrune_ = column_.size(); + return true; + } + + Field_element_type pivotVal(0); + + for (Cell* cell : column_) { + operators_->multiply_inplace(cell->get_element(), val); + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + if (cell->get_row_index() == chain_opt::get_pivot()) operators_->add_inplace(pivotVal, cell->get_element()); + } + } + + for (const Cell& cell : column) { + ++insertsSinceLastPrune_; + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + if (cell.get_row_index() == chain_opt::get_pivot()) operators_->add_inplace(pivotVal, cell.get_element()); + } + column_.push_back(cellPool_->construct(cell.get_row_index())); + column_.back()->set_element(cell.get_element()); + std::push_heap(column_.begin(), column_.end(), cellPointerComp_); + } + + if (2 * insertsSinceLastPrune_ > column_.size()) _prune(); + + if constexpr (Master_matrix::Option_list::is_z2) + return !pivotVal; + else + return pivotVal == Field_operators::get_additive_identity(); +} + +template +template +inline bool Heap_column::_multiply_source_and_add(const Cell_range& column, + const Field_element_type& val) +{ + if (val == 0u || column.begin() == column.end()) { + return false; + } + if (column_.empty()) { // chain should never enter here. + column_.resize(column.size()); + index i = 0; + for (const Cell& cell : column) { + column_[i] = cellPool_->construct(cell.get_row_index()); + column_[i++]->set_element(cell.get_element()); + } + insertsSinceLastPrune_ = column_.size(); + return true; + } + + Field_element_type pivotVal(1); + + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) + pivotVal = get_pivot_value(); + + for (const Cell& cell : column) { + ++insertsSinceLastPrune_; + column_.push_back(cellPool_->construct(cell.get_row_index())); + column_.back()->set_element(cell.get_element()); + operators_->multiply_inplace(column_.back()->get_element(), val); + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + if (cell.get_row_index() == chain_opt::get_pivot()){ + operators_->add_inplace(pivotVal, column_.back()->get_element()); + } + } + std::push_heap(column_.begin(), column_.end(), cellPointerComp_); + } + + if (2 * insertsSinceLastPrune_ > column_.size()) _prune(); + + return pivotVal == 0u; +} + +} // namespace persistence_matrix +} // namespace Gudhi + +/** + * @ingroup persistence_matrix + * + * @brief Hash method for @ref Gudhi::persistence_matrix::Heap_column. + * + * @tparam Master_matrix Template parameter of @ref Gudhi::persistence_matrix::Heap_column. + * @tparam Cell_constructor Template parameter of @ref Gudhi::persistence_matrix::Heap_column. + */ +template +struct std::hash > +{ + size_t operator()(Gudhi::persistence_matrix::Heap_column& column) const { + return column.compute_hash_value(); + } +}; + +#endif // PM_HEAP_COLUMN_H diff --git a/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/intrusive_list_column.h b/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/intrusive_list_column.h new file mode 100644 index 0000000000..4d67e8d2e2 --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/intrusive_list_column.h @@ -0,0 +1,937 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file intrusive_list_column.h + * @author Hannah Schreiber + * @brief Contains the @ref Intrusive_list_column class. + * Also defines the std::hash method for @ref Intrusive_list_column. + */ + +#ifndef PM_INTRUSIVE_LIST_COLUMN_H +#define PM_INTRUSIVE_LIST_COLUMN_H + +#include +#include +#include +#include //std::swap, std::move & std::exchange + +#include + +#include +#include + +namespace Gudhi { +namespace persistence_matrix { + +/** + * @class Intrusive_list_column intrusive_list_column.h gudhi/Persistence_matrix/columns/intrusive_list_column.h + * @ingroup persistence_matrix + * + * @brief Column class following the @ref PersistenceMatrixColumn concept. + * + * Column based on a intrusive list structure. The cells are always ordered by row index and only non-zero values + * are stored uniquely in the underlying container. + * + * @tparam Master_matrix An instanciation of @ref Matrix from which all types and options are deduced. + * @tparam Cell_constructor Factory of @ref Cell classes. + */ +template +class Intrusive_list_column : public Master_matrix::Row_access_option, + public Master_matrix::Column_dimension_option, + public Master_matrix::Chain_column_option +{ + public: + using Master = Master_matrix; + using index = typename Master_matrix::index; + using id_index = typename Master_matrix::id_index; + using dimension_type = typename Master_matrix::dimension_type; + using Field_element_type = typename Master_matrix::element_type; + using Cell = typename Master_matrix::Cell_type; + using Column_settings = typename Master_matrix::Column_settings; + + private: + using Field_operators = typename Master_matrix::Field_operators; + using Column_type = + boost::intrusive::list, + boost::intrusive::base_hook >; + using Cell_constructor = typename Master_matrix::Cell_constructor; + + public: + using iterator = typename Column_type::iterator; + using const_iterator = typename Column_type::const_iterator; + using reverse_iterator = typename Column_type::reverse_iterator; + using const_reverse_iterator = typename Column_type::const_reverse_iterator; + + Intrusive_list_column(Column_settings* colSettings = nullptr); + template + Intrusive_list_column(const Container_type& nonZeroRowIndices, + Column_settings* colSettings); + template + Intrusive_list_column(index columnIndex, + const Container_type& nonZeroRowIndices, + Row_container_type* rowContainer, + Column_settings* colSettings); + template + Intrusive_list_column(const Container_type& nonZeroChainRowIndices, + dimension_type dimension, + Column_settings* colSettings); + template + Intrusive_list_column(index columnIndex, + const Container_type& nonZeroChainRowIndices, + dimension_type dimension, + Row_container_type* rowContainer, + Column_settings* colSettings); + Intrusive_list_column(const Intrusive_list_column& column, + Column_settings* colSettings = nullptr); + template + Intrusive_list_column(const Intrusive_list_column& column, + index columnIndex, + Row_container_type* rowContainer, + Column_settings* colSettings = nullptr); + Intrusive_list_column(Intrusive_list_column&& column) noexcept; + ~Intrusive_list_column(); + + std::vector get_content(int columnLength = -1) const; + bool is_non_zero(id_index rowIndex) const; + bool is_empty() const; + std::size_t size() const; + + template + void reorder(const Map_type& valueMap, [[maybe_unused]] index columnIndex = -1); + void clear(); + void clear(id_index rowIndex); + + id_index get_pivot() const; + Field_element_type get_pivot_value() const; + + iterator begin() noexcept; + const_iterator begin() const noexcept; + iterator end() noexcept; + const_iterator end() const noexcept; + reverse_iterator rbegin() noexcept; + const_reverse_iterator rbegin() const noexcept; + reverse_iterator rend() noexcept; + const_reverse_iterator rend() const noexcept; + + template + Intrusive_list_column& operator+=(const Cell_range& column); + Intrusive_list_column& operator+=(Intrusive_list_column& column); + + Intrusive_list_column& operator*=(const Field_element_type& val); + + // this = v * this + column + template + Intrusive_list_column& multiply_target_and_add(const Field_element_type& val, const Cell_range& column); + Intrusive_list_column& multiply_target_and_add(const Field_element_type& val, Intrusive_list_column& column); + // this = this + column * v + template + Intrusive_list_column& multiply_source_and_add(const Cell_range& column, const Field_element_type& val); + Intrusive_list_column& multiply_source_and_add(Intrusive_list_column& column, const Field_element_type& val); + + friend bool operator==(const Intrusive_list_column& c1, const Intrusive_list_column& c2) { + if (&c1 == &c2) return true; + + if constexpr (Master_matrix::Option_list::is_z2) { + return c1.column_ == c2.column_; + } else { + auto it1 = c1.column_.begin(); + auto it2 = c2.column_.begin(); + if (c1.column_.size() != c2.column_.size()) return false; + while (it1 != c1.column_.end() && it2 != c2.column_.end()) { + if (it1->get_row_index() != it2->get_row_index() || it1->get_element() != it2->get_element()) return false; + ++it1; + ++it2; + } + return true; + } + } + friend bool operator<(const Intrusive_list_column& c1, const Intrusive_list_column& c2) { + if (&c1 == &c2) return false; + + if constexpr (Master_matrix::Option_list::is_z2) { + return c1.column_ < c2.column_; + } else { + auto it1 = c1.column_.begin(); + auto it2 = c2.column_.begin(); + while (it1 != c1.column_.end() && it2 != c2.column_.end()) { + if (it1->get_row_index() != it2->get_row_index()) return it1->get_row_index() < it2->get_row_index(); + if (it1->get_element() != it2->get_element()) return it1->get_element() < it2->get_element(); + ++it1; + ++it2; + } + return it2 != c2.column_.end(); + } + } + + // Disabled with row access. + Intrusive_list_column& operator=(const Intrusive_list_column& other); + + friend void swap(Intrusive_list_column& col1, Intrusive_list_column& col2) { + swap(static_cast(col1), + static_cast(col2)); + swap(static_cast(col1), + static_cast(col2)); + swap(static_cast(col1), + static_cast(col2)); + col1.column_.swap(col2.column_); + std::swap(col1.operators_, col2.operators_); + std::swap(col1.cellPool_, col2.cellPool_); + } + + private: + using ra_opt = typename Master_matrix::Row_access_option; + using dim_opt = typename Master_matrix::Column_dimension_option; + using chain_opt = typename Master_matrix::Chain_column_option; + + // Cloner object function for boost intrusive container + struct new_cloner { + new_cloner(Cell_constructor* cellPool) : cellPool_(cellPool){}; + + Cell* operator()(const Cell& clone_this) { return cellPool_->construct(clone_this); } + + Cell_constructor* cellPool_; + }; + + // The disposer object function for boost intrusive container + struct delete_disposer { + delete_disposer(){}; + delete_disposer(Intrusive_list_column* col) : col_(col){}; + + void operator()(Cell* delete_this) { + if constexpr (Master_matrix::Option_list::has_row_access) col_->unlink(delete_this); + col_->cellPool_->destroy(delete_this); + } + + Intrusive_list_column* col_; + }; + + Field_operators* operators_; + Cell_constructor* cellPool_; + Column_type column_; + + template + friend void _generic_merge_cell_to_column(Column_type& targetColumn, + Cell_iterator& itSource, + typename Column_type::Column_type::iterator& itTarget, + F1&& process_target, + F2&& process_source, + F3&& update_target1, + F4&& update_target2, + bool& pivotIsZeroed); + template + friend bool _generic_add_to_column(const Cell_range& source, + Column_type& targetColumn, + F1&& process_target, + F2&& process_source, + F3&& update_target1, + F4&& update_target2, + F5&& finish_target); + template + friend bool _add_to_column(const Cell_range& source, Column_type& targetColumn); + template + friend bool _multiply_target_and_add_to_column(const typename Column_type::Field_element_type& val, + const Cell_range& source, + Column_type& targetColumn); + template + friend bool _multiply_source_and_add_to_column(const typename Column_type::Field_element_type& val, + const Cell_range& source, + Column_type& targetColumn); + + void _delete_cell(iterator& it); + Cell* _insert_cell(const Field_element_type& value, id_index rowIndex, const iterator& position); + void _insert_cell(id_index rowIndex, const iterator& position); + template + bool _add(const Cell_range& column); + template + bool _multiply_target_and_add(const Field_element_type& val, const Cell_range& column); + template + bool _multiply_source_and_add(const Cell_range& column, const Field_element_type& val); +}; + +template +inline Intrusive_list_column::Intrusive_list_column(Column_settings* colSettings) + : ra_opt(), + dim_opt(), + chain_opt(), + operators_(nullptr), + cellPool_(colSettings == nullptr ? nullptr : &(colSettings->cellConstructor)), + column_() +{ + if (colSettings == nullptr) return; // to allow default constructor which gives a dummy column + if constexpr (!Master_matrix::Option_list::is_z2) { + operators_ = &(colSettings->operators); + } +} + +template +template +inline Intrusive_list_column::Intrusive_list_column( + const Container_type& nonZeroRowIndices, Column_settings* colSettings) + : ra_opt(), + dim_opt(nonZeroRowIndices.size() == 0 ? 0 : nonZeroRowIndices.size() - 1), + chain_opt(), + operators_(nullptr), + cellPool_(&(colSettings->cellConstructor)), + column_() +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Constructor not available for chain columns, please specify the dimension of the chain."); + + if constexpr (Master_matrix::Option_list::is_z2) { + for (id_index id : nonZeroRowIndices) { + _insert_cell(id, column_.end()); + } + } else { + operators_ = &(colSettings->operators); + for (const auto& p : nonZeroRowIndices) { + _insert_cell(operators_->get_value(p.second), p.first, column_.end()); + } + } +} + +template +template +inline Intrusive_list_column::Intrusive_list_column( + index columnIndex, + const Container_type& nonZeroRowIndices, + Row_container_type* rowContainer, + Column_settings* colSettings) + : ra_opt(columnIndex, rowContainer), + dim_opt(nonZeroRowIndices.size() == 0 ? 0 : nonZeroRowIndices.size() - 1), + chain_opt([&] { + if constexpr (Master_matrix::Option_list::is_z2) { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + } else { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + } + }()), + operators_(nullptr), + cellPool_(&(colSettings->cellConstructor)), + column_() +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Constructor not available for chain columns, please specify the dimension of the chain."); + + if constexpr (Master_matrix::Option_list::is_z2) { + for (id_index id : nonZeroRowIndices) { + _insert_cell(id, column_.end()); + } + } else { + operators_ = &(colSettings->operators); + for (const auto& p : nonZeroRowIndices) { + _insert_cell(operators_->get_value(p.second), p.first, column_.end()); + } + } +} + +template +template +inline Intrusive_list_column::Intrusive_list_column( + const Container_type& nonZeroRowIndices, + dimension_type dimension, + Column_settings* colSettings) + : ra_opt(), + dim_opt(dimension), + chain_opt([&] { + if constexpr (Master_matrix::Option_list::is_z2) { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + } else { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + } + }()), + operators_(nullptr), + cellPool_(&(colSettings->cellConstructor)), + column_() +{ + if constexpr (Master_matrix::Option_list::is_z2) { + for (id_index id : nonZeroRowIndices) { + _insert_cell(id, column_.end()); + } + } else { + operators_ = &(colSettings->operators); + for (const auto& p : nonZeroRowIndices) { + _insert_cell(operators_->get_value(p.second), p.first, column_.end()); + } + } +} + +template +template +inline Intrusive_list_column::Intrusive_list_column( + index columnIndex, + const Container_type& nonZeroRowIndices, + dimension_type dimension, + Row_container_type* rowContainer, + Column_settings* colSettings) + : ra_opt(columnIndex, rowContainer), + dim_opt(dimension), + chain_opt([&] { + if constexpr (Master_matrix::Option_list::is_z2) { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + } else { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + } + }()), + operators_(nullptr), + cellPool_(&(colSettings->cellConstructor)), + column_() +{ + if constexpr (Master_matrix::Option_list::is_z2) { + for (id_index id : nonZeroRowIndices) { + _insert_cell(id, column_.end()); + } + } else { + operators_ = &(colSettings->operators); + for (const auto& p : nonZeroRowIndices) { + _insert_cell(operators_->get_value(p.second), p.first, column_.end()); + } + } +} + +template +inline Intrusive_list_column::Intrusive_list_column( + const Intrusive_list_column& column, Column_settings* colSettings) + : ra_opt(), + dim_opt(static_cast(column)), + chain_opt(static_cast(column)), + operators_(colSettings == nullptr ? column.operators_ : nullptr), + cellPool_(colSettings == nullptr ? column.cellPool_ : &(colSettings->cellConstructor)), + column_() +{ + static_assert(!Master_matrix::Option_list::has_row_access, + "Simple copy constructor not available when row access option enabled. Please specify the new column " + "index and the row container."); + if constexpr (!Master_matrix::Option_list::is_z2){ + if (colSettings != nullptr) operators_ = &(colSettings->operators); + } + + column_.clone_from(column.column_, new_cloner(cellPool_), delete_disposer(this)); +} + +template +template +inline Intrusive_list_column::Intrusive_list_column( + const Intrusive_list_column& column, index columnIndex, Row_container_type* rowContainer, + Column_settings* colSettings) + : ra_opt(columnIndex, rowContainer), + dim_opt(static_cast(column)), + chain_opt(static_cast(column)), + operators_(colSettings == nullptr ? column.operators_ : nullptr), + cellPool_(colSettings == nullptr ? column.cellPool_ : &(colSettings->cellConstructor)), + column_() +{ + if constexpr (!Master_matrix::Option_list::is_z2){ + if (colSettings != nullptr) operators_ = &(colSettings->operators); + } + + for (const Cell& cell : column.column_) { + if constexpr (Master_matrix::Option_list::is_z2) { + _insert_cell(cell.get_row_index(), column_.end()); + } else { + _insert_cell(cell.get_element(), cell.get_row_index(), column_.end()); + } + } +} + +template +inline Intrusive_list_column::Intrusive_list_column( + Intrusive_list_column&& column) noexcept + : ra_opt(std::move(static_cast(column))), + dim_opt(std::move(static_cast(column))), + chain_opt(std::move(static_cast(column))), + operators_(std::exchange(column.operators_, nullptr)), + cellPool_(std::exchange(column.cellPool_, nullptr)), + column_(std::move(column.column_)) +{} + +template +inline Intrusive_list_column::~Intrusive_list_column() +{ + column_.clear_and_dispose(delete_disposer(this)); +} + +template +inline std::vector::Field_element_type> +Intrusive_list_column::get_content(int columnLength) const +{ + if (columnLength < 0 && column_.size() > 0) + columnLength = column_.back().get_row_index() + 1; + else if (columnLength < 0) + return std::vector(); + + std::vector container(columnLength); + for (auto it = column_.begin(); it != column_.end() && it->get_row_index() < static_cast(columnLength); + ++it) { + if constexpr (Master_matrix::Option_list::is_z2) { + container[it->get_row_index()] = 1; + } else { + container[it->get_row_index()] = it->get_element(); + } + } + return container; +} + +template +inline bool Intrusive_list_column::is_non_zero(id_index rowIndex) const +{ + // could be changed to dichotomic search as column is ordered by row index, + // but I am not sure if it is really worth it as there is no random access + // and the columns should not be that long anyway. + for (const Cell& cell : column_) + if (cell.get_row_index() == rowIndex) return true; + + return false; +} + +template +inline bool Intrusive_list_column::is_empty() const +{ + return column_.empty(); +} + +template +inline std::size_t Intrusive_list_column::size() const +{ + return column_.size(); +} + +template +template +inline void Intrusive_list_column::reorder(const Map_type& valueMap, + [[maybe_unused]] index columnIndex) +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Method not available for chain columns."); + + for (auto it = column_.begin(); it != column_.end(); ++it) { + Cell* cell = &(*it); + if constexpr (Master_matrix::Option_list::has_row_access) { + ra_opt::unlink(cell); + if (columnIndex != static_cast(-1)) cell->set_column_index(columnIndex); + } + cell->set_row_index(valueMap.at(cell->get_row_index())); + if constexpr (Master_matrix::Option_list::has_intrusive_rows && Master_matrix::Option_list::has_row_access) + ra_opt::insert_cell(cell->get_row_index(), cell); + } + + // all cells have to be deleted first, to avoid problem with insertion when row is a set + if constexpr (!Master_matrix::Option_list::has_intrusive_rows && Master_matrix::Option_list::has_row_access) { + for (auto it = column_.begin(); it != column_.end(); ++it) { + Cell* cell = &(*it); + ra_opt::insert_cell(cell->get_row_index(), cell); + } + } + + column_.sort(); +} + +template +inline void Intrusive_list_column::clear() +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Method not available for chain columns as a base element should not be empty."); + + column_.clear_and_dispose(delete_disposer(this)); +} + +template +inline void Intrusive_list_column::clear(id_index rowIndex) +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Method not available for chain columns."); + + auto it = column_.begin(); + while (it != column_.end() && it->get_row_index() != rowIndex) it++; + if (it != column_.end()) _delete_cell(it); +} + +template +inline typename Intrusive_list_column::id_index +Intrusive_list_column::get_pivot() const +{ + static_assert(Master_matrix::isNonBasic, + "Method not available for base columns."); // could technically be, but is the notion usefull then? + + if constexpr (Master_matrix::Option_list::is_of_boundary_type) { + if (column_.empty()) return -1; + return column_.back().get_row_index(); + } else { + return chain_opt::get_pivot(); + } +} + +template +inline typename Intrusive_list_column::Field_element_type +Intrusive_list_column::get_pivot_value() const +{ + static_assert(Master_matrix::isNonBasic, + "Method not available for base columns."); // could technically be, but is the notion usefull then? + + if constexpr (Master_matrix::Option_list::is_z2) { + return 1; + } else { + if constexpr (Master_matrix::Option_list::is_of_boundary_type) { + if (column_.empty()) return 0; + return column_.back().get_element(); + } else { + if (chain_opt::get_pivot() == static_cast(-1)) return Field_element_type(); + for (const Cell& cell : column_) { + if (cell.get_row_index() == chain_opt::get_pivot()) return cell.get_element(); + } + return Field_element_type(); // should never happen if chain column is used properly + } + } +} + +template +inline typename Intrusive_list_column::iterator +Intrusive_list_column::begin() noexcept +{ + return column_.begin(); +} + +template +inline typename Intrusive_list_column::const_iterator +Intrusive_list_column::begin() const noexcept +{ + return column_.begin(); +} + +template +inline typename Intrusive_list_column::iterator +Intrusive_list_column::end() noexcept +{ + return column_.end(); +} + +template +inline typename Intrusive_list_column::const_iterator +Intrusive_list_column::end() const noexcept +{ + return column_.end(); +} + +template +inline typename Intrusive_list_column::reverse_iterator +Intrusive_list_column::rbegin() noexcept +{ + return column_.rbegin(); +} + +template +inline typename Intrusive_list_column::const_reverse_iterator +Intrusive_list_column::rbegin() const noexcept +{ + return column_.rbegin(); +} + +template +inline typename Intrusive_list_column::reverse_iterator +Intrusive_list_column::rend() noexcept +{ + return column_.rend(); +} + +template +inline typename Intrusive_list_column::const_reverse_iterator +Intrusive_list_column::rend() const noexcept +{ + return column_.rend(); +} + +template +template +inline Intrusive_list_column& +Intrusive_list_column::operator+=(const Cell_range& column) +{ + static_assert((!Master_matrix::isNonBasic || std::is_same_v), + "For boundary columns, the range has to be a column of same type to help ensure the validity of the " + "base element."); // could be removed, if we give the responsability to the user. + static_assert((!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type), + "For chain columns, the given column cannot be constant."); + + _add(column); + + return *this; +} + +template +inline Intrusive_list_column& +Intrusive_list_column::operator+=(Intrusive_list_column& column) +{ + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + // assumes that the addition never zeros out this column. + if (_add(column)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } else { + _add(column); + } + + return *this; +} + +template +inline Intrusive_list_column& +Intrusive_list_column::operator*=(const Field_element_type& val) +{ + if constexpr (Master_matrix::Option_list::is_z2) { + if (val % 2 == 0) { + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + throw std::invalid_argument("A chain column should not be multiplied by 0."); + } else { + clear(); + } + } + } else { + Field_element_type realVal = operators_->get_value(val); + + if (realVal == Field_operators::get_additive_identity()) { + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + throw std::invalid_argument("A chain column should not be multiplied by 0."); + } else { + clear(); + } + return *this; + } + + if (realVal == Field_operators::get_multiplicative_identity()) return *this; + + for (Cell& cell : column_) { + operators_->multiply_inplace(cell.get_element(), realVal); + if constexpr (Master_matrix::Option_list::has_row_access) ra_opt::update_cell(cell); + } + } + + return *this; +} + +template +template +inline Intrusive_list_column& +Intrusive_list_column::multiply_target_and_add(const Field_element_type& val, + const Cell_range& column) +{ + static_assert((!Master_matrix::isNonBasic || std::is_same_v), + "For boundary columns, the range has to be a column of same type to help ensure the validity of the " + "base element."); // could be removed, if we give the responsability to the user. + static_assert((!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type), + "For chain columns, the given column cannot be constant."); + + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + _add(column); + } else { + clear(); + _add(column); + } + } else { + _multiply_target_and_add(val, column); + } + + return *this; +} + +template +inline Intrusive_list_column& +Intrusive_list_column::multiply_target_and_add(const Field_element_type& val, + Intrusive_list_column& column) +{ + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + // assumes that the addition never zeros out this column. + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + if (_add(column)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } else { + throw std::invalid_argument("A chain column should not be multiplied by 0."); + } + } else { + if (_multiply_target_and_add(val, column)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } + } else { + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + _add(column); + } else { + clear(); + _add(column); + } + } else { + _multiply_target_and_add(val, column); + } + } + + return *this; +} + +template +template +inline Intrusive_list_column& +Intrusive_list_column::multiply_source_and_add(const Cell_range& column, + const Field_element_type& val) +{ + static_assert((!Master_matrix::isNonBasic || std::is_same_v), + "For boundary columns, the range has to be a column of same type to help ensure the validity of the " + "base element."); // could be removed, if we give the responsability to the user. + static_assert((!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type), + "For chain columns, the given column cannot be constant."); + + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + _add(column); + } + } else { + _multiply_source_and_add(column, val); + } + + return *this; +} + +template +inline Intrusive_list_column& +Intrusive_list_column::multiply_source_and_add(Intrusive_list_column& column, + const Field_element_type& val) +{ + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + // assumes that the addition never zeros out this column. + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + if (_add(column)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } + } else { + if (_multiply_source_and_add(column, val)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } + } else { + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + _add(column); + } + } else { + _multiply_source_and_add(column, val); + } + } + + return *this; +} + +template +inline Intrusive_list_column& +Intrusive_list_column::operator=(const Intrusive_list_column& other) +{ + static_assert(!Master_matrix::Option_list::has_row_access, "= assignement not enabled with row access option."); + + dim_opt::operator=(other); + chain_opt::operator=(other); + + // order is important + column_.clear_and_dispose(delete_disposer(this)); + operators_ = other.operators_; + cellPool_ = other.cellPool_; + column_.clone_from(other.column_, new_cloner(cellPool_), delete_disposer(this)); + + return *this; +} + +template +inline void Intrusive_list_column::_delete_cell(iterator& it) +{ + it = column_.erase_and_dispose(it, delete_disposer(this)); +} + +template +inline typename Intrusive_list_column::Cell* Intrusive_list_column::_insert_cell( + const Field_element_type& value, id_index rowIndex, const iterator& position) +{ + if constexpr (Master_matrix::Option_list::has_row_access) { + Cell* newCell = cellPool_->construct(ra_opt::columnIndex_, rowIndex); + newCell->set_element(value); + column_.insert(position, *newCell); + ra_opt::insert_cell(rowIndex, newCell); + return newCell; + } else { + Cell* newCell = cellPool_->construct(rowIndex); + newCell->set_element(value); + column_.insert(position, *newCell); + return newCell; + } +} + +template +inline void Intrusive_list_column::_insert_cell(id_index rowIndex, + const iterator& position) +{ + if constexpr (Master_matrix::Option_list::has_row_access) { + Cell* newCell = cellPool_->construct(ra_opt::columnIndex_, rowIndex); + column_.insert(position, *newCell); + ra_opt::insert_cell(rowIndex, newCell); + } else { + Cell* newCell = cellPool_->construct(rowIndex); + column_.insert(position, *newCell); + } +} + +template +template +inline bool Intrusive_list_column::_add(const Cell_range& column) +{ + return _add_to_column(column, *this); +} + +template +template +inline bool Intrusive_list_column::_multiply_target_and_add(const Field_element_type& val, + const Cell_range& column) +{ + return _multiply_target_and_add_to_column(val, column, *this); +} + +template +template +inline bool Intrusive_list_column::_multiply_source_and_add(const Cell_range& column, + const Field_element_type& val) +{ + return _multiply_source_and_add_to_column(val, column, *this); +} + +} // namespace persistence_matrix +} // namespace Gudhi + +/** + * @ingroup persistence_matrix + * + * @brief Hash method for @ref Gudhi::persistence_matrix::Intrusive_list_column. + * + * @tparam Master_matrix Template parameter of @ref Gudhi::persistence_matrix::Intrusive_list_column. + * @tparam Cell_constructor Template parameter of @ref Gudhi::persistence_matrix::Intrusive_list_column. + */ +template +struct std::hash > +{ + std::size_t operator()(const Gudhi::persistence_matrix::Intrusive_list_column& column) const { + return Gudhi::persistence_matrix::hash_column(column); + } +}; + +#endif // PM_INTRUSIVE_LIST_COLUMN_H diff --git a/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/intrusive_set_column.h b/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/intrusive_set_column.h new file mode 100644 index 0000000000..9c8b418954 --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/intrusive_set_column.h @@ -0,0 +1,943 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file intrusive_set_column.h + * @author Hannah Schreiber + * @brief Contains the @ref Intrusive_set_column class. + * Also defines the std::hash method for @ref Intrusive_set_column. + */ + +#ifndef PM_INTRUSIVE_SET_COLUMN_H +#define PM_INTRUSIVE_SET_COLUMN_H + +#include +#include +#include +#include //std::swap, std::move & std::exchange + +#include + +#include +#include +#include + +namespace Gudhi { +namespace persistence_matrix { + +/** + * @class Intrusive_set_column intrusive_set_column.h gudhi/Persistence_matrix/columns/intrusive_set_column.h + * @ingroup persistence_matrix + * + * @brief Column class following the @ref PersistenceMatrixColumn concept. + * + * Column based on a intrusive set structure. The cells are ordered by row index and only non-zero values + * are stored uniquely in the underlying container. + * + * @tparam Master_matrix An instanciation of @ref Matrix from which all types and options are deduced. + * @tparam Cell_constructor Factory of @ref Cell classes. + */ +template +class Intrusive_set_column : public Master_matrix::Row_access_option, + public Master_matrix::Column_dimension_option, + public Master_matrix::Chain_column_option +{ + public: + using Master = Master_matrix; + using index = typename Master_matrix::index; + using id_index = typename Master_matrix::id_index; + using dimension_type = typename Master_matrix::dimension_type; + using Field_element_type = typename Master_matrix::element_type; + using Cell = typename Master_matrix::Cell_type; + using Column_settings = typename Master_matrix::Column_settings; + + private: + using Field_operators = typename Master_matrix::Field_operators; + using Column_type = + boost::intrusive::set, + boost::intrusive::base_hook >; + using Cell_constructor = typename Master_matrix::Cell_constructor; + + public: + using iterator = typename Column_type::iterator; + using const_iterator = typename Column_type::const_iterator; + using reverse_iterator = typename Column_type::reverse_iterator; + using const_reverse_iterator = typename Column_type::const_reverse_iterator; + + Intrusive_set_column(Column_settings* colSettings = nullptr); + template + Intrusive_set_column(const Container_type& nonZeroRowIndices, + Column_settings* colSettings); + template + Intrusive_set_column(index columnIndex, + const Container_type& nonZeroRowIndices, + Row_container_type* rowContainer, + Column_settings* colSettings); + template + Intrusive_set_column(const Container_type& nonZeroChainRowIndices, + dimension_type dimension, + Column_settings* colSettings); + template + Intrusive_set_column(index columnIndex, + const Container_type& nonZeroChainRowIndices, + dimension_type dimension, + Row_container_type* rowContainer, + Column_settings* colSettings); + Intrusive_set_column(const Intrusive_set_column& column, + Column_settings* colSettings = nullptr); + template + Intrusive_set_column(const Intrusive_set_column& column, + index columnIndex, + Row_container_type* rowContainer, + Column_settings* colSettings = nullptr); + Intrusive_set_column(Intrusive_set_column&& column) noexcept; + ~Intrusive_set_column(); + + std::vector get_content(int columnLength = -1) const; + bool is_non_zero(id_index rowIndex) const; + bool is_empty() const; + std::size_t size() const; + + template + void reorder(const Map_type& valueMap, [[maybe_unused]] index columnIndex = -1); + void clear(); + void clear(id_index rowIndex); + + id_index get_pivot() const; + Field_element_type get_pivot_value() const; + + iterator begin() noexcept; + const_iterator begin() const noexcept; + iterator end() noexcept; + const_iterator end() const noexcept; + reverse_iterator rbegin() noexcept; + const_reverse_iterator rbegin() const noexcept; + reverse_iterator rend() noexcept; + const_reverse_iterator rend() const noexcept; + + template + Intrusive_set_column& operator+=(const Cell_range& column); + Intrusive_set_column& operator+=(Intrusive_set_column& column); + + Intrusive_set_column& operator*=(unsigned int v); + + // this = v * this + column + template + Intrusive_set_column& multiply_target_and_add(const Field_element_type& val, const Cell_range& column); + Intrusive_set_column& multiply_target_and_add(const Field_element_type& val, Intrusive_set_column& column); + // this = this + column * v + template + Intrusive_set_column& multiply_source_and_add(const Cell_range& column, const Field_element_type& val); + Intrusive_set_column& multiply_source_and_add(Intrusive_set_column& column, const Field_element_type& val); + + friend bool operator==(const Intrusive_set_column& c1, const Intrusive_set_column& c2) { + if (&c1 == &c2) return true; + + if constexpr (Master_matrix::Option_list::is_z2) { + return c1.column_ == c2.column_; + } else { + auto it1 = c1.column_.begin(); + auto it2 = c2.column_.begin(); + if (c1.column_.size() != c2.column_.size()) return false; + while (it1 != c1.column_.end() && it2 != c2.column_.end()) { + if (it1->get_row_index() != it2->get_row_index() || it1->get_element() != it2->get_element()) return false; + ++it1; + ++it2; + } + return true; + } + } + friend bool operator<(const Intrusive_set_column& c1, const Intrusive_set_column& c2) { + if (&c1 == &c2) return false; + + if constexpr (Master_matrix::Option_list::is_z2) { + return c1.column_ < c2.column_; + } else { + auto it1 = c1.column_.begin(); + auto it2 = c2.column_.begin(); + while (it1 != c1.column_.end() && it2 != c2.column_.end()) { + if (it1->get_row_index() != it2->get_row_index()) return it1->get_row_index() < it2->get_row_index(); + if (it1->get_element() != it2->get_element()) return it1->get_element() < it2->get_element(); + ++it1; + ++it2; + } + return it2 != c2.column_.end(); + } + } + + // Disabled with row access. + Intrusive_set_column& operator=(const Intrusive_set_column& other); + + friend void swap(Intrusive_set_column& col1, Intrusive_set_column& col2) { + swap(static_cast(col1), + static_cast(col2)); + swap(static_cast(col1), + static_cast(col2)); + swap(static_cast(col1), + static_cast(col2)); + col1.column_.swap(col2.column_); + std::swap(col1.operators_, col2.operators_); + std::swap(col1.cellPool_, col2.cellPool_); + } + + private: + using ra_opt = typename Master_matrix::Row_access_option; + using dim_opt = typename Master_matrix::Column_dimension_option; + using chain_opt = typename Master_matrix::Chain_column_option; + + // Cloner object function for boost intrusive container + struct new_cloner { + new_cloner(Cell_constructor* cellPool) : cellPool_(cellPool){}; + + Cell* operator()(const Cell& clone_this) { return cellPool_->construct(clone_this); } + + Cell_constructor* cellPool_; + }; + + // The disposer object function for boost intrusive container + struct delete_disposer { + delete_disposer(){}; + delete_disposer(Intrusive_set_column* col) : col_(col){}; + + void operator()(Cell* delete_this) { + if constexpr (Master_matrix::Option_list::has_row_access) col_->unlink(delete_this); + col_->cellPool_->destroy(delete_this); + } + + Intrusive_set_column* col_; + }; + + Column_type column_; + Field_operators* operators_; + Cell_constructor* cellPool_; + + template + friend void _generic_merge_cell_to_column(Column_type& targetColumn, + Cell_iterator& itSource, + typename Column_type::Column_type::iterator& itTarget, + F1&& process_target, + F2&& process_source, + F3&& update_target1, + F4&& update_target2, + bool& pivotIsZeroed); + template + friend bool _generic_add_to_column(const Cell_range& source, + Column_type& targetColumn, + F1&& process_target, + F2&& process_source, + F3&& update_target1, + F4&& update_target2, + F5&& finish_target); + template + friend bool _add_to_column(const Cell_range& source, Column_type& targetColumn); + template + friend bool _multiply_target_and_add_to_column(const typename Column_type::Field_element_type& val, + const Cell_range& source, + Column_type& targetColumn); + template + friend bool _multiply_source_and_add_to_column(const typename Column_type::Field_element_type& val, + const Cell_range& source, + Column_type& targetColumn); + + void _delete_cell(iterator& it); + Cell* _insert_cell(const Field_element_type& value, id_index rowIndex, const iterator& position); + void _insert_cell(id_index rowIndex, const iterator& position); + template + bool _add(const Cell_range& column); + template + bool _multiply_target_and_add(const Field_element_type& val, const Cell_range& column); + template + bool _multiply_source_and_add(const Cell_range& column, const Field_element_type& val); +}; + +template +inline Intrusive_set_column::Intrusive_set_column(Column_settings* colSettings) + : ra_opt(), + dim_opt(), + chain_opt(), + operators_(nullptr), + cellPool_(colSettings == nullptr ? nullptr : &(colSettings->cellConstructor)) +{ + if (operators_ == nullptr && cellPool_ == nullptr) return; // to allow default constructor which gives a dummy column + if constexpr (!Master_matrix::Option_list::is_z2) { + operators_ = &(colSettings->operators); + } +} + +template +template +inline Intrusive_set_column::Intrusive_set_column( + const Container_type& nonZeroRowIndices, Column_settings* colSettings) + : ra_opt(), + dim_opt(nonZeroRowIndices.size() == 0 ? 0 : nonZeroRowIndices.size() - 1), + chain_opt(), + operators_(nullptr), + cellPool_(&(colSettings->cellConstructor)) +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Constructor not available for chain columns, please specify the dimension of the chain."); + + if constexpr (Master_matrix::Option_list::is_z2) { + for (id_index id : nonZeroRowIndices) { + _insert_cell(id, column_.end()); + } + } else { + operators_ = &(colSettings->operators); + for (const auto& p : nonZeroRowIndices) { + _insert_cell(operators_->get_value(p.second), p.first, column_.end()); + } + } +} + +template +template +inline Intrusive_set_column::Intrusive_set_column( + index columnIndex, + const Container_type& nonZeroRowIndices, + Row_container_type* rowContainer, + Column_settings* colSettings) + : ra_opt(columnIndex, rowContainer), + dim_opt(nonZeroRowIndices.size() == 0 ? 0 : nonZeroRowIndices.size() - 1), + chain_opt([&] { + if constexpr (Master_matrix::Option_list::is_z2) { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + } else { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + } + }()), + operators_(nullptr), + cellPool_(&(colSettings->cellConstructor)) +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Constructor not available for chain columns, please specify the dimension of the chain."); + + if constexpr (Master_matrix::Option_list::is_z2) { + for (id_index id : nonZeroRowIndices) { + _insert_cell(id, column_.end()); + } + } else { + operators_ = &(colSettings->operators); + for (const auto& p : nonZeroRowIndices) { + _insert_cell(operators_->get_value(p.second), p.first, column_.end()); + } + } +} + +template +template +inline Intrusive_set_column::Intrusive_set_column( + const Container_type& nonZeroRowIndices, + dimension_type dimension, + Column_settings* colSettings) + : ra_opt(), + dim_opt(dimension), + chain_opt([&] { + if constexpr (Master_matrix::Option_list::is_z2) { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + } else { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + } + }()), + operators_(nullptr), + cellPool_(&(colSettings->cellConstructor)) +{ + if constexpr (Master_matrix::Option_list::is_z2) { + for (id_index id : nonZeroRowIndices) { + _insert_cell(id, column_.end()); + } + } else { + operators_ = &(colSettings->operators); + for (const auto& p : nonZeroRowIndices) { + _insert_cell(operators_->get_value(p.second), p.first, column_.end()); + } + } +} + +template +template +inline Intrusive_set_column::Intrusive_set_column( + index columnIndex, + const Container_type& nonZeroRowIndices, + dimension_type dimension, + Row_container_type* rowContainer, + Column_settings* colSettings) + : ra_opt(columnIndex, rowContainer), + dim_opt(dimension), + chain_opt([&] { + if constexpr (Master_matrix::Option_list::is_z2) { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + } else { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + } + }()), + operators_(nullptr), + cellPool_(&(colSettings->cellConstructor)) +{ + if constexpr (Master_matrix::Option_list::is_z2) { + for (id_index id : nonZeroRowIndices) { + _insert_cell(id, column_.end()); + } + } else { + operators_ = &(colSettings->operators); + for (const auto& p : nonZeroRowIndices) { + _insert_cell(operators_->get_value(p.second), p.first, column_.end()); + } + } +} + +template +inline Intrusive_set_column::Intrusive_set_column(const Intrusive_set_column& column, + Column_settings* colSettings) + : ra_opt(), + dim_opt(static_cast(column)), + chain_opt(static_cast(column)), + operators_(colSettings == nullptr ? column.operators_ : nullptr), + cellPool_(colSettings == nullptr ? column.cellPool_ : &(colSettings->cellConstructor)) +{ + static_assert(!Master_matrix::Option_list::has_row_access, + "Simple copy constructor not available when row access option enabled. Please specify the new column " + "index and the row container."); + + if constexpr (!Master_matrix::Option_list::is_z2){ + if (colSettings != nullptr) operators_ = &(colSettings->operators); + } + + column_.clone_from(column.column_, new_cloner(cellPool_), delete_disposer(this)); +} + +template +template +inline Intrusive_set_column::Intrusive_set_column(const Intrusive_set_column& column, + index columnIndex, + Row_container_type* rowContainer, + Column_settings* colSettings) + : ra_opt(columnIndex, rowContainer), + dim_opt(static_cast(column)), + chain_opt(static_cast(column)), + operators_(colSettings == nullptr ? column.operators_ : nullptr), + cellPool_(colSettings == nullptr ? column.cellPool_ : &(colSettings->cellConstructor)) +{ + if constexpr (!Master_matrix::Option_list::is_z2){ + if (colSettings != nullptr) operators_ = &(colSettings->operators); + } + + for (const Cell& cell : column.column_) { + if constexpr (Master_matrix::Option_list::is_z2) { + _insert_cell(cell.get_row_index(), column_.end()); + } else { + _insert_cell(cell.get_element(), cell.get_row_index(), column_.end()); + } + } +} + +template +inline Intrusive_set_column::Intrusive_set_column( + Intrusive_set_column&& column) noexcept + : ra_opt(std::move(static_cast(column))), + dim_opt(std::move(static_cast(column))), + chain_opt(std::move(static_cast(column))), + column_(std::move(column.column_)), + operators_(std::exchange(column.operators_, nullptr)), + cellPool_(std::exchange(column.cellPool_, nullptr)) +{} + +template +inline Intrusive_set_column::~Intrusive_set_column() +{ + column_.clear_and_dispose(delete_disposer(this)); +} + +template +inline std::vector::Field_element_type> +Intrusive_set_column::get_content(int columnLength) const +{ + if (columnLength < 0 && column_.size() > 0) + columnLength = column_.rbegin()->get_row_index() + 1; + else if (columnLength < 0) + return std::vector(); + + std::vector container(columnLength); + for (auto it = column_.begin(); it != column_.end() && it->get_row_index() < static_cast(columnLength); + ++it) { + if constexpr (Master_matrix::Option_list::is_z2) { + container[it->get_row_index()] = 1; + } else { + container[it->get_row_index()] = it->get_element(); + } + } + return container; +} + +template +inline bool Intrusive_set_column::is_non_zero(id_index rowIndex) const +{ + return column_.find(Cell(rowIndex)) != column_.end(); +} + +template +inline bool Intrusive_set_column::is_empty() const +{ + return column_.empty(); +} + +template +inline std::size_t Intrusive_set_column::size() const +{ + return column_.size(); +} + +template +template +inline void Intrusive_set_column::reorder(const Map_type& valueMap, + [[maybe_unused]] index columnIndex) +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Method not available for chain columns."); + + Column_type newSet; + + if constexpr (Master_matrix::Option_list::has_row_access) { + for (auto it = column_.begin(); it != column_.end();) { + Cell* newCell = cellPool_->construct(columnIndex == static_cast(-1) ? + ra_opt::columnIndex_ : columnIndex, + valueMap.at(it->get_row_index())); + if constexpr (!Master_matrix::Option_list::is_z2) { + newCell->set_element(it->get_element()); + } + newSet.insert(newSet.end(), *newCell); + _delete_cell(it); // increases it + if constexpr (Master_matrix::Option_list::has_intrusive_rows) // intrusive list + ra_opt::insert_cell(newCell->get_row_index(), newCell); + } + + // when row is a set, all cells have to be deleted first, to avoid colliding when inserting + if constexpr (!Master_matrix::Option_list::has_intrusive_rows) { // set + for (Cell& cell : newSet) { + ra_opt::insert_cell(cell.get_row_index(), &cell); + } + } + } else { + for (auto it = column_.begin(); it != column_.end();) { + Cell* newCell = cellPool_->construct(valueMap.at(it->get_row_index())); + if constexpr (!Master_matrix::Option_list::is_z2) { + newCell->set_element(it->get_element()); + } + newSet.insert(newSet.end(), *newCell); + _delete_cell(it); // increases it + } + } + + column_.swap(newSet); +} + +template +inline void Intrusive_set_column::clear() +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Method not available for chain columns as a base element should not be empty."); + + column_.clear_and_dispose(delete_disposer(this)); +} + +template +inline void Intrusive_set_column::clear(id_index rowIndex) +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Method not available for chain columns."); + + auto it = column_.find(Cell(rowIndex)); + if (it != column_.end()) { + _delete_cell(it); + } +} + +template +inline typename Intrusive_set_column::id_index +Intrusive_set_column::get_pivot() const +{ + static_assert(Master_matrix::isNonBasic, + "Method not available for base columns."); // could technically be, but is the notion usefull then? + + if constexpr (Master_matrix::Option_list::is_of_boundary_type) { + if (column_.empty()) return -1; + return column_.rbegin()->get_row_index(); + } else { + return chain_opt::get_pivot(); + } +} + +template +inline typename Intrusive_set_column::Field_element_type +Intrusive_set_column::get_pivot_value() const +{ + static_assert(Master_matrix::isNonBasic, + "Method not available for base columns."); // could technically be, but is the notion usefull then? + + if constexpr (Master_matrix::Option_list::is_z2) { + return 1; + } else { + if constexpr (Master_matrix::Option_list::is_of_boundary_type) { + if (column_.empty()) return 0; + return column_.rbegin()->get_element(); + } else { + if (chain_opt::get_pivot() == static_cast(-1)) return 0; + auto it = column_.find(Cell(chain_opt::get_pivot())); + GUDHI_CHECK(it != column_.end(), + "Intrusive_set_column::get_pivot_value - Pivot not found only if the column was misused."); + return it->get_element(); + } + } +} + +template +inline typename Intrusive_set_column::iterator +Intrusive_set_column::begin() noexcept +{ + return column_.begin(); +} + +template +inline typename Intrusive_set_column::const_iterator +Intrusive_set_column::begin() const noexcept +{ + return column_.begin(); +} + +template +inline typename Intrusive_set_column::iterator +Intrusive_set_column::end() noexcept +{ + return column_.end(); +} + +template +inline typename Intrusive_set_column::const_iterator +Intrusive_set_column::end() const noexcept +{ + return column_.end(); +} + +template +inline typename Intrusive_set_column::reverse_iterator +Intrusive_set_column::rbegin() noexcept +{ + return column_.rbegin(); +} + +template +inline typename Intrusive_set_column::const_reverse_iterator +Intrusive_set_column::rbegin() const noexcept +{ + return column_.rbegin(); +} + +template +inline typename Intrusive_set_column::reverse_iterator +Intrusive_set_column::rend() noexcept +{ + return column_.rend(); +} + +template +inline typename Intrusive_set_column::const_reverse_iterator +Intrusive_set_column::rend() const noexcept +{ + return column_.rend(); +} + +template +template +inline Intrusive_set_column& +Intrusive_set_column::operator+=(const Cell_range& column) +{ + static_assert((!Master_matrix::isNonBasic || std::is_same_v), + "For boundary columns, the range has to be a column of same type to help ensure the validity of the " + "base element."); // could be removed, if we give the responsability to the user. + static_assert((!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type), + "For chain columns, the given column cannot be constant."); + + _add(column); + + return *this; +} + +template +inline Intrusive_set_column& +Intrusive_set_column::operator+=(Intrusive_set_column& column) +{ + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + // assumes that the addition never zeros out this column. + if (_add(column)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } else { + _add(column); + } + + return *this; +} + +template +inline Intrusive_set_column& +Intrusive_set_column::operator*=(unsigned int v) +{ + if constexpr (Master_matrix::Option_list::is_z2) { + if (v % 2 == 0) { + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + throw std::invalid_argument("A chain column should not be multiplied by 0."); + } else { + clear(); + } + } + } else { + Field_element_type val = operators_->get_value(v); + + if (val == Field_operators::get_additive_identity()) { + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + throw std::invalid_argument("A chain column should not be multiplied by 0."); + } else { + clear(); + } + return *this; + } + + if (val == Field_operators::get_multiplicative_identity()) return *this; + + for (Cell& cell : column_) { + operators_->multiply_inplace(cell.get_element(), val); + if constexpr (Master_matrix::Option_list::has_row_access) ra_opt::update_cell(cell); + } + } + + return *this; +} + +template +template +inline Intrusive_set_column& +Intrusive_set_column::multiply_target_and_add(const Field_element_type& val, + const Cell_range& column) +{ + static_assert((!Master_matrix::isNonBasic || std::is_same_v), + "For boundary columns, the range has to be a column of same type to help ensure the validity of the " + "base element."); // could be removed, if we give the responsability to the user. + static_assert((!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type), + "For chain columns, the given column cannot be constant."); + + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + _add(column); + } else { + clear(); + _add(column); + } + } else { + _multiply_target_and_add(val, column); + } + + return *this; +} + +template +inline Intrusive_set_column& +Intrusive_set_column::multiply_target_and_add(const Field_element_type& val, + Intrusive_set_column& column) +{ + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + // assumes that the addition never zeros out this column. + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + if (_add(column)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } else { + throw std::invalid_argument("A chain column should not be multiplied by 0."); + } + } else { + if (_multiply_target_and_add(val, column)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } + } else { + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + _add(column); + } else { + clear(); + _add(column); + } + } else { + _multiply_target_and_add(val, column); + } + } + + return *this; +} + +template +template +inline Intrusive_set_column& +Intrusive_set_column::multiply_source_and_add(const Cell_range& column, + const Field_element_type& val) +{ + static_assert((!Master_matrix::isNonBasic || std::is_same_v), + "For boundary columns, the range has to be a column of same type to help ensure the validity of the " + "base element."); // could be removed, if we give the responsability to the user. + static_assert((!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type), + "For chain columns, the given column cannot be constant."); + + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + _add(column); + } + } else { + _multiply_source_and_add(column, val); + } + + return *this; +} + +template +inline Intrusive_set_column& +Intrusive_set_column::multiply_source_and_add(Intrusive_set_column& column, + const Field_element_type& val) +{ + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + // assumes that the addition never zeros out this column. + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + if (_add(column)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } + } else { + if (_multiply_source_and_add(column, val)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } + } else { + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + _add(column); + } + } else { + _multiply_source_and_add(column, val); + } + } + + return *this; +} + +template +inline Intrusive_set_column& +Intrusive_set_column::operator=(const Intrusive_set_column& other) +{ + static_assert(!Master_matrix::Option_list::has_row_access, "= assignement not enabled with row access option."); + + dim_opt::operator=(other); + chain_opt::operator=(other); + + // order is important + column_.clear_and_dispose(delete_disposer(this)); + operators_ = other.operators_; + cellPool_ = other.cellPool_; + column_.clone_from(other.column_, new_cloner(cellPool_), delete_disposer(this)); + + return *this; +} + +template +inline void Intrusive_set_column::_delete_cell(iterator& it) +{ + it = column_.erase_and_dispose(it, delete_disposer(this)); +} + +template +inline typename Intrusive_set_column::Cell* Intrusive_set_column::_insert_cell( + const Field_element_type& value, id_index rowIndex, const iterator& position) +{ + if constexpr (Master_matrix::Option_list::has_row_access) { + Cell* newCell = cellPool_->construct(ra_opt::columnIndex_, rowIndex); + newCell->set_element(value); + column_.insert(position, *newCell); + ra_opt::insert_cell(rowIndex, newCell); + return newCell; + } else { + Cell* newCell = cellPool_->construct(rowIndex); + newCell->set_element(value); + column_.insert(position, *newCell); + return newCell; + } +} + +template +inline void Intrusive_set_column::_insert_cell(id_index rowIndex, + const iterator& position) +{ + if constexpr (Master_matrix::Option_list::has_row_access) { + Cell* newCell = cellPool_->construct(ra_opt::columnIndex_, rowIndex); + column_.insert(position, *newCell); + ra_opt::insert_cell(rowIndex, newCell); + } else { + Cell* newCell = cellPool_->construct(rowIndex); + column_.insert(position, *newCell); + } +} + +template +template +inline bool Intrusive_set_column::_add(const Cell_range& column) +{ + return _add_to_column(column, *this); +} + +template +template +inline bool Intrusive_set_column::_multiply_target_and_add(const Field_element_type& val, + const Cell_range& column) +{ + return _multiply_target_and_add_to_column(val, column, *this); +} + +template +template +inline bool Intrusive_set_column::_multiply_source_and_add(const Cell_range& column, + const Field_element_type& val) +{ + return _multiply_source_and_add_to_column(val, column, *this); +} + +} // namespace persistence_matrix +} // namespace Gudhi + + +/** + * @ingroup persistence_matrix + * + * @brief Hash method for @ref Gudhi::persistence_matrix::Intrusive_set_column. + * + * @tparam Master_matrix Template parameter of @ref Gudhi::persistence_matrix::Intrusive_set_column. + * @tparam Cell_constructor Template parameter of @ref Gudhi::persistence_matrix::Intrusive_set_column. + */ +template +struct std::hash > +{ + std::size_t operator()(const Gudhi::persistence_matrix::Intrusive_set_column& column) const { + return Gudhi::persistence_matrix::hash_column(column); + } +}; + +#endif // PM_INTRUSIVE_SET_COLUMN_H diff --git a/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/list_column.h b/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/list_column.h new file mode 100644 index 0000000000..7dcbb3b2ee --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/list_column.h @@ -0,0 +1,991 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file list_column.h + * @author Hannah Schreiber + * @brief Contains the @ref List_column class. + * Also defines the std::hash method for @ref List_column. + */ + +#ifndef PM_LIST_COLUMN_H +#define PM_LIST_COLUMN_H + +#include +#include +#include +#include +#include //std::swap, std::move & std::exchange + +#include + +#include +#include + +namespace Gudhi { +namespace persistence_matrix { + +/** + * @class List_column list_column.h gudhi/Persistence_matrix/columns/list_column.h + * @ingroup persistence_matrix + * + * @brief Column class following the @ref PersistenceMatrixColumn concept. + * + * Column based on a list structure. The cells are always ordered by row index and only non-zero values + * are stored uniquely in the underlying container. + * + * @tparam Master_matrix An instanciation of @ref Matrix from which all types and options are deduced. + * @tparam Cell_constructor Factory of @ref Cell classes. + */ +template +class List_column : public Master_matrix::Row_access_option, + public Master_matrix::Column_dimension_option, + public Master_matrix::Chain_column_option +{ + public: + using Master = Master_matrix; + using index = typename Master_matrix::index; + using id_index = typename Master_matrix::id_index; + using dimension_type = typename Master_matrix::dimension_type; + using Field_element_type = typename Master_matrix::element_type; + using Cell = typename Master_matrix::Cell_type; + using Column_settings = typename Master_matrix::Column_settings; + + private: + using Field_operators = typename Master_matrix::Field_operators; + using Column_type = std::list; + using Cell_constructor = typename Master_matrix::Cell_constructor; + + public: + using iterator = boost::indirect_iterator; + using const_iterator = boost::indirect_iterator; + using reverse_iterator = boost::indirect_iterator; + using const_reverse_iterator = boost::indirect_iterator; + + List_column(Column_settings* colSettings = nullptr); + template + List_column(const Container_type& nonZeroRowIndices, Column_settings* colSettings); + template + List_column(index columnIndex, + const Container_type& nonZeroRowIndices, + Row_container_type* rowContainer, + Column_settings* colSettings); + template + List_column(const Container_type& nonZeroChainRowIndices, + dimension_type dimension, + Column_settings* colSettings); + template + List_column(index columnIndex, + const Container_type& nonZeroChainRowIndices, + dimension_type dimension, + Row_container_type* rowContainer, + Column_settings* colSettings); + List_column(const List_column& column, + Column_settings* colSettings = nullptr); + template + List_column(const List_column& column, + index columnIndex, + Row_container_type* rowContainer, + Column_settings* colSettings = nullptr); + List_column(List_column&& column) noexcept; + ~List_column(); + + std::vector get_content(int columnLength = -1) const; + bool is_non_zero(id_index rowIndex) const; + bool is_empty() const; + std::size_t size() const; + + template + void reorder(const Map_type& valueMap, [[maybe_unused]] index columnIndex = -1); + void clear(); + void clear(id_index rowIndex); + + id_index get_pivot() const; + Field_element_type get_pivot_value() const; + + iterator begin() noexcept; + const_iterator begin() const noexcept; + iterator end() noexcept; + const_iterator end() const noexcept; + reverse_iterator rbegin() noexcept; + const_reverse_iterator rbegin() const noexcept; + reverse_iterator rend() noexcept; + const_reverse_iterator rend() const noexcept; + + template + List_column& operator+=(const Cell_range& column); + List_column& operator+=(List_column& column); + + List_column& operator*=(unsigned int v); + + // this = v * this + column + template + List_column& multiply_target_and_add(const Field_element_type& val, const Cell_range& column); + List_column& multiply_target_and_add(const Field_element_type& val, List_column& column); + // this = this + column * v + template + List_column& multiply_source_and_add(const Cell_range& column, const Field_element_type& val); + List_column& multiply_source_and_add(List_column& column, const Field_element_type& val); + + friend bool operator==(const List_column& c1, const List_column& c2) { + if (&c1 == &c2) return true; + + auto it1 = c1.column_.begin(); + auto it2 = c2.column_.begin(); + if (c1.column_.size() != c2.column_.size()) return false; + while (it1 != c1.column_.end() && it2 != c2.column_.end()) { + if constexpr (Master_matrix::Option_list::is_z2) { + if ((*it1)->get_row_index() != (*it2)->get_row_index()) return false; + } else { + if ((*it1)->get_row_index() != (*it2)->get_row_index() || (*it1)->get_element() != (*it2)->get_element()) + return false; + } + ++it1; + ++it2; + } + return true; + } + friend bool operator<(const List_column& c1, const List_column& c2) { + if (&c1 == &c2) return false; + + auto it1 = c1.column_.begin(); + auto it2 = c2.column_.begin(); + while (it1 != c1.column_.end() && it2 != c2.column_.end()) { + if ((*it1)->get_row_index() != (*it2)->get_row_index()) return (*it1)->get_row_index() < (*it2)->get_row_index(); + if constexpr (!Master_matrix::Option_list::is_z2) { + if ((*it1)->get_element() != (*it2)->get_element()) return (*it1)->get_element() < (*it2)->get_element(); + } + ++it1; + ++it2; + } + return it2 != c2.column_.end(); + } + + // Disabled with row access. + List_column& operator=(const List_column& other); + + friend void swap(List_column& col1, List_column& col2) { + swap(static_cast(col1), + static_cast(col2)); + swap(static_cast(col1), + static_cast(col2)); + swap(static_cast(col1), + static_cast(col2)); + col1.column_.swap(col2.column_); + std::swap(col1.operators_, col2.operators_); + std::swap(col1.cellPool_, col2.cellPool_); + } + + private: + using ra_opt = typename Master_matrix::Row_access_option; + using dim_opt = typename Master_matrix::Column_dimension_option; + using chain_opt = typename Master_matrix::Chain_column_option; + + Column_type column_; + Field_operators* operators_; + Cell_constructor* cellPool_; + + template + friend void _generic_merge_cell_to_column(Column_type& targetColumn, + Cell_iterator& itSource, + typename Column_type::Column_type::iterator& itTarget, + F1&& process_target, + F2&& process_source, + F3&& update_target1, + F4&& update_target2, + bool& pivotIsZeroed); + template + friend bool _generic_add_to_column(const Cell_range& source, + Column_type& targetColumn, + F1&& process_target, + F2&& process_source, + F3&& update_target1, + F4&& update_target2, + F5&& finish_target); + template + friend bool _add_to_column(const Cell_range& source, Column_type& targetColumn); + template + friend bool _multiply_target_and_add_to_column(const typename Column_type::Field_element_type& val, + const Cell_range& source, + Column_type& targetColumn); + template + friend bool _multiply_source_and_add_to_column(const typename Column_type::Field_element_type& val, + const Cell_range& source, + Column_type& targetColumn); + + void _delete_cell(typename Column_type::iterator& it); + Cell* _insert_cell(const Field_element_type& value, + id_index rowIndex, + const typename Column_type::iterator& position); + void _insert_cell(id_index rowIndex, const typename Column_type::iterator& position); + void _update_cell(const Field_element_type& value, id_index rowIndex, const typename Column_type::iterator& position); + void _update_cell(id_index rowIndex, const typename Column_type::iterator& position); + template + bool _add(const Cell_range& column); + template + bool _multiply_target_and_add(const Field_element_type& val, const Cell_range& column); + template + bool _multiply_source_and_add(const Cell_range& column, const Field_element_type& val); +}; + +template +inline List_column::List_column(Column_settings* colSettings) + : ra_opt(), + dim_opt(), + chain_opt(), + operators_(nullptr), + cellPool_(colSettings == nullptr ? nullptr : &(colSettings->cellConstructor)) +{ + if (operators_ == nullptr && cellPool_ == nullptr) return; // to allow default constructor which gives a dummy column + if constexpr (!Master_matrix::Option_list::is_z2) { + operators_ = &(colSettings->operators); + } +} + +template +template +inline List_column::List_column(const Container_type& nonZeroRowIndices, + Column_settings* colSettings) + : ra_opt(), + dim_opt(nonZeroRowIndices.size() == 0 ? 0 : nonZeroRowIndices.size() - 1), + chain_opt(), + column_(nonZeroRowIndices.size()), + operators_(nullptr), + cellPool_(&(colSettings->cellConstructor)) +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Constructor not available for chain columns, please specify the dimension of the chain."); + + auto it = column_.begin(); + if constexpr (Master_matrix::Option_list::is_z2) { + for (id_index id : nonZeroRowIndices) { + _update_cell(id, it++); + } + } else { + operators_ = &(colSettings->operators); + for (const auto& p : nonZeroRowIndices) { + _update_cell(operators_->get_value(p.second), p.first, it++); + } + } +} + +template +template +inline List_column::List_column(index columnIndex, + const Container_type& nonZeroRowIndices, + Row_container_type* rowContainer, + Column_settings* colSettings) + : ra_opt(columnIndex, rowContainer), + dim_opt(nonZeroRowIndices.size() == 0 ? 0 : nonZeroRowIndices.size() - 1), + chain_opt([&] { + if constexpr (Master_matrix::Option_list::is_z2) { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + } else { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + } + }()), + column_(nonZeroRowIndices.size()), + operators_(nullptr), + cellPool_(&(colSettings->cellConstructor)) +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Constructor not available for chain columns, please specify the dimension of the chain."); + + auto it = column_.begin(); + if constexpr (Master_matrix::Option_list::is_z2) { + for (id_index id : nonZeroRowIndices) { + _update_cell(id, it++); + } + } else { + operators_ = &(colSettings->operators); + for (const auto& p : nonZeroRowIndices) { + _update_cell(operators_->get_value(p.second), p.first, it++); + } + } +} + +template +template +inline List_column::List_column(const Container_type& nonZeroRowIndices, + dimension_type dimension, + Column_settings* colSettings) + : ra_opt(), + dim_opt(dimension), + chain_opt([&] { + if constexpr (Master_matrix::Option_list::is_z2) { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + } else { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + } + }()), + column_(nonZeroRowIndices.size()), + operators_(nullptr), + cellPool_(&(colSettings->cellConstructor)) +{ + auto it = column_.begin(); + if constexpr (Master_matrix::Option_list::is_z2) { + for (id_index id : nonZeroRowIndices) { + _update_cell(id, it++); + } + } else { + operators_ = &(colSettings->operators); + for (const auto& p : nonZeroRowIndices) { + _update_cell(operators_->get_value(p.second), p.first, it++); + } + } +} + +template +template +inline List_column::List_column( + index columnIndex, + const Container_type& nonZeroRowIndices, + dimension_type dimension, + Row_container_type* rowContainer, + Column_settings* colSettings) + : ra_opt(columnIndex, rowContainer), + dim_opt(dimension), + chain_opt([&] { + if constexpr (Master_matrix::Option_list::is_z2) { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + } else { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + } + }()), + column_(nonZeroRowIndices.size()), + operators_(nullptr), + cellPool_(&(colSettings->cellConstructor)) +{ + auto it = column_.begin(); + if constexpr (Master_matrix::Option_list::is_z2) { + for (id_index id : nonZeroRowIndices) { + _update_cell(id, it++); + } + } else { + operators_ = &(colSettings->operators); + for (const auto& p : nonZeroRowIndices) { + _update_cell(operators_->get_value(p.second), p.first, it++); + } + } +} + +template +inline List_column::List_column(const List_column& column, + Column_settings* colSettings) + : ra_opt(), + dim_opt(static_cast(column)), + chain_opt(static_cast(column)), + column_(column.column_.size()), + operators_(colSettings == nullptr ? column.operators_ : nullptr), + cellPool_(colSettings == nullptr ? column.cellPool_ : &(colSettings->cellConstructor)) +{ + static_assert(!Master_matrix::Option_list::has_row_access, + "Simple copy constructor not available when row access option enabled. Please specify the new column " + "index and the row container."); + + if constexpr (!Master_matrix::Option_list::is_z2){ + if (colSettings != nullptr) operators_ = &(colSettings->operators); + } + + auto it = column_.begin(); + for (const Cell* cell : column.column_) { + if constexpr (Master_matrix::Option_list::is_z2) { + _update_cell(cell->get_row_index(), it++); + } else { + _update_cell(cell->get_element(), cell->get_row_index(), it++); + } + } +} + +template +template +inline List_column::List_column(const List_column& column, + index columnIndex, + Row_container_type* rowContainer, + Column_settings* colSettings) + : ra_opt(columnIndex, rowContainer), + dim_opt(static_cast(column)), + chain_opt(static_cast(column)), + column_(column.column_.size()), + operators_(colSettings == nullptr ? column.operators_ : nullptr), + cellPool_(colSettings == nullptr ? column.cellPool_ : &(colSettings->cellConstructor)) +{ + if constexpr (!Master_matrix::Option_list::is_z2){ + if (colSettings != nullptr) operators_ = &(colSettings->operators); + } + + auto it = column_.begin(); + for (const Cell* cell : column.column_) { + if constexpr (Master_matrix::Option_list::is_z2) { + _update_cell(cell->get_row_index(), it++); + } else { + _update_cell(cell->get_element(), cell->get_row_index(), it++); + } + } +} + +template +inline List_column::List_column(List_column&& column) noexcept + : ra_opt(std::move(static_cast(column))), + dim_opt(std::move(static_cast(column))), + chain_opt(std::move(static_cast(column))), + column_(std::move(column.column_)), + operators_(std::exchange(column.operators_, nullptr)), + cellPool_(std::exchange(column.cellPool_, nullptr)) +{} + +template +inline List_column::~List_column() +{ + for (auto* cell : column_) { + if constexpr (Master_matrix::Option_list::has_row_access) ra_opt::unlink(cell); + cellPool_->destroy(cell); + } +} + +template +inline std::vector::Field_element_type> +List_column::get_content(int columnLength) const +{ + if (columnLength < 0 && column_.size() > 0) + columnLength = column_.back()->get_row_index() + 1; + else if (columnLength < 0) + return std::vector(); + + std::vector container(columnLength, 0); + for (auto it = column_.begin(); it != column_.end() && (*it)->get_row_index() < static_cast(columnLength); + ++it) { + if constexpr (Master_matrix::Option_list::is_z2) { + container[(*it)->get_row_index()] = 1; + } else { + container[(*it)->get_row_index()] = (*it)->get_element(); + } + } + return container; +} + +template +inline bool List_column::is_non_zero(id_index rowIndex) const +{ + // could be changed to dichotomic search as column is ordered by row index, + // but I am not sure if it is really worth it as there is no random access + // and the columns should not be that long anyway. + for (const Cell* cell : column_) + if (cell->get_row_index() == rowIndex) return true; + + return false; +} + +template +inline bool List_column::is_empty() const +{ + return column_.empty(); +} + +template +inline std::size_t List_column::size() const +{ + return column_.size(); +} + +template +template +inline void List_column::reorder(const Map_type& valueMap, [[maybe_unused]] index columnIndex) +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Method not available for chain columns."); + + for (auto it = column_.begin(); it != column_.end(); ++it) { + Cell* cell = *it; + if constexpr (Master_matrix::Option_list::has_row_access) { + ra_opt::unlink(cell); + if (columnIndex != static_cast(-1)) cell->set_column_index(columnIndex); + } + cell->set_row_index(valueMap.at(cell->get_row_index())); + if constexpr (Master_matrix::Option_list::has_intrusive_rows && Master_matrix::Option_list::has_row_access) + ra_opt::insert_cell(cell->get_row_index(), cell); + } + + // all cells have to be deleted first, to avoid problem with insertion when row is a set + if constexpr (!Master_matrix::Option_list::has_intrusive_rows && Master_matrix::Option_list::has_row_access) { + for (auto it = column_.begin(); it != column_.end(); ++it) { + Cell* cell = *it; + ra_opt::insert_cell(cell->get_row_index(), cell); + } + } + + column_.sort([](const Cell* c1, const Cell* c2) { return *c1 < *c2; }); +} + +template +inline void List_column::clear() +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Method not available for chain columns as a base element should not be empty."); + + for (auto* cell : column_) { + if constexpr (Master_matrix::Option_list::has_row_access) ra_opt::unlink(cell); + cellPool_->destroy(cell); + } + + column_.clear(); +} + +template +inline void List_column::clear(id_index rowIndex) +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Method not available for chain columns."); + + auto it = column_.begin(); + while (it != column_.end() && (*it)->get_row_index() != rowIndex) it++; + if (it != column_.end()) _delete_cell(it); +} + +template +inline typename List_column::id_index +List_column::get_pivot() const +{ + static_assert(Master_matrix::isNonBasic, + "Method not available for base columns."); // could technically be, but is the notion usefull then? + + if constexpr (Master_matrix::Option_list::is_of_boundary_type) { + if (column_.empty()) return -1; + return column_.back()->get_row_index(); + } else { + return chain_opt::get_pivot(); + } +} + +template +inline typename List_column::Field_element_type +List_column::get_pivot_value() const +{ + static_assert(Master_matrix::isNonBasic, + "Method not available for base columns."); // could technically be, but is the notion usefull then? + + if constexpr (Master_matrix::Option_list::is_z2) { + return 1; + } else { + if constexpr (Master_matrix::Option_list::is_of_boundary_type) { + if (column_.empty()) return 0; + return column_.back()->get_element(); + } else { + if (chain_opt::get_pivot() == static_cast(-1)) return Field_element_type(); + for (const Cell* cell : column_) { + if (cell->get_row_index() == chain_opt::get_pivot()) return cell->get_element(); + } + return Field_element_type(); // should never happen if chain column is used properly + } + } +} + +template +inline typename List_column::iterator +List_column::begin() noexcept +{ + return column_.begin(); +} + +template +inline typename List_column::const_iterator +List_column::begin() const noexcept +{ + return column_.begin(); +} + +template +inline typename List_column::iterator +List_column::end() noexcept +{ + return column_.end(); +} + +template +inline typename List_column::const_iterator +List_column::end() const noexcept +{ + return column_.end(); +} + +template +inline typename List_column::reverse_iterator +List_column::rbegin() noexcept +{ + return column_.rbegin(); +} + +template +inline typename List_column::const_reverse_iterator +List_column::rbegin() const noexcept +{ + return column_.rbegin(); +} + +template +inline typename List_column::reverse_iterator +List_column::rend() noexcept +{ + return column_.rend(); +} + +template +inline typename List_column::const_reverse_iterator +List_column::rend() const noexcept +{ + return column_.rend(); +} + +template +template +inline List_column& List_column::operator+=( + const Cell_range& column) +{ + static_assert((!Master_matrix::isNonBasic || std::is_same_v), + "For boundary columns, the range has to be a column of same type to help ensure the validity of the " + "base element."); // could be removed, if we give the responsability to the user. + static_assert((!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type), + "For chain columns, the given column cannot be constant."); + + _add(column); + + return *this; +} + +template +inline List_column& List_column::operator+=( + List_column& column) +{ + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + // assumes that the addition never zeros out this column. + if (_add(column)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } else { + _add(column); + } + + return *this; +} + +template +inline List_column& List_column::operator*=( + unsigned int v) +{ + if constexpr (Master_matrix::Option_list::is_z2) { + if (v % 2 == 0) { + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + throw std::invalid_argument("A chain column should not be multiplied by 0."); + } else { + clear(); + } + } + } else { + Field_element_type val = operators_->get_value(v); + + if (val == 0u) { + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + throw std::invalid_argument("A chain column should not be multiplied by 0."); + } else { + clear(); + } + return *this; + } + + if (val == 1u) return *this; + + for (Cell* cell : column_) { + operators_->multiply_inplace(cell->get_element(), val); + if constexpr (Master_matrix::Option_list::has_row_access) ra_opt::update_cell(*cell); + } + } + + return *this; +} + +template +template +inline List_column& List_column::multiply_target_and_add( + const Field_element_type& val, const Cell_range& column) +{ + static_assert((!Master_matrix::isNonBasic || std::is_same_v), + "For boundary columns, the range has to be a column of same type to help ensure the validity of the " + "base element."); // could be removed, if we give the responsability to the user. + static_assert((!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type), + "For chain columns, the given column cannot be constant."); + + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + _add(column); + } else { + clear(); + _add(column); + } + } else { + _multiply_target_and_add(val, column); + } + + return *this; +} + +template +inline List_column& List_column::multiply_target_and_add( + const Field_element_type& val, List_column& column) +{ + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + // assumes that the addition never zeros out this column. + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + if (_add(column)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } else { + throw std::invalid_argument("A chain column should not be multiplied by 0."); + } + } else { + if (_multiply_target_and_add(val, column)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } + } else { + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + _add(column); + } else { + clear(); + _add(column); + } + } else { + _multiply_target_and_add(val, column); + } + } + + return *this; +} + +template +template +inline List_column& List_column::multiply_source_and_add( + const Cell_range& column, const Field_element_type& val) +{ + static_assert((!Master_matrix::isNonBasic || std::is_same_v), + "For boundary columns, the range has to be a column of same type to help ensure the validity of the " + "base element."); // could be removed, if we give the responsability to the user. + static_assert((!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type), + "For chain columns, the given column cannot be constant."); + + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + _add(column); + } + } else { + _multiply_source_and_add(column, val); + } + + return *this; +} + +template +inline List_column& List_column::multiply_source_and_add( + List_column& column, const Field_element_type& val) +{ + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + // assumes that the addition never zeros out this column. + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + if (_add(column)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } + } else { + if (_multiply_source_and_add(column, val)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } + } else { + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + _add(column); + } + } else { + _multiply_source_and_add(column, val); + } + } + + return *this; +} + +template +inline List_column& List_column::operator=(const List_column& other) +{ + static_assert(!Master_matrix::Option_list::has_row_access, "= assignement not enabled with row access option."); + + dim_opt::operator=(other); + chain_opt::operator=(other); + + auto tmpPool = cellPool_; + cellPool_ = other.cellPool_; + + while (column_.size() > other.column_.size()) { + if (column_.back() != nullptr) { + if constexpr (Master_matrix::Option_list::has_row_access) ra_opt::unlink(column_.back()); + tmpPool->destroy(column_.back()); + } + column_.pop_back(); + } + + column_.resize(other.column_.size(), nullptr); + auto it = column_.begin(); + for (const Cell* cell : other.column_) { + if (*it != nullptr) { + if constexpr (Master_matrix::Option_list::has_row_access) ra_opt::unlink(*it); + tmpPool->destroy(*it); + } + if constexpr (Master_matrix::Option_list::is_z2) { + _update_cell(cell->get_row_index(), it++); + } else { + _update_cell(cell->get_element(), cell->get_row_index(), it++); + } + } + + operators_ = other.operators_; + + return *this; +} + +template +inline void List_column::_delete_cell(typename Column_type::iterator& it) +{ + if constexpr (Master_matrix::Option_list::has_row_access) ra_opt::unlink(*it); + cellPool_->destroy(*it); + it = column_.erase(it); +} + +template +inline typename List_column::Cell* List_column::_insert_cell( + const Field_element_type& value, id_index rowIndex, const typename Column_type::iterator& position) +{ + if constexpr (Master_matrix::Option_list::has_row_access) { + Cell* newCell = cellPool_->construct(ra_opt::columnIndex_, rowIndex); + newCell->set_element(value); + column_.insert(position, newCell); + ra_opt::insert_cell(rowIndex, newCell); + return newCell; + } else { + Cell* newCell = cellPool_->construct(rowIndex); + newCell->set_element(value); + column_.insert(position, newCell); + return newCell; + } +} + +template +inline void List_column::_insert_cell(id_index rowIndex, + const typename Column_type::iterator& position) +{ + if constexpr (Master_matrix::Option_list::has_row_access) { + Cell* newCell = cellPool_->construct(ra_opt::columnIndex_, rowIndex); + column_.insert(position, newCell); + ra_opt::insert_cell(rowIndex, newCell); + } else { + Cell* newCell = cellPool_->construct(rowIndex); + column_.insert(position, newCell); + } +} + +template +inline void List_column::_update_cell(const Field_element_type& value, + id_index rowIndex, + const typename Column_type::iterator& position) +{ + if constexpr (Master_matrix::Option_list::has_row_access) { + *position = cellPool_->construct(ra_opt::columnIndex_, rowIndex); + (*position)->set_element(value); + ra_opt::insert_cell(rowIndex, *position); + } else { + *position = cellPool_->construct(rowIndex); + (*position)->set_element(value); + } +} + +template +inline void List_column::_update_cell(id_index rowIndex, + const typename Column_type::iterator& position) +{ + if constexpr (Master_matrix::Option_list::has_row_access) { + *position = cellPool_->construct(ra_opt::columnIndex_, rowIndex); + ra_opt::insert_cell(rowIndex, *position); + } else { + *position = cellPool_->construct(rowIndex); + } +} + +template +template +inline bool List_column::_add(const Cell_range& column) +{ + if (column.begin() == column.end()) return false; + if (column_.empty()) { // chain should never enter here. + column_.resize(column.size()); + auto it = column_.begin(); + for (const Cell& cell : column) { + if constexpr (Master_matrix::Option_list::is_z2) { + _update_cell(cell.get_row_index(), it++); + } else { + _update_cell(cell.get_element(), cell.get_row_index(), it++); + } + } + return true; + } + + return _add_to_column(column, *this); +} + +template +template +inline bool List_column::_multiply_target_and_add(const Field_element_type& val, + const Cell_range& column) +{ + return _multiply_target_and_add_to_column(val, column, *this); +} + +template +template +inline bool List_column::_multiply_source_and_add(const Cell_range& column, + const Field_element_type& val) +{ + return _multiply_source_and_add_to_column(val, column, *this); +} + +} // namespace persistence_matrix +} // namespace Gudhi + +/** + * @ingroup persistence_matrix + * + * @brief Hash method for @ref Gudhi::persistence_matrix::List_column. + * + * @tparam Master_matrix Template parameter of @ref Gudhi::persistence_matrix::List_column. + * @tparam Cell_constructor Template parameter of @ref Gudhi::persistence_matrix::List_column. + */ +template +struct std::hash > +{ + std::size_t operator()(const Gudhi::persistence_matrix::List_column& column) const { + return Gudhi::persistence_matrix::hash_column(column); + } +}; + +#endif // PM_LIST_COLUMN_H diff --git a/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/naive_vector_column.h b/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/naive_vector_column.h new file mode 100644 index 0000000000..439b1b4efc --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/naive_vector_column.h @@ -0,0 +1,1095 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file naive_vector_column.h + * @author Hannah Schreiber + * @brief Contains the @ref Naive_vector_column class. + * Also defines the std::hash method for @ref Naive_vector_column. + */ + +#ifndef PM_NAIVE_VECTOR_COLUMN_H +#define PM_NAIVE_VECTOR_COLUMN_H + +#include +#include +#include +#include //binary_search +#include //std::swap, std::move & std::exchange + +#include + +#include +#include + +namespace Gudhi { +namespace persistence_matrix { + +/** + * @class Naive_vector_column naive_vector_column.h gudhi/Persistence_matrix/columns/naive_vector_column.h + * @ingroup persistence_matrix + * + * @brief Column class following the @ref PersistenceMatrixColumn concept. + * + * Column based on a vector structure. The cells are always ordered by row index and only non-zero values + * are stored uniquely in the underlying container. + * + * @tparam Master_matrix An instanciation of @ref Matrix from which all types and options are deduced. + * @tparam Cell_constructor Factory of @ref Cell classes. + */ +template +class Naive_vector_column : public Master_matrix::Row_access_option, + public Master_matrix::Column_dimension_option, + public Master_matrix::Chain_column_option +{ + public: + using Master = Master_matrix; + using index = typename Master_matrix::index; + using id_index = typename Master_matrix::id_index; + using dimension_type = typename Master_matrix::dimension_type; + using Field_element_type = typename Master_matrix::element_type; + using Cell = typename Master_matrix::Cell_type; + using Column_settings = typename Master_matrix::Column_settings; + + private: + using Field_operators = typename Master_matrix::Field_operators; + using Column_type = std::vector; + using Cell_constructor = typename Master_matrix::Cell_constructor; + + public: + using iterator = boost::indirect_iterator; + using const_iterator = boost::indirect_iterator; + using reverse_iterator = boost::indirect_iterator; + using const_reverse_iterator = boost::indirect_iterator; + + Naive_vector_column(Column_settings* colSettings = nullptr); + template + Naive_vector_column(const Container_type& nonZeroRowIndices, + Column_settings* colSettings); + template + Naive_vector_column(index columnIndex, + const Container_type& nonZeroRowIndices, + Row_container_type* rowContainer, + Column_settings* colSettings); + template + Naive_vector_column(const Container_type& nonZeroChainRowIndices, + dimension_type dimension, + Column_settings* colSettings); + template + Naive_vector_column(index columnIndex, + const Container_type& nonZeroChainRowIndices, + dimension_type dimension, + Row_container_type* rowContainer, + Column_settings* colSettings); + Naive_vector_column(const Naive_vector_column& column, + Column_settings* colSettings = nullptr); + template + Naive_vector_column(const Naive_vector_column& column, + index columnIndex, + Row_container_type* rowContainer, + Column_settings* colSettings = nullptr); + Naive_vector_column(Naive_vector_column&& column) noexcept; + ~Naive_vector_column(); + + std::vector get_content(int columnLength = -1) const; + bool is_non_zero(id_index rowIndex) const; + bool is_empty() const; + std::size_t size() const; + + template + void reorder(const Map_type& valueMap, [[maybe_unused]] index columnIndex = -1); + void clear(); + void clear(id_index rowIndex); + + id_index get_pivot() const; + Field_element_type get_pivot_value() const; + + iterator begin() noexcept; + const_iterator begin() const noexcept; + iterator end() noexcept; + const_iterator end() const noexcept; + reverse_iterator rbegin() noexcept; + const_reverse_iterator rbegin() const noexcept; + reverse_iterator rend() noexcept; + const_reverse_iterator rend() const noexcept; + + template + Naive_vector_column& operator+=(const Cell_range& column); + Naive_vector_column& operator+=(Naive_vector_column& column); + + Naive_vector_column& operator*=(unsigned int v); + + // this = v * this + column + template + Naive_vector_column& multiply_target_and_add(const Field_element_type& val, const Cell_range& column); + Naive_vector_column& multiply_target_and_add(const Field_element_type& val, Naive_vector_column& column); + // this = this + column * v + template + Naive_vector_column& multiply_source_and_add(const Cell_range& column, const Field_element_type& val); + Naive_vector_column& multiply_source_and_add(Naive_vector_column& column, const Field_element_type& val); + + friend bool operator==(const Naive_vector_column& c1, const Naive_vector_column& c2) { + if (&c1 == &c2) return true; + if (c1.column_.size() != c2.column_.size()) return false; + + auto it1 = c1.column_.begin(); + auto it2 = c2.column_.begin(); + while (it1 != c1.column_.end() && it2 != c2.column_.end()) { + if constexpr (Master_matrix::Option_list::is_z2) { + if ((*it1)->get_row_index() != (*it2)->get_row_index()) return false; + } else { + if ((*it1)->get_row_index() != (*it2)->get_row_index() || (*it1)->get_element() != (*it2)->get_element()) + return false; + } + ++it1; + ++it2; + } + return true; + } + friend bool operator<(const Naive_vector_column& c1, const Naive_vector_column& c2) { + if (&c1 == &c2) return false; + + auto it1 = c1.column_.begin(); + auto it2 = c2.column_.begin(); + while (it1 != c1.column_.end() && it2 != c2.column_.end()) { + if ((*it1)->get_row_index() != (*it2)->get_row_index()) return (*it1)->get_row_index() < (*it2)->get_row_index(); + if constexpr (!Master_matrix::Option_list::is_z2) { + if ((*it1)->get_element() != (*it2)->get_element()) return (*it1)->get_element() < (*it2)->get_element(); + } + ++it1; + ++it2; + } + return it2 != c2.column_.end(); + } + + // Disabled with row access. + Naive_vector_column& operator=(const Naive_vector_column& other); + + friend void swap(Naive_vector_column& col1, Naive_vector_column& col2) { + swap(static_cast(col1), + static_cast(col2)); + swap(static_cast(col1), + static_cast(col2)); + swap(static_cast(col1), + static_cast(col2)); + col1.column_.swap(col2.column_); + std::swap(col1.operators_, col2.operators_); + std::swap(col1.cellPool_, col2.cellPool_); + } + + private: + using ra_opt = typename Master_matrix::Row_access_option; + using dim_opt = typename Master_matrix::Column_dimension_option; + using chain_opt = typename Master_matrix::Chain_column_option; + + Column_type column_; + Field_operators* operators_; + Cell_constructor* cellPool_; + + template + friend void _generic_merge_cell_to_column(Column_type& targetColumn, + Cell_iterator& itSource, + typename Column_type::Column_type::iterator& itTarget, + F1&& process_target, + F2&& process_source, + F3&& update_target1, + F4&& update_target2, + bool& pivotIsZeroed); + template + friend bool _generic_add_to_column(const Cell_range& source, + Column_type& targetColumn, + F1&& process_target, + F2&& process_source, + F3&& update_target1, + F4&& update_target2, + F5&& finish_target); + + void _delete_cell(Cell* cell); + void _delete_cell(typename Column_type::iterator& it); + Cell* _insert_cell(const Field_element_type& value, id_index rowIndex, Column_type& column); + void _insert_cell(id_index rowIndex, Column_type& column); + void _update_cell(const Field_element_type& value, id_index rowIndex, index position); + void _update_cell(id_index rowIndex, index position); + template + bool _add(const Cell_range& column); + template + bool _multiply_target_and_add(const Field_element_type& val, const Cell_range& column); + template + bool _multiply_source_and_add(const Cell_range& column, const Field_element_type& val); +}; + +template +inline Naive_vector_column::Naive_vector_column(Column_settings* colSettings) + : ra_opt(), + dim_opt(), + chain_opt(), + operators_(nullptr), + cellPool_(colSettings == nullptr ? nullptr : &(colSettings->cellConstructor)) +{ + if (operators_ == nullptr && cellPool_ == nullptr) return; // to allow default constructor which gives a dummy column + if constexpr (!Master_matrix::Option_list::is_z2) { + operators_ = &(colSettings->operators); + } +} + +template +template +inline Naive_vector_column::Naive_vector_column( + const Container_type& nonZeroRowIndices, Column_settings* colSettings) + : ra_opt(), + dim_opt(nonZeroRowIndices.size() == 0 ? 0 : nonZeroRowIndices.size() - 1), + chain_opt(), + column_(nonZeroRowIndices.size(), nullptr), + operators_(nullptr), + cellPool_(&(colSettings->cellConstructor)) +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Constructor not available for chain columns, please specify the dimension of the chain."); + + index i = 0; + if constexpr (Master_matrix::Option_list::is_z2) { + for (id_index id : nonZeroRowIndices) { + _update_cell(id, i++); + } + } else { + operators_ = &(colSettings->operators); + for (const auto& p : nonZeroRowIndices) { + _update_cell(operators_->get_value(p.second), p.first, i++); + } + } +} + +template +template +inline Naive_vector_column::Naive_vector_column( + index columnIndex, + const Container_type& nonZeroRowIndices, + Row_container_type* rowContainer, + Column_settings* colSettings) + : ra_opt(columnIndex, rowContainer), + dim_opt(nonZeroRowIndices.size() == 0 ? 0 : nonZeroRowIndices.size() - 1), + chain_opt([&] { + if constexpr (Master_matrix::Option_list::is_z2) { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + } else { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + } + }()), + column_(nonZeroRowIndices.size(), nullptr), + operators_(nullptr), + cellPool_(&(colSettings->cellConstructor)) +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Constructor not available for chain columns, please specify the dimension of the chain."); + + index i = 0; + if constexpr (Master_matrix::Option_list::is_z2) { + for (id_index id : nonZeroRowIndices) { + _update_cell(id, i++); + } + } else { + operators_ = &(colSettings->operators); + for (const auto& p : nonZeroRowIndices) { + _update_cell(operators_->get_value(p.second), p.first, i++); + } + } +} + +template +template +inline Naive_vector_column::Naive_vector_column( + const Container_type& nonZeroRowIndices, + dimension_type dimension, + Column_settings* colSettings) + : ra_opt(), + dim_opt(dimension), + chain_opt([&] { + if constexpr (Master_matrix::Option_list::is_z2) { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + } else { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + } + }()), + column_(nonZeroRowIndices.size(), nullptr), + operators_(nullptr), + cellPool_(&(colSettings->cellConstructor)) +{ + index i = 0; + if constexpr (Master_matrix::Option_list::is_z2) { + for (id_index id : nonZeroRowIndices) { + _update_cell(id, i++); + } + } else { + operators_ = &(colSettings->operators); + for (const auto& p : nonZeroRowIndices) { + _update_cell(operators_->get_value(p.second), p.first, i++); + } + } +} + +template +template +inline Naive_vector_column::Naive_vector_column( + index columnIndex, + const Container_type& nonZeroRowIndices, + dimension_type dimension, + Row_container_type* rowContainer, + Column_settings* colSettings) + : ra_opt(columnIndex, rowContainer), + dim_opt(dimension), + chain_opt([&] { + if constexpr (Master_matrix::Option_list::is_z2) { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + } else { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + } + }()), + column_(nonZeroRowIndices.size(), nullptr), + operators_(nullptr), + cellPool_(&(colSettings->cellConstructor)) +{ + index i = 0; + if constexpr (Master_matrix::Option_list::is_z2) { + for (id_index id : nonZeroRowIndices) { + _update_cell(id, i++); + } + } else { + operators_ = &(colSettings->operators); + for (const auto& p : nonZeroRowIndices) { + _update_cell(operators_->get_value(p.second), p.first, i++); + } + } +} + +template +inline Naive_vector_column::Naive_vector_column(const Naive_vector_column& column, + Column_settings* colSettings) + : ra_opt(), + dim_opt(static_cast(column)), + chain_opt(static_cast(column)), + column_(column.column_.size(), nullptr), + operators_(colSettings == nullptr ? column.operators_ : nullptr), + cellPool_(colSettings == nullptr ? column.cellPool_ : &(colSettings->cellConstructor)) +{ + static_assert(!Master_matrix::Option_list::has_row_access, + "Simple copy constructor not available when row access option enabled. Please specify the new column " + "index and the row container."); + + if constexpr (!Master_matrix::Option_list::is_z2){ + if (colSettings != nullptr) operators_ = &(colSettings->operators); + } + + index i = 0; + for (const Cell* cell : column.column_) { + if constexpr (Master_matrix::Option_list::is_z2) { + _update_cell(cell->get_row_index(), i++); + } else { + _update_cell(cell->get_element(), cell->get_row_index(), i++); + } + } +} + +template +template +inline Naive_vector_column::Naive_vector_column(const Naive_vector_column& column, + index columnIndex, + Row_container_type* rowContainer, + Column_settings* colSettings) + : ra_opt(columnIndex, rowContainer), + dim_opt(static_cast(column)), + chain_opt(static_cast(column)), + column_(column.column_.size(), nullptr), + operators_(colSettings == nullptr ? column.operators_ : nullptr), + cellPool_(colSettings == nullptr ? column.cellPool_ : &(colSettings->cellConstructor)) +{ + if constexpr (!Master_matrix::Option_list::is_z2){ + if (colSettings != nullptr) operators_ = &(colSettings->operators); + } + + index i = 0; + for (const Cell* cell : column.column_) { + if constexpr (Master_matrix::Option_list::is_z2) { + _update_cell(cell->get_row_index(), i++); + } else { + _update_cell(cell->get_element(), cell->get_row_index(), i++); + } + } +} + +template +inline Naive_vector_column::Naive_vector_column(Naive_vector_column&& column) noexcept + : ra_opt(std::move(static_cast(column))), + dim_opt(std::move(static_cast(column))), + chain_opt(std::move(static_cast(column))), + column_(std::move(column.column_)), + operators_(std::exchange(column.operators_, nullptr)), + cellPool_(std::exchange(column.cellPool_, nullptr)) +{} + +template +inline Naive_vector_column::~Naive_vector_column() +{ + for (auto* cell : column_) { + _delete_cell(cell); + } +} + +template +inline std::vector::Field_element_type> +Naive_vector_column::get_content(int columnLength) const +{ + if (columnLength < 0 && column_.size() > 0) + columnLength = column_.back()->get_row_index() + 1; + else if (columnLength < 0) + return std::vector(); + + std::vector container(columnLength, 0); + for (auto it = column_.begin(); it != column_.end() && (*it)->get_row_index() < static_cast(columnLength); + ++it) { + if constexpr (Master_matrix::Option_list::is_z2) { + container[(*it)->get_row_index()] = 1; + } else { + container[(*it)->get_row_index()] = (*it)->get_element(); + } + } + return container; +} + +template +inline bool Naive_vector_column::is_non_zero(id_index rowIndex) const +{ + Cell cell(rowIndex); + return std::binary_search(column_.begin(), column_.end(), &cell, + [](const Cell* a, const Cell* b) { return a->get_row_index() < b->get_row_index(); }); +} + +template +inline bool Naive_vector_column::is_empty() const +{ + return column_.empty(); +} + +template +inline std::size_t Naive_vector_column::size() const +{ + return column_.size(); +} + +template +template +inline void Naive_vector_column::reorder(const Map_type& valueMap, + [[maybe_unused]] index columnIndex) +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Method not available for chain columns."); + + for (Cell* cell : column_) { + if constexpr (Master_matrix::Option_list::has_row_access) { + ra_opt::unlink(cell); + if (columnIndex != static_cast(-1)) cell->set_column_index(columnIndex); + } + cell->set_row_index(valueMap.at(cell->get_row_index())); + if constexpr (Master_matrix::Option_list::has_intrusive_rows && Master_matrix::Option_list::has_row_access) + ra_opt::insert_cell(cell->get_row_index(), cell); + } + + // all cells have to be deleted first, to avoid problem with insertion when row is a set + if constexpr (!Master_matrix::Option_list::has_intrusive_rows && Master_matrix::Option_list::has_row_access) { + for (Cell* cell : column_) { + ra_opt::insert_cell(cell->get_row_index(), cell); + } + } + + std::sort(column_.begin(), column_.end(), [](const Cell* c1, const Cell* c2) { return *c1 < *c2; }); +} + +template +inline void Naive_vector_column::clear() +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Method not available for chain columns as a base element should not be empty."); + + for (auto* cell : column_) { + if constexpr (Master_matrix::Option_list::has_row_access) ra_opt::unlink(cell); + cellPool_->destroy(cell); + } + + column_.clear(); +} + +template +inline void Naive_vector_column::clear(id_index rowIndex) +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Method not available for chain columns."); + + auto it = column_.begin(); + while (it != column_.end() && (*it)->get_row_index() != rowIndex) ++it; + if (it != column_.end()) { + _delete_cell(*it); + column_.erase(it); + } +} + +template +inline typename Naive_vector_column::id_index +Naive_vector_column::get_pivot() const +{ + static_assert(Master_matrix::isNonBasic, + "Method not available for base columns."); // could technically be, but is the notion usefull then? + + if constexpr (Master_matrix::Option_list::is_of_boundary_type) { + return column_.empty() ? -1 : column_.back()->get_row_index(); + } else { + return chain_opt::get_pivot(); + } +} + +template +inline typename Naive_vector_column::Field_element_type +Naive_vector_column::get_pivot_value() const +{ + static_assert(Master_matrix::isNonBasic, + "Method not available for base columns."); // could technically be, but is the notion usefull then? + + if constexpr (Master_matrix::Option_list::is_z2) { + return 1; + } else { + if constexpr (Master_matrix::Option_list::is_of_boundary_type) { + return column_.empty() ? Field_element_type() : column_.back()->get_element(); + } else { + if (chain_opt::get_pivot() == static_cast(-1)) return Field_element_type(); + for (const Cell* cell : column_) { + if (cell->get_row_index() == chain_opt::get_pivot()) return cell->get_element(); + } + return Field_element_type(); // should never happen if chain column is used properly + } + } +} + +template +inline typename Naive_vector_column::iterator +Naive_vector_column::begin() noexcept +{ + return column_.begin(); +} + +template +inline typename Naive_vector_column::const_iterator +Naive_vector_column::begin() const noexcept +{ + return column_.begin(); +} + +template +inline typename Naive_vector_column::iterator +Naive_vector_column::end() noexcept +{ + return column_.end(); +} + +template +inline typename Naive_vector_column::const_iterator +Naive_vector_column::end() const noexcept +{ + return column_.end(); +} + +template +inline typename Naive_vector_column::reverse_iterator +Naive_vector_column::rbegin() noexcept +{ + return column_.rbegin(); +} + +template +inline typename Naive_vector_column::const_reverse_iterator +Naive_vector_column::rbegin() const noexcept +{ + return column_.rbegin(); +} + +template +inline typename Naive_vector_column::reverse_iterator +Naive_vector_column::rend() noexcept +{ + return column_.rend(); +} + +template +inline typename Naive_vector_column::const_reverse_iterator +Naive_vector_column::rend() const noexcept +{ + return column_.rend(); +} + +template +template +inline Naive_vector_column& +Naive_vector_column::operator+=(const Cell_range& column) +{ + static_assert((!Master_matrix::isNonBasic || std::is_same_v), + "For boundary columns, the range has to be a column of same type to help ensure the validity of the " + "base element."); // could be removed, if we give the responsability to the user. + static_assert((!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type), + "For chain columns, the given column cannot be constant."); + + _add(column); + + return *this; +} + +template +inline Naive_vector_column& +Naive_vector_column::operator+=(Naive_vector_column& column) +{ + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + // assumes that the addition never zeros out this column. + if (_add(column)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } else { + _add(column); + } + + return *this; +} + +template +inline Naive_vector_column& +Naive_vector_column::operator*=(unsigned int v) +{ + if constexpr (Master_matrix::Option_list::is_z2) { + if (v % 2 == 0) { + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + throw std::invalid_argument("A chain column should not be multiplied by 0."); + } else { + clear(); + } + } + } else { + Field_element_type val = operators_->get_value(v); + + if (val == Field_operators::get_additive_identity()) { + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + throw std::invalid_argument("A chain column should not be multiplied by 0."); + } else { + clear(); + } + return *this; + } + + if (val == Field_operators::get_multiplicative_identity()) return *this; + + for (Cell* cell : column_) { + operators_->multiply_inplace(cell->get_element(), val); + if constexpr (Master_matrix::Option_list::has_row_access) ra_opt::update_cell(*cell); + } + } + + return *this; +} + +template +template +inline Naive_vector_column& +Naive_vector_column::multiply_target_and_add(const Field_element_type& val, + const Cell_range& column) +{ + static_assert((!Master_matrix::isNonBasic || std::is_same_v), + "For boundary columns, the range has to be a column of same type to help ensure the validity of the " + "base element."); // could be removed, if we give the responsability to the user. + static_assert((!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type), + "For chain columns, the given column cannot be constant."); + + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + _add(column); + } else { + clear(); + _add(column); + } + } else { + _multiply_target_and_add(val, column); + } + + return *this; +} + +template +inline Naive_vector_column& +Naive_vector_column::multiply_target_and_add(const Field_element_type& val, + Naive_vector_column& column) +{ + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + // assumes that the addition never zeros out this column. + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + if (_add(column)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } else { + throw std::invalid_argument("A chain column should not be multiplied by 0."); + } + } else { + if (_multiply_target_and_add(val, column)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } + } else { + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + _add(column); + } else { + clear(); + _add(column); + } + } else { + _multiply_target_and_add(val, column); + } + } + + return *this; +} + +template +template +inline Naive_vector_column& +Naive_vector_column::multiply_source_and_add(const Cell_range& column, + const Field_element_type& val) +{ + static_assert((!Master_matrix::isNonBasic || std::is_same_v), + "For boundary columns, the range has to be a column of same type to help ensure the validity of the " + "base element."); // could be removed, if we give the responsability to the user. + static_assert((!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type), + "For chain columns, the given column cannot be constant."); + + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + _add(column); + } + } else { + _multiply_source_and_add(column, val); + } + + return *this; +} + +template +inline Naive_vector_column& +Naive_vector_column::multiply_source_and_add(Naive_vector_column& column, + const Field_element_type& val) +{ + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + // assumes that the addition never zeros out this column. + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + if (_add(column)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } + } else { + if (_multiply_source_and_add(column, val)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } + } else { + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + _add(column); + } + } else { + _multiply_source_and_add(column, val); + } + } + + return *this; +} + +template +inline Naive_vector_column& +Naive_vector_column::operator=(const Naive_vector_column& other) +{ + static_assert(!Master_matrix::Option_list::has_row_access, "= assignement not enabled with row access option."); + + dim_opt::operator=(other); + chain_opt::operator=(other); + + auto tmpPool = cellPool_; + cellPool_ = other.cellPool_; + + while (column_.size() > other.column_.size()) { + if (column_.back() == nullptr) { + if constexpr (Master_matrix::Option_list::has_row_access) ra_opt::unlink(column_.back()); + tmpPool->destroy(column_.back()); + } + column_.pop_back(); + } + + column_.resize(other.column_.size(), nullptr); + index i = 0; + for (const Cell* cell : other.column_) { + if (column_[i] != nullptr) { + if constexpr (Master_matrix::Option_list::has_row_access) ra_opt::unlink(column_[i]); + tmpPool->destroy(column_[i]); + } + if constexpr (Master_matrix::Option_list::is_z2) { + _update_cell(cell->get_row_index(), i++); + } else { + _update_cell(cell->get_element(), cell->get_row_index(), i++); + } + } + + operators_ = other.operators_; + + return *this; +} + +template +inline void Naive_vector_column::_delete_cell(Cell* cell) +{ + if constexpr (Master_matrix::Option_list::has_row_access) ra_opt::unlink(cell); + cellPool_->destroy(cell); +} + +template +inline void Naive_vector_column::_delete_cell(typename Column_type::iterator& it) +{ + _delete_cell(*it); + ++it; +} + +template +inline typename Naive_vector_column::Cell* Naive_vector_column::_insert_cell( + const Field_element_type& value, id_index rowIndex, Column_type& column) +{ + if constexpr (Master_matrix::Option_list::has_row_access) { + Cell* newCell = cellPool_->construct(ra_opt::columnIndex_, rowIndex); + newCell->set_element(value); + column.push_back(newCell); + ra_opt::insert_cell(rowIndex, newCell); + return newCell; + } else { + Cell* newCell = cellPool_->construct(rowIndex); + column.push_back(newCell); + newCell->set_element(value); + return newCell; + } +} + +template +inline void Naive_vector_column::_insert_cell(id_index rowIndex, Column_type& column) +{ + if constexpr (Master_matrix::Option_list::has_row_access) { + Cell* newCell = cellPool_->construct(ra_opt::columnIndex_, rowIndex); + column.push_back(newCell); + ra_opt::insert_cell(rowIndex, newCell); + } else { + column.push_back(cellPool_->construct(rowIndex)); + } +} + +template +inline void Naive_vector_column::_update_cell(const Field_element_type& value, + id_index rowIndex, + index position) +{ + if constexpr (Master_matrix::Option_list::has_row_access) { + Cell* newCell = cellPool_->construct(ra_opt::columnIndex_, rowIndex); + newCell->set_element(value); + column_[position] = newCell; + ra_opt::insert_cell(rowIndex, newCell); + } else { + column_[position] = cellPool_->construct(rowIndex); + column_[position]->set_element(value); + } +} + +template +inline void Naive_vector_column::_update_cell(id_index rowIndex, index position) +{ + if constexpr (Master_matrix::Option_list::has_row_access) { + Cell* newCell = cellPool_->construct(ra_opt::columnIndex_, rowIndex); + column_[position] = newCell; + ra_opt::insert_cell(rowIndex, newCell); + } else { + column_[position] = cellPool_->construct(rowIndex); + } +} + +template +template +inline bool Naive_vector_column::_add(const Cell_range& column) +{ + if (column.begin() == column.end()) return false; + if (column_.empty()) { // chain should never enter here. + column_.resize(column.size()); + index i = 0; + for (const Cell& cell : column) { + if constexpr (Master_matrix::Option_list::is_z2) { + _update_cell(cell.get_row_index(), i++); + } else { + _update_cell(cell.get_element(), cell.get_row_index(), i++); + } + } + return true; + } + + Column_type newColumn; + newColumn.reserve(column_.size() + column.size()); // safe upper bound + + auto pivotIsZeroed = _generic_add_to_column( + column, + *this, + [&](Cell* cellTarget) { newColumn.push_back(cellTarget); }, + [&](typename Cell_range::const_iterator& itSource, + [[maybe_unused]] const typename Column_type::iterator& itTarget) { + if constexpr (Master_matrix::Option_list::is_z2) { + _insert_cell(itSource->get_row_index(), newColumn); + } else { + _insert_cell(itSource->get_element(), itSource->get_row_index(), newColumn); + } + }, + [&](Field_element_type& targetElement, typename Cell_range::const_iterator& itSource) { + if constexpr (!Master_matrix::Option_list::is_z2) + operators_->add_inplace(targetElement, itSource->get_element()); + }, + [&](Cell* cellTarget) { newColumn.push_back(cellTarget); }, + [&](typename Column_type::iterator& itTarget) { + while (itTarget != column_.end()) { + newColumn.push_back(*itTarget); + itTarget++; + } + }); + + column_.swap(newColumn); + + return pivotIsZeroed; +} + +template +template +inline bool Naive_vector_column::_multiply_target_and_add(const Field_element_type& val, + const Cell_range& column) +{ + if (val == 0u) { + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + throw std::invalid_argument("A chain column should not be multiplied by 0."); + // this would not only mess up the base, but also the pivots stored. + } else { + clear(); + } + } + if (column_.empty()) { // chain should never enter here. + column_.resize(column.size()); + index i = 0; + for (const Cell& cell : column) { + if constexpr (Master_matrix::Option_list::is_z2) { + _update_cell(cell.get_row_index(), i++); + } else { + _update_cell(cell.get_element(), cell.get_row_index(), i++); + } + } + return true; + } + + Column_type newColumn; + newColumn.reserve(column_.size() + column.size()); // safe upper bound + + auto pivotIsZeroed = _generic_add_to_column( + column, + *this, + [&](Cell* cellTarget) { + operators_->multiply_inplace(cellTarget->get_element(), val); + if constexpr (Master_matrix::Option_list::has_row_access) ra_opt::update_cell(*cellTarget); + newColumn.push_back(cellTarget); + }, + [&](typename Cell_range::const_iterator& itSource, const typename Column_type::iterator& itTarget) { + _insert_cell(itSource->get_element(), itSource->get_row_index(), newColumn); + }, + [&](Field_element_type& targetElement, typename Cell_range::const_iterator& itSource) { + operators_->multiply_and_add_inplace_front(targetElement, val, itSource->get_element()); + }, + [&](Cell* cellTarget) { newColumn.push_back(cellTarget); }, + [&](typename Column_type::iterator& itTarget) { + while (itTarget != column_.end()) { + operators_->multiply_inplace((*itTarget)->get_element(), val); + if constexpr (Master_matrix::Option_list::has_row_access) ra_opt::update_cell(**itTarget); + newColumn.push_back(*itTarget); + itTarget++; + } + }); + + column_.swap(newColumn); + + return pivotIsZeroed; +} + +template +template +inline bool Naive_vector_column::_multiply_source_and_add(const Cell_range& column, + const Field_element_type& val) +{ + if (val == 0u || column.begin() == column.end()) { + return false; + } + + Column_type newColumn; + newColumn.reserve(column_.size() + column.size()); // safe upper bound + + auto pivotIsZeroed = _generic_add_to_column( + column, *this, + [&](Cell* cellTarget) { newColumn.push_back(cellTarget); }, + [&](typename Cell_range::const_iterator& itSource, const typename Column_type::iterator& itTarget) { + Cell* newCell = _insert_cell(itSource->get_element(), itSource->get_row_index(), newColumn); + operators_->multiply_inplace(newCell->get_element(), val); + if constexpr (Master_matrix::Option_list::has_row_access) ra_opt::update_cell(*newCell); + }, + [&](Field_element_type& targetElement, typename Cell_range::const_iterator& itSource) { + operators_->multiply_and_add_inplace_back(itSource->get_element(), val, targetElement); + }, + [&](Cell* cellTarget) { newColumn.push_back(cellTarget); }, + [&](typename Column_type::iterator& itTarget) { + while (itTarget != column_.end()) { + newColumn.push_back(*itTarget); + itTarget++; + } + }); + + column_.swap(newColumn); + + return pivotIsZeroed; +} + +} // namespace persistence_matrix +} // namespace Gudhi + +/** + * @ingroup persistence_matrix + * + * @brief Hash method for @ref Gudhi::persistence_matrix::Naive_vector_column. + * + * @tparam Master_matrix Template parameter of @ref Gudhi::persistence_matrix::Naive_vector_column. + * @tparam Cell_constructor Template parameter of @ref Gudhi::persistence_matrix::Naive_vector_column. + */ +template +struct std::hash > +{ + std::size_t operator()(const Gudhi::persistence_matrix::Naive_vector_column& column) const { + return Gudhi::persistence_matrix::hash_column(column); + } +}; + +#endif // PM_NAIVE_VECTOR_COLUMN_H diff --git a/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/row_access.h b/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/row_access.h new file mode 100644 index 0000000000..241926f371 --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/row_access.h @@ -0,0 +1,199 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file row_access.h + * @author Hannah Schreiber + * @brief Contains the @ref Row_access class and @ref Dummy_row_access structure. + */ + +#ifndef PM_ROW_ACCESS_H +#define PM_ROW_ACCESS_H + +#include //std::swap + +namespace Gudhi { +namespace persistence_matrix { + +/** + * @ingroup persistence_matrix + * + * @brief Empty structure. + * Inheritated instead of @ref Row_access, if the row access is not enabled. + */ +struct Dummy_row_access +{ + friend void swap([[maybe_unused]] Dummy_row_access& d1, [[maybe_unused]] Dummy_row_access& d2) {} + + Dummy_row_access() {} + template + Dummy_row_access([[maybe_unused]] index columnIndex, [[maybe_unused]] Row_container_type& rows) {} +}; + +/** + * @class Row_access row_access.h gudhi/Persistence_matrix/columns/row_access.h + * @ingroup persistence_matrix + * + * @brief Class managing the row access for the columns. + * + * @tparam Master_matrix An instanciation of @ref Matrix from which all types and options are deduced. + */ +template +class Row_access +{ + public: + using index = typename Master_matrix::index; /**< @ref MatIdx index type. */ + using id_index = typename Master_matrix::id_index; /**< @ref IDIdx index type. */ + using Cell_type = typename Master_matrix::Cell_type; /**< @ref Cell. */ + using Row_container_type = typename Master_matrix::row_container_type; /**< Type of the row container. */ + + /** + * @brief Default constructor. Sets the column index to -1 and the row container to nullptr. + * Should only be used by dummy columns. + */ + Row_access(); + /** + * @brief Constructor setting the column index and the row container by the given values. + * + * @param columnIndex Column index to store. + * @param rows Pointer to the row container. + */ + Row_access(index columnIndex, Row_container_type* rows); + /** + * @brief Move constructor. + * + * @param other Column to move. + */ + Row_access(Row_access&& other) noexcept; + + /** + * @brief Inserts the given cell at the given row index. + * + * @param rowIndex @ref rowindex "Row index" of the cell. + * @param cell Pointer to the cell to insert. + */ + void insert_cell(id_index rowIndex, Cell_type* cell); + /** + * @brief Removes the given cell from its row. + * + * @param cell Pointer to the cell to remove. + */ + void unlink(Cell_type* cell); + /** + * @brief If @ref PersistenceMatrixOptions::has_intrusive_rows is false, updates the copy of the cell in its row. + * Otherwise does nothing. + * + * If the rows are intrusive, only a pointer of the cell is stored and therefore any update on the cell (value + * or column index) is automatically forwarded. But for non intrusive rows, any update has to be pushed explicitely. + * + * @param cell Cell to update. + */ + void update_cell(const Cell_type& cell); + /** + * @brief Returns the @ref MatIdx column index. + * + * @return The @ref MatIdx column index. + */ + index get_column_index() const; + + /** + * @brief Swap operator. + */ + friend void swap(Row_access& r1, Row_access& r2) { + std::swap(r1.rows_, r2.rows_); + std::swap(r1.columnIndex_, r2.columnIndex_); + } + + // void set_rows(Row_container_type *rows); + + protected: + index columnIndex_; /**< Column index. */ + Row_container_type* rows_; /**< Row container. Be carefull to not destroy before the columns. */ + + private: + using base_hook_matrix_row = typename Master_matrix::base_hook_matrix_row; +}; + +template +inline Row_access::Row_access() : columnIndex_(-1), rows_(nullptr) +{} + +template +inline Row_access::Row_access(index columnIndex, Row_container_type* rows) + : columnIndex_(columnIndex), rows_(rows) +{} + +template +inline Row_access::Row_access(Row_access&& other) noexcept + : columnIndex_(std::exchange(other.columnIndex_, 0)), rows_(other.rows_) +{} + +template +inline void Row_access::insert_cell(id_index rowIndex, Cell_type* cell) +{ + if (rows_ == nullptr) return; + + if constexpr (!Master_matrix::Option_list::has_removable_rows) { + if (rows_->size() < rowIndex + 1) rows_->resize(rowIndex + 1); + } + + // if has_removable_rows should op[] create non existing entry? If not, use try_emplace() + if constexpr (Master_matrix::Option_list::has_intrusive_rows) { + rows_->operator[](rowIndex).push_back(*cell); + } else { + rows_->operator[](rowIndex).insert(*cell); + } +} + +template +inline void Row_access::unlink(Cell_type* cell) +{ + if (rows_ == nullptr) return; + + if constexpr (Master_matrix::Option_list::has_intrusive_rows) { + cell->base_hook_matrix_row::unlink(); + } else { + if constexpr (Master_matrix::Option_list::has_removable_rows) { + auto it = rows_->find(cell->get_row_index()); + it->second.erase(*cell); + } else { + rows_->operator[](cell->get_row_index()).erase(*cell); + } + } +} + +template +inline void Row_access::update_cell(const Cell_type& cell) +{ + if constexpr (!Master_matrix::Option_list::has_intrusive_rows) { + if (rows_ == nullptr) return; + auto& row = rows_->at(cell.get_row_index()); + auto it = row.find(cell); + it = row.erase(it); + row.insert(it, cell); + } +} + +template +inline typename Row_access::index Row_access::get_column_index() const +{ + return columnIndex_; +} + +// template +// inline void Row_access::set_rows(Row_container_type *rows) +// { +// rows_ = rows; +// } + +} // namespace persistence_matrix +} // namespace Gudhi + +#endif // PM_ROW_ACCESS_H diff --git a/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/set_column.h b/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/set_column.h new file mode 100644 index 0000000000..b87eb841f4 --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/set_column.h @@ -0,0 +1,932 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file set_column.h + * @author Hannah Schreiber + * @brief Contains the @ref Set_column class. + * Also defines the std::hash method for @ref Set_column. + */ + +#ifndef PM_SET_COLUMN_H +#define PM_SET_COLUMN_H + +#include +#include +#include +#include +#include //std::swap, std::move & std::exchange + +#include + +#include +#include + +namespace Gudhi { +namespace persistence_matrix { + +/** + * @class Set_column set_column.h gudhi/Persistence_matrix/columns/set_column.h + * @ingroup persistence_matrix + * + * @brief Column class following the @ref PersistenceMatrixColumn concept. + * + * Column based on a set structure. The cells are always ordered by row index and only non-zero values + * are stored uniquely in the underlying container. + * + * @tparam Master_matrix An instanciation of @ref Matrix from which all types and options are deduced. + * @tparam Cell_constructor Factory of @ref Cell classes. + */ +template +class Set_column : public Master_matrix::Row_access_option, + public Master_matrix::Column_dimension_option, + public Master_matrix::Chain_column_option +{ + public: + using Master = Master_matrix; + using index = typename Master_matrix::index; + using id_index = typename Master_matrix::id_index; + using dimension_type = typename Master_matrix::dimension_type; + using Field_element_type = typename Master_matrix::element_type; + using Cell = typename Master_matrix::Cell_type; + using Column_settings = typename Master_matrix::Column_settings; + + private: + using Field_operators = typename Master_matrix::Field_operators; + + struct CellPointerComp { + bool operator()(const Cell* c1, const Cell* c2) const { return *c1 < *c2; } + }; + + using Column_type = std::set; + using Cell_constructor = typename Master_matrix::Cell_constructor; + + public: + using iterator = boost::indirect_iterator; + using const_iterator = boost::indirect_iterator; + using reverse_iterator = boost::indirect_iterator; + using const_reverse_iterator = boost::indirect_iterator; + + Set_column(Column_settings* colSettings = nullptr); + template + Set_column(const Container_type& nonZeroRowIndices, Column_settings* colSettings); + template + Set_column(index columnIndex, + const Container_type& nonZeroRowIndices, + Row_container_type* rowContainer, + Column_settings* colSettings); + template + Set_column(const Container_type& nonZeroChainRowIndices, + dimension_type dimension, + Column_settings* colSettings); + template + Set_column(index columnIndex, + const Container_type& nonZeroChainRowIndices, + dimension_type dimension, + Row_container_type* rowContainer, + Column_settings* colSettings); + Set_column(const Set_column& column, + Column_settings* colSettings = nullptr); + template + Set_column(const Set_column& column, + index columnIndex, + Row_container_type* rowContainer, + Column_settings* colSettings = nullptr); + Set_column(Set_column&& column) noexcept; + ~Set_column(); + + std::vector get_content(int columnLength = -1) const; + bool is_non_zero(id_index rowIndex) const; + bool is_empty() const; + std::size_t size() const; + + template + void reorder(const Map_type& valueMap, [[maybe_unused]] index columnIndex = -1); + void clear(); + void clear(id_index rowIndex); + + id_index get_pivot() const; + Field_element_type get_pivot_value() const; + + iterator begin() noexcept; + const_iterator begin() const noexcept; + iterator end() noexcept; + const_iterator end() const noexcept; + reverse_iterator rbegin() noexcept; + const_reverse_iterator rbegin() const noexcept; + reverse_iterator rend() noexcept; + const_reverse_iterator rend() const noexcept; + + template + Set_column& operator+=(const Cell_range& column); + Set_column& operator+=(Set_column& column); + + Set_column& operator*=(unsigned int v); + + // this = v * this + column + template + Set_column& multiply_target_and_add(const Field_element_type& val, const Cell_range& column); + Set_column& multiply_target_and_add(const Field_element_type& val, Set_column& column); + // this = this + column * v + template + Set_column& multiply_source_and_add(const Cell_range& column, const Field_element_type& val); + Set_column& multiply_source_and_add(Set_column& column, const Field_element_type& val); + + friend bool operator==(const Set_column& c1, const Set_column& c2) { + if (&c1 == &c2) return true; + + auto it1 = c1.column_.begin(); + auto it2 = c2.column_.begin(); + if (c1.column_.size() != c2.column_.size()) return false; + while (it1 != c1.column_.end() && it2 != c2.column_.end()) { + if constexpr (Master_matrix::Option_list::is_z2) { + if ((*it1)->get_row_index() != (*it2)->get_row_index()) return false; + } else { + if ((*it1)->get_row_index() != (*it2)->get_row_index() || (*it1)->get_element() != (*it2)->get_element()) + return false; + } + ++it1; + ++it2; + } + return true; + } + friend bool operator<(const Set_column& c1, const Set_column& c2) { + if (&c1 == &c2) return false; + + auto it1 = c1.column_.begin(); + auto it2 = c2.column_.begin(); + while (it1 != c1.column_.end() && it2 != c2.column_.end()) { + if ((*it1)->get_row_index() != (*it2)->get_row_index()) return (*it1)->get_row_index() < (*it2)->get_row_index(); + if constexpr (!Master_matrix::Option_list::is_z2) { + if ((*it1)->get_element() != (*it2)->get_element()) return (*it1)->get_element() < (*it2)->get_element(); + } + ++it1; + ++it2; + } + return it2 != c2.column_.end(); + } + + // Disabled with row access. + Set_column& operator=(const Set_column& other); + + friend void swap(Set_column& col1, Set_column& col2) { + swap(static_cast(col1), + static_cast(col2)); + swap(static_cast(col1), + static_cast(col2)); + swap(static_cast(col1), + static_cast(col2)); + col1.column_.swap(col2.column_); + std::swap(col1.operators_, col2.operators_); + std::swap(col1.cellPool_, col2.cellPool_); + } + + private: + using ra_opt = typename Master_matrix::Row_access_option; + using dim_opt = typename Master_matrix::Column_dimension_option; + using chain_opt = typename Master_matrix::Chain_column_option; + + Column_type column_; + Field_operators* operators_; + Cell_constructor* cellPool_; + + template + friend void _generic_merge_cell_to_column(Column_type& targetColumn, + Cell_iterator& itSource, + typename Column_type::Column_type::iterator& itTarget, + F1&& process_target, + F2&& process_source, + F3&& update_target1, + F4&& update_target2, + bool& pivotIsZeroed); + template + friend bool _generic_add_to_column(const Cell_range& source, + Column_type& targetColumn, + F1&& process_target, + F2&& process_source, + F3&& update_target1, + F4&& update_target2, + F5&& finish_target); + template + friend bool _add_to_column(const Cell_range& source, Column_type& targetColumn); + template + friend bool _multiply_target_and_add_to_column(const typename Column_type::Field_element_type& val, + const Cell_range& source, + Column_type& targetColumn); + template + friend bool _multiply_source_and_add_to_column(const typename Column_type::Field_element_type& val, + const Cell_range& source, + Column_type& targetColumn); + + void _delete_cell(typename Column_type::iterator& it); + Cell* _insert_cell(const Field_element_type& value, + id_index rowIndex, + const typename Column_type::iterator& position); + void _insert_cell(id_index rowIndex, const typename Column_type::iterator& position); + template + bool _add(const Cell_range& column); + template + bool _multiply_target_and_add(const Field_element_type& val, const Cell_range& column); + template + bool _multiply_source_and_add(const Cell_range& column, const Field_element_type& val); +}; + +template +inline Set_column::Set_column(Column_settings* colSettings) + : ra_opt(), + dim_opt(), + chain_opt(), + operators_(nullptr), + cellPool_(colSettings == nullptr ? nullptr : &(colSettings->cellConstructor)) +{ + if (operators_ == nullptr && cellPool_ == nullptr) return; // to allow default constructor which gives a dummy column + if constexpr (!Master_matrix::Option_list::is_z2) { + operators_ = &(colSettings->operators); + } +} + +template +template +inline Set_column::Set_column(const Container_type& nonZeroRowIndices, + Column_settings* colSettings) + : ra_opt(), + dim_opt(nonZeroRowIndices.size() == 0 ? 0 : nonZeroRowIndices.size() - 1), + chain_opt(), + operators_(nullptr), + cellPool_(&(colSettings->cellConstructor)) +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Constructor not available for chain columns, please specify the dimension of the chain."); + + if constexpr (Master_matrix::Option_list::is_z2) { + for (id_index id : nonZeroRowIndices) { + _insert_cell(id, column_.end()); + } + } else { + operators_ = &(colSettings->operators); + for (const auto& p : nonZeroRowIndices) { + _insert_cell(operators_->get_value(p.second), p.first, column_.end()); + } + } +} + +template +template +inline Set_column::Set_column(index columnIndex, + const Container_type& nonZeroRowIndices, + Row_container_type* rowContainer, + Column_settings* colSettings) + : ra_opt(columnIndex, rowContainer), + dim_opt(nonZeroRowIndices.size() == 0 ? 0 : nonZeroRowIndices.size() - 1), + chain_opt([&] { + if constexpr (Master_matrix::Option_list::is_z2) { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + } else { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + } + }()), + operators_(nullptr), + cellPool_(&(colSettings->cellConstructor)) +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Constructor not available for chain columns, please specify the dimension of the chain."); + + if constexpr (Master_matrix::Option_list::is_z2) { + for (id_index id : nonZeroRowIndices) { + _insert_cell(id, column_.end()); + } + } else { + operators_ = &(colSettings->operators); + for (const auto& p : nonZeroRowIndices) { + _insert_cell(operators_->get_value(p.second), p.first, column_.end()); + } + } +} + +template +template +inline Set_column::Set_column(const Container_type& nonZeroRowIndices, + dimension_type dimension, + Column_settings* colSettings) + : ra_opt(), + dim_opt(dimension), + chain_opt([&] { + if constexpr (Master_matrix::Option_list::is_z2) { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + } else { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + } + }()), + operators_(nullptr), + cellPool_(&(colSettings->cellConstructor)) +{ + if constexpr (Master_matrix::Option_list::is_z2) { + for (id_index id : nonZeroRowIndices) { + _insert_cell(id, column_.end()); + } + } else { + operators_ = &(colSettings->operators); + for (const auto& p : nonZeroRowIndices) { + _insert_cell(operators_->get_value(p.second), p.first, column_.end()); + } + } +} + +template +template +inline Set_column::Set_column( + index columnIndex, + const Container_type& nonZeroRowIndices, + dimension_type dimension, + Row_container_type* rowContainer, + Column_settings* colSettings) + : ra_opt(columnIndex, rowContainer), + dim_opt(dimension), + chain_opt([&] { + if constexpr (Master_matrix::Option_list::is_z2) { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + } else { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + } + }()), + operators_(nullptr), + cellPool_(&(colSettings->cellConstructor)) +{ + if constexpr (Master_matrix::Option_list::is_z2) { + for (id_index id : nonZeroRowIndices) { + _insert_cell(id, column_.end()); + } + } else { + operators_ = &(colSettings->operators); + for (const auto& p : nonZeroRowIndices) { + _insert_cell(operators_->get_value(p.second), p.first, column_.end()); + } + } +} + +template +inline Set_column::Set_column(const Set_column& column, + Column_settings* colSettings) + : ra_opt(), + dim_opt(static_cast(column)), + chain_opt(static_cast(column)), + operators_(colSettings == nullptr ? column.operators_ : nullptr), + cellPool_(colSettings == nullptr ? column.cellPool_ : &(colSettings->cellConstructor)) +{ + static_assert(!Master_matrix::Option_list::has_row_access, + "Simple copy constructor not available when row access option enabled. Please specify the new column " + "index and the row container."); + + if constexpr (!Master_matrix::Option_list::is_z2){ + if (colSettings != nullptr) operators_ = &(colSettings->operators); + } + + for (const Cell* cell : column.column_) { + if constexpr (Master_matrix::Option_list::is_z2) { + _insert_cell(cell->get_row_index(), column_.end()); + } else { + _insert_cell(cell->get_element(), cell->get_row_index(), column_.end()); + } + } +} + +template +template +inline Set_column::Set_column(const Set_column& column, index columnIndex, + Row_container_type* rowContainer, + Column_settings* colSettings) + : ra_opt(columnIndex, rowContainer), + dim_opt(static_cast(column)), + chain_opt(static_cast(column)), + operators_(colSettings == nullptr ? column.operators_ : nullptr), + cellPool_(colSettings == nullptr ? column.cellPool_ : &(colSettings->cellConstructor)) +{ + if constexpr (!Master_matrix::Option_list::is_z2){ + if (colSettings != nullptr) operators_ = &(colSettings->operators); + } + + for (const Cell* cell : column.column_) { + if constexpr (Master_matrix::Option_list::is_z2) { + _insert_cell(cell->get_row_index(), column_.end()); + } else { + _insert_cell(cell->get_element(), cell->get_row_index(), column_.end()); + } + } +} + +template +inline Set_column::Set_column(Set_column&& column) noexcept + : ra_opt(std::move(static_cast(column))), + dim_opt(std::move(static_cast(column))), + chain_opt(std::move(static_cast(column))), + column_(std::move(column.column_)), + operators_(std::exchange(column.operators_, nullptr)), + cellPool_(std::exchange(column.cellPool_, nullptr)) +{} + +template +inline Set_column::~Set_column() +{ + for (auto* cell : column_) { + if constexpr (Master_matrix::Option_list::has_row_access) ra_opt::unlink(cell); + cellPool_->destroy(cell); + } +} + +template +inline std::vector::Field_element_type> +Set_column::get_content(int columnLength) const +{ + if (columnLength < 0 && column_.size() > 0) + columnLength = (*column_.rbegin())->get_row_index() + 1; + else if (columnLength < 0) + return std::vector(); + + std::vector container(columnLength, 0); + for (auto it = column_.begin(); it != column_.end() && (*it)->get_row_index() < static_cast(columnLength); + ++it) { + if constexpr (Master_matrix::Option_list::is_z2) { + container[(*it)->get_row_index()] = 1; + } else { + container[(*it)->get_row_index()] = (*it)->get_element(); + } + } + return container; +} + +template +inline bool Set_column::is_non_zero(id_index rowIndex) const +{ + Cell cell(rowIndex); + return column_.find(&cell) != column_.end(); +} + +template +inline bool Set_column::is_empty() const +{ + return column_.empty(); +} + +template +inline std::size_t Set_column::size() const +{ + return column_.size(); +} + +template +template +inline void Set_column::reorder(const Map_type& valueMap, + [[maybe_unused]] index columnIndex) +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Method not available for chain columns."); + + Column_type newSet; + + for (Cell* cell : column_) { + if constexpr (Master_matrix::Option_list::has_row_access) { + ra_opt::unlink(cell); + if (columnIndex != static_cast(-1)) cell->set_column_index(columnIndex); + } + cell->set_row_index(valueMap.at(cell->get_row_index())); + newSet.insert(cell); + if constexpr (Master_matrix::Option_list::has_row_access && + Master_matrix::Option_list::has_intrusive_rows) // intrusive list + ra_opt::insert_cell(cell->get_row_index(), cell); + } + + // when row is a set, all cells have to be deleted first, to avoid colliding when inserting + if constexpr (Master_matrix::Option_list::has_row_access && !Master_matrix::Option_list::has_intrusive_rows) { // set + for (Cell* cell : newSet) { + ra_opt::insert_cell(cell->get_row_index(), cell); + } + } + + column_.swap(newSet); +} + +template +inline void Set_column::clear() +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Method not available for chain columns as a base element should not be empty."); + + for (auto* cell : column_) { + if constexpr (Master_matrix::Option_list::has_row_access) ra_opt::unlink(cell); + cellPool_->destroy(cell); + } + + column_.clear(); +} + +template +inline void Set_column::clear(id_index rowIndex) +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Method not available for chain columns."); + + auto cell = cellPool_->construct(rowIndex); + auto it = column_.find(cell); + if (it != column_.end()) { + _delete_cell(it); + } + cellPool_->destroy(cell); +} + +template +inline typename Set_column::id_index +Set_column::get_pivot() const +{ + static_assert(Master_matrix::isNonBasic, + "Method not available for base columns."); // could technically be, but is the notion usefull then? + + if constexpr (Master_matrix::Option_list::is_of_boundary_type) { + if (column_.empty()) return -1; + return (*column_.rbegin())->get_row_index(); + } else { + return chain_opt::get_pivot(); + } +} + +template +inline typename Set_column::Field_element_type +Set_column::get_pivot_value() const +{ + static_assert(Master_matrix::isNonBasic, + "Method not available for base columns."); // could technically be, but is the notion usefull then? + + if constexpr (Master_matrix::Option_list::is_z2) { + return 1; + } else { + if constexpr (Master_matrix::Option_list::is_of_boundary_type) { + if (column_.empty()) return 0; + return (*column_.rbegin())->get_element(); + } else { + if (chain_opt::get_pivot() == static_cast(-1)) return Field_element_type(); + for (const Cell* cell : column_) { + if (cell->get_row_index() == chain_opt::get_pivot()) return cell->get_element(); + } + return Field_element_type(); // should never happen if chain column is used properly + } + } +} + +template +inline typename Set_column::iterator +Set_column::begin() noexcept +{ + return column_.begin(); +} + +template +inline typename Set_column::const_iterator +Set_column::begin() const noexcept +{ + return column_.begin(); +} + +template +inline typename Set_column::iterator +Set_column::end() noexcept +{ + return column_.end(); +} + +template +inline typename Set_column::const_iterator +Set_column::end() const noexcept +{ + return column_.end(); +} + +template +inline typename Set_column::reverse_iterator +Set_column::rbegin() noexcept +{ + return column_.rbegin(); +} + +template +inline typename Set_column::const_reverse_iterator +Set_column::rbegin() const noexcept +{ + return column_.rbegin(); +} + +template +inline typename Set_column::reverse_iterator +Set_column::rend() noexcept +{ + return column_.rend(); +} + +template +inline typename Set_column::const_reverse_iterator +Set_column::rend() const noexcept +{ + return column_.rend(); +} + +template +template +inline Set_column& Set_column::operator+=( + const Cell_range& column) +{ + static_assert((!Master_matrix::isNonBasic || std::is_same_v), + "For boundary columns, the range has to be a column of same type to help ensure the validity of the " + "base element."); // could be removed, if we give the responsability to the user. + static_assert((!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type), + "For chain columns, the given column cannot be constant."); + + _add(column); + + return *this; +} + +template +inline Set_column& Set_column::operator+=( + Set_column& column) +{ + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + // assumes that the addition never zeros out this column. + if (_add(column)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } else { + _add(column); + } + + return *this; +} + +template +inline Set_column& Set_column::operator*=( + unsigned int v) +{ + if constexpr (Master_matrix::Option_list::is_z2) { + if (v % 2 == 0) { + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + throw std::invalid_argument("A chain column should not be multiplied by 0."); + } else { + clear(); + } + } + } else { + Field_element_type val = operators_->get_value(v); + + if (val == Field_operators::get_additive_identity()) { + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + throw std::invalid_argument("A chain column should not be multiplied by 0."); + } else { + clear(); + } + return *this; + } + + if (val == Field_operators::get_multiplicative_identity()) return *this; + + for (Cell* cell : column_) { + operators_->multiply_inplace(cell->get_element(), val); + if constexpr (Master_matrix::Option_list::has_row_access) ra_opt::update_cell(*cell); + } + } + + return *this; +} + +template +template +inline Set_column& Set_column::multiply_target_and_add( + const Field_element_type& val, const Cell_range& column) +{ + static_assert((!Master_matrix::isNonBasic || std::is_same_v), + "For boundary columns, the range has to be a column of same type to help ensure the validity of the " + "base element."); // could be removed, if we give the responsability to the user. + static_assert((!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type), + "For chain columns, the given column cannot be constant."); + + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + _add(column); + } else { + clear(); + _add(column); + } + } else { + _multiply_target_and_add(val, column); + } + + return *this; +} + +template +inline Set_column& Set_column::multiply_target_and_add( + const Field_element_type& val, Set_column& column) +{ + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + // assumes that the addition never zeros out this column. + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + if (_add(column)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } else { + throw std::invalid_argument("A chain column should not be multiplied by 0."); + } + } else { + if (_multiply_target_and_add(val, column)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } + } else { + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + _add(column); + } else { + clear(); + _add(column); + } + } else { + _multiply_target_and_add(val, column); + } + } + + return *this; +} + +template +template +inline Set_column& Set_column::multiply_source_and_add( + const Cell_range& column, const Field_element_type& val) +{ + static_assert((!Master_matrix::isNonBasic || std::is_same_v), + "For boundary columns, the range has to be a column of same type to help ensure the validity of the " + "base element."); // could be removed, if we give the responsability to the user. + static_assert((!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type), + "For chain columns, the given column cannot be constant."); + + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + _add(column); + } + } else { + _multiply_source_and_add(column, val); + } + + return *this; +} + +template +inline Set_column& Set_column::multiply_source_and_add( + Set_column& column, const Field_element_type& val) +{ + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + // assumes that the addition never zeros out this column. + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + if (_add(column)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } + } else { + if (_multiply_source_and_add(column, val)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } + } else { + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + _add(column); + } + } else { + _multiply_source_and_add(column, val); + } + } + + return *this; +} + +template +inline Set_column& Set_column::operator=( + const Set_column& other) +{ + static_assert(!Master_matrix::Option_list::has_row_access, "= assignement not enabled with row access option."); + + dim_opt::operator=(other); + chain_opt::operator=(other); + + for (auto* cell : column_) { + if constexpr (Master_matrix::Option_list::has_row_access) ra_opt::unlink(cell); + cellPool_->destroy(cell); + } + column_.clear(); + + cellPool_ = other.cellPool_; + operators_ = other.operators_; + + for (const Cell* cell : other.column_) { + if constexpr (Master_matrix::Option_list::is_z2) { + _insert_cell(cell->get_row_index(), column_.end()); + } else { + _insert_cell(cell->get_element(), cell->get_row_index(), column_.end()); + } + } + + return *this; +} + +template +inline void Set_column::_delete_cell(typename Column_type::iterator& it) +{ + if constexpr (Master_matrix::Option_list::has_row_access) ra_opt::unlink(*it); + cellPool_->destroy(*it); + it = column_.erase(it); +} + +template +inline typename Set_column::Cell* Set_column::_insert_cell( + const Field_element_type& value, id_index rowIndex, const typename Column_type::iterator& position) +{ + if constexpr (Master_matrix::Option_list::has_row_access) { + Cell* newCell = cellPool_->construct(ra_opt::columnIndex_, rowIndex); + newCell->set_element(value); + column_.insert(position, newCell); + ra_opt::insert_cell(rowIndex, newCell); + return newCell; + } else { + Cell* newCell = cellPool_->construct(rowIndex); + newCell->set_element(value); + column_.insert(position, newCell); + return newCell; + } +} + +template +inline void Set_column::_insert_cell(id_index rowIndex, + const typename Column_type::iterator& position) +{ + if constexpr (Master_matrix::Option_list::has_row_access) { + Cell* newCell = cellPool_->construct(ra_opt::columnIndex_, rowIndex); + column_.insert(position, newCell); + ra_opt::insert_cell(rowIndex, newCell); + } else { + Cell* newCell = cellPool_->construct(rowIndex); + column_.insert(position, newCell); + } +} + +template +template +inline bool Set_column::_add(const Cell_range& column) +{ + return _add_to_column(column, *this); +} + +template +template +inline bool Set_column::_multiply_target_and_add(const Field_element_type& val, + const Cell_range& column) +{ + return _multiply_target_and_add_to_column(val, column, *this); +} + +template +template +inline bool Set_column::_multiply_source_and_add(const Cell_range& column, + const Field_element_type& val) +{ + return _multiply_source_and_add_to_column(val, column, *this); +} + +} // namespace persistence_matrix +} // namespace Gudhi + +/** + * @ingroup persistence_matrix + * + * @brief Hash method for @ref Gudhi::persistence_matrix::Set_column. + * + * @tparam Master_matrix Template parameter of @ref Gudhi::persistence_matrix::Set_column. + * @tparam Cell_constructor Template parameter of @ref Gudhi::persistence_matrix::Set_column. + */ +template +struct std::hash > +{ + std::size_t operator()(const Gudhi::persistence_matrix::Set_column& column) const { + return Gudhi::persistence_matrix::hash_column(column); + } +}; + +#endif // PM_SET_COLUMN_H diff --git a/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/unordered_set_column.h b/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/unordered_set_column.h new file mode 100644 index 0000000000..87cd1e5ae8 --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/unordered_set_column.h @@ -0,0 +1,1012 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file unordered_set_column.h + * @author Hannah Schreiber + * @brief Contains the @ref Unordered_set_column class. + * Also defines the std::hash method for @ref Unordered_set_column. + */ + +#ifndef PM_UNORDERED_SET_COLUMN_H +#define PM_UNORDERED_SET_COLUMN_H + +#include +#include +#include +#include +#include //std::swap, std::move & std::exchange + +#include +#if BOOST_VERSION >= 108100 +#include +#else +#include +#endif + +#include + +namespace Gudhi { +namespace persistence_matrix { + +//For unordered_set container. Outside of Unordered_set_column because of a msvc bug who can't compile properly +//unordered_flat_set if the hash method is nested. +template +struct CellPointerHash { + size_t operator()(const Cell* c) const { return std::hash()(*c); } +}; +template +struct CellPointerEq { + bool operator()(const Cell* c1, const Cell* c2) const { return *c1 == *c2; } +}; + +/** + * @class Unordered_set_column unordered_set_column.h gudhi/Persistence_matrix/columns/unordered_set_column.h + * @ingroup persistence_matrix + * + * @brief Column class following the @ref PersistenceMatrixColumn concept. + * + * Column based on an unordered set structure. The cells are not ordered, but only non-zero values + * are stored uniquely in the underlying container. When adding a cell range into it, the given cell range + * also does not need to be ordered (contrary to most other column types). + * + * @tparam Master_matrix An instanciation of @ref Matrix from which all types and options are deduced. + * @tparam Cell_constructor Factory of @ref Cell classes. + */ +template +class Unordered_set_column : public Master_matrix::Row_access_option, + public Master_matrix::Column_dimension_option, + public Master_matrix::Chain_column_option +{ + public: + using Master = Master_matrix; + using index = typename Master_matrix::index; + using id_index = typename Master_matrix::id_index; + using dimension_type = typename Master_matrix::dimension_type; + using Field_element_type = typename Master_matrix::element_type; + using Cell = typename Master_matrix::Cell_type; + using Column_settings = typename Master_matrix::Column_settings; + + private: + using Field_operators = typename Master_matrix::Field_operators; + using Cell_constructor = typename Master_matrix::Cell_constructor; + + struct CellPointerComp { + bool operator()(const Cell* c1, const Cell* c2) const { return *c1 < *c2; } + }; + +#if BOOST_VERSION >= 108100 + using Column_type = boost::unordered_flat_set, CellPointerEq>; +#else + using Column_type = std::unordered_set, CellPointerEq>; +#endif + + public: + using iterator = boost::indirect_iterator; + using const_iterator = boost::indirect_iterator; + + Unordered_set_column(Column_settings* colSettings = nullptr); + template + Unordered_set_column(const Container_type& nonZeroRowIndices, + Column_settings* colSettings); + template + Unordered_set_column(index columnIndex, + const Container_type& nonZeroRowIndices, + Row_container_type* rowContainer, + Column_settings* colSettings); + template + Unordered_set_column(const Container_type& nonZeroChainRowIndices, + dimension_type dimension, + Column_settings* colSettings); + template + Unordered_set_column(index columnIndex, + const Container_type& nonZeroChainRowIndices, + dimension_type dimension, + Row_container_type* rowContainer, + Column_settings* colSettings); + Unordered_set_column(const Unordered_set_column& column, + Column_settings* colSettings = nullptr); + template + Unordered_set_column(const Unordered_set_column& column, + index columnIndex, + Row_container_type* rowContainer, + Column_settings* colSettings = nullptr); + Unordered_set_column(Unordered_set_column&& column) noexcept; + ~Unordered_set_column(); + + std::vector get_content(int columnLength = -1) const; + bool is_non_zero(id_index rowIndex) const; + bool is_empty() const; + std::size_t size() const; + + template + void reorder(const Map_type& valueMap, [[maybe_unused]] index columnIndex = -1); + void clear(); + void clear(id_index rowIndex); + + id_index get_pivot() const; + Field_element_type get_pivot_value() const; + + iterator begin() noexcept; + const_iterator begin() const noexcept; + iterator end() noexcept; + const_iterator end() const noexcept; + + template + Unordered_set_column& operator+=(const Cell_range& column); + Unordered_set_column& operator+=(Unordered_set_column& column); + + Unordered_set_column& operator*=(unsigned int v); + + // this = v * this + column + template + Unordered_set_column& multiply_target_and_add(const Field_element_type& val, const Cell_range& column); + Unordered_set_column& multiply_target_and_add(const Field_element_type& val, Unordered_set_column& column); + // this = this + column * v + template + Unordered_set_column& multiply_source_and_add(const Cell_range& column, const Field_element_type& val); + Unordered_set_column& multiply_source_and_add(Unordered_set_column& column, const Field_element_type& val); + + friend bool operator==(const Unordered_set_column& c1, const Unordered_set_column& c2) { + if (&c1 == &c2) return true; + if (c1.column_.size() != c2.column_.size()) return false; + + for (Cell* cell : c1.column_){ + auto it = c2.column_.find(cell); + if (it == c2.column_.end()) return false; + if constexpr (!Master_matrix::Option_list::is_z2) + if ((*it)->get_element() != cell->get_element()) return false; + } + return true; + } + friend bool operator<(const Unordered_set_column& c1, const Unordered_set_column& c2) { + if (&c1 == &c2) return false; + + using id_index = Unordered_set_column::id_index; + using rep_type = typename std::conditional + >::type; + + auto it1 = c1.column_.begin(); + auto it2 = c2.column_.begin(); + std::set cells1, cells2; + while (it1 != c1.column_.end() && it2 != c2.column_.end()) { + if constexpr (Master_matrix::Option_list::is_z2) { + cells1.insert((*it1)->get_row_index()); + cells2.insert((*it2)->get_row_index()); + } else { + cells1.emplace((*it1)->get_row_index(), (*it1)->get_element()); + cells2.emplace((*it2)->get_row_index(), (*it2)->get_element()); + } + ++it1; + ++it2; + } + while (it1 != c1.column_.end()) { + if constexpr (Master_matrix::Option_list::is_z2) { + cells1.insert((*it1)->get_row_index()); + } else { + cells1.emplace((*it1)->get_row_index(), (*it1)->get_element()); + } + ++it1; + } + while (it2 != c2.column_.end()) { + if constexpr (Master_matrix::Option_list::is_z2) { + cells2.insert((*it2)->get_row_index()); + } else { + cells2.emplace((*it2)->get_row_index(), (*it2)->get_element()); + } + ++it2; + } + return cells1 < cells2; + } + + // Disabled with row access. + Unordered_set_column& operator=(const Unordered_set_column& other); + + friend void swap(Unordered_set_column& col1, Unordered_set_column& col2) { + swap(static_cast(col1), + static_cast(col2)); + swap(static_cast(col1), + static_cast(col2)); + swap(static_cast(col1), + static_cast(col2)); + col1.column_.swap(col2.column_); + std::swap(col1.operators_, col2.operators_); + std::swap(col1.cellPool_, col2.cellPool_); + } + + private: + using ra_opt = typename Master_matrix::Row_access_option; + using dim_opt = typename Master_matrix::Column_dimension_option; + using chain_opt = typename Master_matrix::Chain_column_option; + + Column_type column_; + Field_operators* operators_; + Cell_constructor* cellPool_; + + void _delete_cell(typename Column_type::iterator& it); + Cell* _insert_cell(const Field_element_type& value, id_index rowIndex); + void _insert_cell(id_index rowIndex); + template + bool _add(const Cell_range& column); + template + bool _multiply_target_and_add(const Field_element_type& val, const Cell_range& column); + template + bool _multiply_source_and_add(const Cell_range& column, const Field_element_type& val); + template + bool _generic_add(const Cell_range& source, F1&& process_source, F2&& update_target); +}; + +template +inline Unordered_set_column::Unordered_set_column(Column_settings* colSettings) + : ra_opt(), + dim_opt(), + chain_opt(), + operators_(nullptr), + cellPool_(colSettings == nullptr ? nullptr : &(colSettings->cellConstructor)) +{ + if (operators_ == nullptr && cellPool_ == nullptr) return; // to allow default constructor which gives a dummy column + if constexpr (!Master_matrix::Option_list::is_z2) { + operators_ = &(colSettings->operators); + } +} + +template +template +inline Unordered_set_column::Unordered_set_column( + const Container_type& nonZeroRowIndices, Column_settings* colSettings) + : ra_opt(), + dim_opt(nonZeroRowIndices.size() == 0 ? 0 : nonZeroRowIndices.size() - 1), + chain_opt(), + column_(nonZeroRowIndices.size()), + operators_(nullptr), + cellPool_(&(colSettings->cellConstructor)) +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Constructor not available for chain columns, please specify the dimension of the chain."); + + if constexpr (Master_matrix::Option_list::is_z2) { + for (id_index id : nonZeroRowIndices) { + _insert_cell(id); + } + } else { + operators_ = &(colSettings->operators); + for (const auto& p : nonZeroRowIndices) { + _insert_cell(operators_->get_value(p.second), p.first); + } + } +} + +template +template +inline Unordered_set_column::Unordered_set_column( + index columnIndex, + const Container_type& nonZeroRowIndices, + Row_container_type* rowContainer, + Column_settings* colSettings) + : ra_opt(columnIndex, rowContainer), + dim_opt(nonZeroRowIndices.size() == 0 ? 0 : nonZeroRowIndices.size() - 1), + chain_opt([&] { + if constexpr (Master_matrix::Option_list::is_z2) { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + } else { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + } + }()), + column_(nonZeroRowIndices.size()), + operators_(nullptr), + cellPool_(&(colSettings->cellConstructor)) +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Constructor not available for chain columns, please specify the dimension of the chain."); + + if constexpr (Master_matrix::Option_list::is_z2) { + for (id_index id : nonZeroRowIndices) { + _insert_cell(id); + } + } else { + operators_ = &(colSettings->operators); + for (const auto& p : nonZeroRowIndices) { + _insert_cell(operators_->get_value(p.second), p.first); + } + } +} + +template +template +inline Unordered_set_column::Unordered_set_column( + const Container_type& nonZeroRowIndices, + dimension_type dimension, + Column_settings* colSettings) + : ra_opt(), + dim_opt(dimension), + chain_opt([&] { + if constexpr (Master_matrix::Option_list::is_z2) { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + } else { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + } + }()), + column_(nonZeroRowIndices.size()), + operators_(nullptr), + cellPool_(&(colSettings->cellConstructor)) +{ + if constexpr (Master_matrix::Option_list::is_z2) { + for (id_index id : nonZeroRowIndices) { + _insert_cell(id); + } + } else { + operators_ = &(colSettings->operators); + for (const auto& p : nonZeroRowIndices) { + _insert_cell(operators_->get_value(p.second), p.first); + } + } +} + +template +template +inline Unordered_set_column::Unordered_set_column( + index columnIndex, + const Container_type& nonZeroRowIndices, + dimension_type dimension, + Row_container_type* rowContainer, + Column_settings* colSettings) + : ra_opt(columnIndex, rowContainer), + dim_opt(dimension), + chain_opt([&] { + if constexpr (Master_matrix::Option_list::is_z2) { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + } else { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + } + }()), + column_(nonZeroRowIndices.size()), + operators_(nullptr), + cellPool_(&(colSettings->cellConstructor)) +{ + if constexpr (Master_matrix::Option_list::is_z2) { + for (id_index id : nonZeroRowIndices) { + _insert_cell(id); + } + } else { + operators_ = &(colSettings->operators); + for (const auto& p : nonZeroRowIndices) { + _insert_cell(operators_->get_value(p.second), p.first); + } + } +} + +template +inline Unordered_set_column::Unordered_set_column(const Unordered_set_column& column, + Column_settings* colSettings) + : ra_opt(), + dim_opt(static_cast(column)), + chain_opt(static_cast(column)), + column_(column.column_.bucket_count()), + operators_(colSettings == nullptr ? column.operators_ : nullptr), + cellPool_(colSettings == nullptr ? column.cellPool_ : &(colSettings->cellConstructor)) +{ + static_assert(!Master_matrix::Option_list::has_row_access, + "Simple copy constructor not available when row access option enabled. Please specify the new column " + "index and the row container."); + + if constexpr (!Master_matrix::Option_list::is_z2){ + if (colSettings != nullptr) operators_ = &(colSettings->operators); + } + + for (const Cell* cell : column.column_) { + if constexpr (Master_matrix::Option_list::is_z2) { + _insert_cell(cell->get_row_index()); + } else { + _insert_cell(cell->get_element(), cell->get_row_index()); + } + } +} + +template +template +inline Unordered_set_column::Unordered_set_column(const Unordered_set_column& column, + index columnIndex, + Row_container_type* rowContainer, + Column_settings* colSettings) + : ra_opt(columnIndex, rowContainer), + dim_opt(static_cast(column)), + chain_opt(static_cast(column)), + column_(column.column_.bucket_count()), + operators_(colSettings == nullptr ? column.operators_ : nullptr), + cellPool_(colSettings == nullptr ? column.cellPool_ : &(colSettings->cellConstructor)) +{ + if constexpr (!Master_matrix::Option_list::is_z2){ + if (colSettings != nullptr) operators_ = &(colSettings->operators); + } + + for (const Cell* cell : column.column_) { + if constexpr (Master_matrix::Option_list::is_z2) { + _insert_cell(cell->get_row_index()); + } else { + _insert_cell(cell->get_element(), cell->get_row_index()); + } + } +} + +template +inline Unordered_set_column::Unordered_set_column( + Unordered_set_column&& column) noexcept + : ra_opt(std::move(static_cast(column))), + dim_opt(std::move(static_cast(column))), + chain_opt(std::move(static_cast(column))), + column_(std::move(column.column_)), + operators_(std::exchange(column.operators_, nullptr)), + cellPool_(std::exchange(column.cellPool_, nullptr)) +{} + +template +inline Unordered_set_column::~Unordered_set_column() +{ + for (auto* cell : column_) { + if constexpr (Master_matrix::Option_list::has_row_access) ra_opt::unlink(cell); + cellPool_->destroy(cell); + } +} + +template +inline std::vector::Field_element_type> +Unordered_set_column::get_content(int columnLength) const +{ + if (columnLength < 0 && column_.size() > 0) + columnLength = (*std::max_element(column_.begin(), column_.end(), CellPointerComp()))->get_row_index() + 1; + else if (columnLength < 0) + return std::vector(); + + std::vector container(columnLength, 0); + for (auto it = column_.begin(); it != column_.end(); ++it) { + if ((*it)->get_row_index() < static_cast(columnLength)) { + if constexpr (Master_matrix::Option_list::is_z2) { + container[(*it)->get_row_index()] = 1; + } else { + container[(*it)->get_row_index()] = (*it)->get_element(); + } + } + } + return container; +} + +template +inline bool Unordered_set_column::is_non_zero(id_index rowIndex) const +{ + Cell cell(rowIndex); + return column_.find(&cell) != column_.end(); +} + +template +inline bool Unordered_set_column::is_empty() const +{ + return column_.empty(); +} + +template +inline std::size_t Unordered_set_column::size() const +{ + return column_.size(); +} + +template +template +inline void Unordered_set_column::reorder(const Map_type& valueMap, + [[maybe_unused]] index columnIndex) +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Method not available for chain columns."); + + Column_type newSet; + + for (Cell* cell : column_) { + if constexpr (Master_matrix::Option_list::has_row_access) { + ra_opt::unlink(cell); + if (columnIndex != static_cast(-1)) cell->set_column_index(columnIndex); + } + cell->set_row_index(valueMap.at(cell->get_row_index())); + newSet.insert(cell); + if constexpr (Master_matrix::Option_list::has_row_access && + Master_matrix::Option_list::has_intrusive_rows) // intrusive list + ra_opt::insert_cell(cell->get_row_index(), cell); + } + + // when row is a set, all cells have to be deleted first, to avoid colliding when inserting + if constexpr (Master_matrix::Option_list::has_row_access && !Master_matrix::Option_list::has_intrusive_rows) { // set + for (Cell* cell : newSet) { + ra_opt::insert_cell(cell->get_row_index(), cell); + } + } + + column_.swap(newSet); +} + +template +inline void Unordered_set_column::clear() +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Method not available for chain columns as a base element should not be empty."); + + for (auto* cell : column_) { + if constexpr (Master_matrix::Option_list::has_row_access) ra_opt::unlink(cell); + cellPool_->destroy(cell); + } + + column_.clear(); +} + +template +inline void Unordered_set_column::clear(id_index rowIndex) +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Method not available for chain columns."); + + auto cell = cellPool_->construct(rowIndex); + auto it = column_.find(cell); + if (it != column_.end()) { + _delete_cell(it); + } + cellPool_->destroy(cell); +} + +template +inline typename Unordered_set_column::id_index +Unordered_set_column::get_pivot() const +{ + static_assert(Master_matrix::isNonBasic, + "Method not available for base columns."); // could technically be, but is the notion usefull then? + + if constexpr (Master_matrix::Option_list::is_of_boundary_type) { + if (column_.empty()) return -1; + // linear search could be avoided with storing the pivot. But even then, some modifications of the column requires + // the max, so not clear how much it is worth it. + return (*std::max_element(column_.begin(), column_.end(), CellPointerComp()))->get_row_index(); + } else { + return chain_opt::get_pivot(); + } +} + +template +inline typename Unordered_set_column::Field_element_type +Unordered_set_column::get_pivot_value() const +{ + static_assert(Master_matrix::isNonBasic, + "Method not available for base columns."); // could technically be, but is the notion usefull then? + + if constexpr (Master_matrix::Option_list::is_z2) { + return 1; + } else { + if constexpr (Master_matrix::Option_list::is_of_boundary_type) { + if (column_.empty()) return 0; + return (*std::max_element(column_.begin(), column_.end(), CellPointerComp()))->get_element(); + } else { + if (chain_opt::get_pivot() == static_cast(-1)) return Field_element_type(); + for (const Cell* cell : column_) { + if (cell->get_row_index() == chain_opt::get_pivot()) return cell->get_element(); + } + return Field_element_type(); // should never happen if chain column is used properly + } + } +} + +template +inline typename Unordered_set_column::iterator +Unordered_set_column::begin() noexcept +{ + return column_.begin(); +} + +template +inline typename Unordered_set_column::const_iterator +Unordered_set_column::begin() const noexcept +{ + return column_.begin(); +} + +template +inline typename Unordered_set_column::iterator +Unordered_set_column::end() noexcept +{ + return column_.end(); +} + +template +inline typename Unordered_set_column::const_iterator +Unordered_set_column::end() const noexcept +{ + return column_.end(); +} + +template +template +inline Unordered_set_column& +Unordered_set_column::operator+=(const Cell_range& column) +{ + static_assert((!Master_matrix::isNonBasic || std::is_same_v), + "For boundary columns, the range has to be a column of same type to help ensure the validity of the " + "base element."); // could be removed, if we give the responsability to the user. + static_assert((!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type), + "For chain columns, the given column cannot be constant."); + + _add(column); + + return *this; +} + +template +inline Unordered_set_column& +Unordered_set_column::operator+=(Unordered_set_column& column) +{ + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + // assumes that the addition never zeros out this column. + if (_add(column)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } else { + _add(column); + } + + return *this; +} + +template +inline Unordered_set_column& +Unordered_set_column::operator*=(unsigned int v) +{ + if constexpr (Master_matrix::Option_list::is_z2) { + if (v % 2 == 0) { + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + throw std::invalid_argument("A chain column should not be multiplied by 0."); + } else { + clear(); + } + } + } else { + Field_element_type val = operators_->get_value(v); + + if (val == Field_operators::get_additive_identity()) { + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + throw std::invalid_argument("A chain column should not be multiplied by 0."); + } else { + clear(); + } + return *this; + } + + if (val == Field_operators::get_multiplicative_identity()) return *this; + + for (Cell* cell : column_) { + operators_->multiply_inplace(cell->get_element(), val); + if constexpr (Master_matrix::Option_list::has_row_access) ra_opt::update_cell(*cell); + } + } + + return *this; +} + +template +template +inline Unordered_set_column& +Unordered_set_column::multiply_target_and_add(const Field_element_type& val, + const Cell_range& column) +{ + static_assert((!Master_matrix::isNonBasic || std::is_same_v), + "For boundary columns, the range has to be a column of same type to help ensure the validity of the " + "base element."); // could be removed, if we give the responsability to the user. + static_assert((!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type), + "For chain columns, the given column cannot be constant."); + + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + _add(column); + } else { + clear(); + _add(column); + } + } else { + _multiply_target_and_add(val, column); + } + + return *this; +} + +template +inline Unordered_set_column& +Unordered_set_column::multiply_target_and_add(const Field_element_type& val, + Unordered_set_column& column) +{ + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + // assumes that the addition never zeros out this column. + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + if (_add(column)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } else { + throw std::invalid_argument("A chain column should not be multiplied by 0."); + } + } else { + if (_multiply_target_and_add(val, column)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } + } else { + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + _add(column); + } else { + clear(); + _add(column); + } + } else { + _multiply_target_and_add(val, column); + } + } + + return *this; +} + +template +template +inline Unordered_set_column& +Unordered_set_column::multiply_source_and_add(const Cell_range& column, + const Field_element_type& val) +{ + static_assert((!Master_matrix::isNonBasic || std::is_same_v), + "For boundary columns, the range has to be a column of same type to help ensure the validity of the " + "base element."); // could be removed, if we give the responsability to the user. + static_assert((!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type), + "For chain columns, the given column cannot be constant."); + + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + _add(column); + } + } else { + _multiply_source_and_add(column, val); + } + + return *this; +} + +template +inline Unordered_set_column& +Unordered_set_column::multiply_source_and_add(Unordered_set_column& column, + const Field_element_type& val) +{ + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + // assumes that the addition never zeros out this column. + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + if (_add(column)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } + } else { + if (_multiply_source_and_add(column, val)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } + } else { + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + _add(column); + } + } else { + _multiply_source_and_add(column, val); + } + } + + return *this; +} + +template +inline Unordered_set_column& +Unordered_set_column::operator=(const Unordered_set_column& other) +{ + static_assert(!Master_matrix::Option_list::has_row_access, "= assignement not enabled with row access option."); + + dim_opt::operator=(other); + chain_opt::operator=(other); + + for (auto* cell : column_) { + if constexpr (Master_matrix::Option_list::has_row_access) ra_opt::unlink(cell); + cellPool_->destroy(cell); + } + column_.clear(); + + operators_ = other.operators_; + cellPool_ = other.cellPool_; + + for (const Cell* cell : other.column_) { + if constexpr (Master_matrix::Option_list::is_z2) { + _insert_cell(cell->get_row_index()); + } else { + _insert_cell(cell->get_element(), cell->get_row_index()); + } + } + + return *this; +} + +template +inline void Unordered_set_column::_delete_cell(typename Column_type::iterator& it) +{ + if constexpr (Master_matrix::Option_list::has_row_access) ra_opt::unlink(*it); + cellPool_->destroy(*it); + auto tmp = it++; + // it = column_.erase(it); + column_.erase(tmp); +} + +template +inline typename Unordered_set_column::Cell* Unordered_set_column::_insert_cell( + const Field_element_type& value, id_index rowIndex) +{ + if constexpr (Master_matrix::Option_list::has_row_access) { + Cell* newCell = cellPool_->construct(ra_opt::columnIndex_, rowIndex); + newCell->set_element(value); + column_.insert(newCell); + ra_opt::insert_cell(rowIndex, newCell); + return newCell; + } else { + Cell* newCell = cellPool_->construct(rowIndex); + newCell->set_element(value); + column_.insert(newCell); + return newCell; + } +} + +template +inline void Unordered_set_column::_insert_cell(id_index rowIndex) +{ + if constexpr (Master_matrix::Option_list::has_row_access) { + Cell* newCell = cellPool_->construct(ra_opt::columnIndex_, rowIndex); + column_.insert(newCell); + ra_opt::insert_cell(rowIndex, newCell); + } else { + Cell* newCell = cellPool_->construct(rowIndex); + column_.insert(newCell); + } +} + +template +template +inline bool Unordered_set_column::_add(const Cell_range& column) +{ + return _generic_add( + column, + [&](const Cell& oldCell, Cell* newCell) { + if constexpr (!Master_matrix::Option_list::is_z2) newCell->set_element(oldCell.get_element()); + }, + [&](Cell* targetCell, const Cell& sourceCell) { + if constexpr (!Master_matrix::Option_list::is_z2) + operators_->add_inplace(targetCell->get_element(), sourceCell.get_element()); + }); +} + +template +template +inline bool Unordered_set_column::_multiply_target_and_add(const Field_element_type& val, + const Cell_range& column) +{ + if (val == 0u) { + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + throw std::invalid_argument("A chain column should not be multiplied by 0."); + // this would not only mess up the base, but also the pivots stored. + } else { + clear(); + for (const Cell& v : column) { + _insert_cell(v.get_element(), v.get_row_index()); + } + return true; + } + } + + // because the column is unordered, I don't see a way to do both operations in one go + // without garantees on the cell range... + operator*=(val); + return _add(column); +} + +template +template +inline bool Unordered_set_column::_multiply_source_and_add(const Cell_range& column, + const Field_element_type& val) +{ + if (val == 0u) { + return false; + } + + return _generic_add(column, + [&](const Cell& oldCell, Cell* newCell) { + newCell->set_element(oldCell.get_element()); + operators_->multiply_inplace(newCell->get_element(), val); + }, + [&](Cell* targetCell, const Cell& sourceCell) { + operators_->multiply_and_add_inplace_back(sourceCell.get_element(), val, targetCell->get_element()); + }); +} + +template +template +inline bool Unordered_set_column::_generic_add(const Cell_range& source, + F1&& process_source, + F2&& update_target) +{ + bool pivotIsZeroed = false; + + for (const Cell& cell : source) { + Cell* newCell; + if constexpr (Master_matrix::Option_list::has_row_access) { + newCell = cellPool_->construct(ra_opt::columnIndex_, cell.get_row_index()); + } else { + newCell = cellPool_->construct(cell.get_row_index()); + } + auto res = column_.insert(newCell); + if (res.second){ + process_source(cell, newCell); + if constexpr (Master_matrix::Option_list::has_row_access) ra_opt::insert_cell(cell.get_row_index(), newCell); + } else { + cellPool_->destroy(newCell); + if constexpr (Master_matrix::Option_list::is_z2) { + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + if (cell.get_row_index() == chain_opt::get_pivot()) pivotIsZeroed = true; + } + _delete_cell(res.first); + } else { + update_target(*res.first, cell); + if ((*res.first)->get_element() == Field_operators::get_additive_identity()) { + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + if ((*res.first)->get_row_index() == chain_opt::get_pivot()) pivotIsZeroed = true; + } + _delete_cell(res.first); + } else { + if constexpr (Master_matrix::Option_list::has_row_access) ra_opt::update_cell(**res.first); + } + } + } + } + + return pivotIsZeroed; +} + +} // namespace persistence_matrix +} // namespace Gudhi + +/** + * @ingroup persistence_matrix + * + * @brief Hash method for @ref Gudhi::persistence_matrix::Unordered_set_column. + * + * @tparam Master_matrix Template parameter of @ref Gudhi::persistence_matrix::Unordered_set_column. + * @tparam Cell_constructor Template parameter of @ref Gudhi::persistence_matrix::Unordered_set_column. + */ +template +struct std::hash > +{ + std::size_t operator()(const Gudhi::persistence_matrix::Unordered_set_column& column) const { + //can't use Gudhi::persistence_matrix::hash_column because unordered + std::size_t seed = 0; + for (const auto& cell : column) { + seed ^= std::hash()(cell.get_row_index() * static_cast(cell.get_element())); + } + return seed; + } +}; + +#endif // PM_UNORDERED_SET_COLUMN_H diff --git a/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/vector_column.h b/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/vector_column.h new file mode 100644 index 0000000000..c0a11ad3e8 --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Persistence_matrix/columns/vector_column.h @@ -0,0 +1,1246 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file vector_column.h + * @author Hannah Schreiber + * @brief Contains the @ref Vector_column class. + * Also defines the std::hash method for @ref Vector_column. + */ + +#ifndef PM_VECTOR_COLUMN_H +#define PM_VECTOR_COLUMN_H + +#include +#include +#include +#include +#include //binary_search +#include +#include //std::swap, std::move & std::exchange + +#include +#include + +namespace Gudhi { +namespace persistence_matrix { + +/** + * @class Vector_column vector_column.h gudhi/Persistence_matrix/columns/vector_column.h + * @ingroup persistence_matrix + * + * @brief Column class following the @ref PersistenceMatrixColumn concept. + * + * Column based on a vector structure. The cells are always ordered by row index, but cells are removed by + * @ref PersistenceMatrixColumn::clear(PersistenceMatrixOptions::index_type rowIndex) "clear(index)" in a lazy way, + * so erased values can still be in the underlying container. + * On the other hand, two cells will never have the same row index. + * + * @tparam Master_matrix An instanciation of @ref Matrix from which all types and options are deduced. + * @tparam Cell_constructor Factory of @ref Cell classes. + */ +template +class Vector_column : public Master_matrix::Row_access_option, + public Master_matrix::Column_dimension_option, + public Master_matrix::Chain_column_option +{ + public: + using Master = Master_matrix; + using index = typename Master_matrix::index; + using id_index = typename Master_matrix::id_index; + using dimension_type = typename Master_matrix::dimension_type; + using Field_element_type = typename Master_matrix::element_type; + using Cell = typename Master_matrix::Cell_type; + using Column_settings = typename Master_matrix::Column_settings; + + private: + using Field_operators = typename Master_matrix::Field_operators; + using Column_type = std::vector; + using Cell_constructor = typename Master_matrix::Cell_constructor; + + public: + using iterator = boost::indirect_iterator; + using const_iterator = boost::indirect_iterator; + using reverse_iterator = boost::indirect_iterator; + using const_reverse_iterator = boost::indirect_iterator; + + Vector_column(Column_settings* colSettings = nullptr); + template + Vector_column(const Container_type& nonZeroRowIndices, Column_settings* colSettings); + template + Vector_column(index columnIndex, + const Container_type& nonZeroRowIndices, + Row_container_type* rowContainer, + Column_settings* colSettings); + template + Vector_column(const Container_type& nonZeroChainRowIndices, + dimension_type dimension, + Column_settings* colSettings); + template + Vector_column(index columnIndex, + const Container_type& nonZeroChainRowIndices, + dimension_type dimension, + Row_container_type* rowContainer, + Column_settings* colSettings); + Vector_column(const Vector_column& column, + Column_settings* colSettings = nullptr); + template + Vector_column(const Vector_column& column, + index columnIndex, + Row_container_type* rowContainer, + Column_settings* colSettings = nullptr); + Vector_column(Vector_column&& column) noexcept; + ~Vector_column(); + + std::vector get_content(int columnLength = -1) const; + bool is_non_zero(id_index rowIndex) const; + bool is_empty() const; + std::size_t size() const; + + template + void reorder(const Map_type& valueMap, [[maybe_unused]] index columnIndex = -1); + void clear(); + // do not clear a cell to 0 if the cell was already 0, otherwise size/is_empty will be wrong. + void clear(id_index rowIndex); + + id_index get_pivot(); + Field_element_type get_pivot_value(); + + iterator begin() noexcept; + const_iterator begin() const noexcept; + iterator end() noexcept; + const_iterator end() const noexcept; + reverse_iterator rbegin() noexcept; + const_reverse_iterator rbegin() const noexcept; + reverse_iterator rend() noexcept; + const_reverse_iterator rend() const noexcept; + + template + Vector_column& operator+=(const Cell_range& column); + Vector_column& operator+=(Vector_column& column); + + Vector_column& operator*=(unsigned int v); + + // this = v * this + column + template + Vector_column& multiply_target_and_add(const Field_element_type& val, const Cell_range& column); + Vector_column& multiply_target_and_add(const Field_element_type& val, Vector_column& column); + // this = this + column * v + template + Vector_column& multiply_source_and_add(const Cell_range& column, const Field_element_type& val); + Vector_column& multiply_source_and_add(Vector_column& column, const Field_element_type& val); + + std::size_t compute_hash_value(); + + friend bool operator==(const Vector_column& c1, const Vector_column& c2) { + if (&c1 == &c2) return true; + if (c1.erasedValues_.empty() && c2.erasedValues_.empty() && c1.column_.size() != c2.column_.size()) return false; + + auto it1 = c1.column_.begin(); + auto it2 = c2.column_.begin(); + while (it1 != c1.column_.end() && it2 != c2.column_.end()) { + if constexpr (!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type) { + while (it1 != c1.column_.end() && c1.erasedValues_.find((*it1)->get_row_index()) != c1.erasedValues_.end()) + ++it1; + while (it2 != c2.column_.end() && c2.erasedValues_.find((*it2)->get_row_index()) != c2.erasedValues_.end()) + ++it2; + if (it1 == c1.column_.end() || it2 == c2.column_.end()) break; + } + if constexpr (Master_matrix::Option_list::is_z2) { + if ((*it1)->get_row_index() != (*it2)->get_row_index()) return false; + } else { + if ((*it1)->get_row_index() != (*it2)->get_row_index() || (*it1)->get_element() != (*it2)->get_element()) + return false; + } + ++it1; + ++it2; + } + + if constexpr (!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type) { + while (it1 != c1.column_.end() && c1.erasedValues_.find((*it1)->get_row_index()) != c1.erasedValues_.end()) ++it1; + while (it2 != c2.column_.end() && c2.erasedValues_.find((*it2)->get_row_index()) != c2.erasedValues_.end()) ++it2; + return it2 == c2.column_.end() && it1 == c1.column_.end(); + } else { + return true; + } + } + friend bool operator<(const Vector_column& c1, const Vector_column& c2) { + if (&c1 == &c2) return false; + + auto it1 = c1.column_.begin(); + auto it2 = c2.column_.begin(); + while (it1 != c1.column_.end() && it2 != c2.column_.end()) { + if constexpr (!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type) { + while (it1 != c1.column_.end() && c1.erasedValues_.find((*it1)->get_row_index()) != c1.erasedValues_.end()) + ++it1; + while (it2 != c2.column_.end() && c2.erasedValues_.find((*it2)->get_row_index()) != c2.erasedValues_.end()) + ++it2; + if (it1 == c1.column_.end() || it2 == c2.column_.end()) break; + } + + if ((*it1)->get_row_index() != (*it2)->get_row_index()) return (*it1)->get_row_index() < (*it2)->get_row_index(); + if constexpr (!Master_matrix::Option_list::is_z2) { + if ((*it1)->get_element() != (*it2)->get_element()) return (*it1)->get_element() < (*it2)->get_element(); + } + ++it1; + ++it2; + } + if constexpr (!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type) { + while (it1 != c1.column_.end() && c1.erasedValues_.find((*it1)->get_row_index()) != c1.erasedValues_.end()) ++it1; + while (it2 != c2.column_.end() && c2.erasedValues_.find((*it2)->get_row_index()) != c2.erasedValues_.end()) ++it2; + } + return it2 != c2.column_.end(); + } + + // Disabled with row access. + Vector_column& operator=(const Vector_column& other); + + friend void swap(Vector_column& col1, Vector_column& col2) { + swap(static_cast(col1), + static_cast(col2)); + swap(static_cast(col1), + static_cast(col2)); + swap(static_cast(col1), + static_cast(col2)); + col1.column_.swap(col2.column_); + col1.erasedValues_.swap(col2.erasedValues_); + std::swap(col1.operators_, col2.operators_); + std::swap(col1.cellPool_, col2.cellPool_); + } + + private: + using ra_opt = typename Master_matrix::Row_access_option; + using dim_opt = typename Master_matrix::Column_dimension_option; + using chain_opt = typename Master_matrix::Chain_column_option; + + Column_type column_; + std::unordered_set erasedValues_; // TODO: test other containers? Useless when clear(index) is never + // called, how much is it worth it? + Field_operators* operators_; + Cell_constructor* cellPool_; + + template + friend void _generic_merge_cell_to_column(Column_type& targetColumn, + Cell_iterator& itSource, + typename Column_type::Column_type::iterator& itTarget, + F1&& process_target, + F2&& process_source, + F3&& update_target1, + F4&& update_target2, + bool& pivotIsZeroed); + + void _delete_cell(Cell* cell); + void _delete_cell(typename Column_type::iterator& it); + Cell* _insert_cell(const Field_element_type& value, id_index rowIndex, Column_type& column); + void _insert_cell(id_index rowIndex, Column_type& column); + void _update_cell(const Field_element_type& value, id_index rowIndex, index position); + void _update_cell(id_index rowIndex, index position); + template + bool _add(const Cell_range& column); + template + bool _multiply_target_and_add(const Field_element_type& val, const Cell_range& column); + template + bool _multiply_source_and_add(const Cell_range& column, const Field_element_type& val); + template + bool _generic_add(const Cell_range& source, + F1&& process_target, + F2&& process_source, + F3&& update_target1, + F4&& update_target2); +}; + +template +inline Vector_column::Vector_column(Column_settings* colSettings) + : ra_opt(), dim_opt(), chain_opt(), operators_(nullptr), cellPool_(colSettings == nullptr ? nullptr : &(colSettings->cellConstructor)) +{ + if (operators_ == nullptr && cellPool_ == nullptr) return; //to allow default constructor which gives a dummy column + if constexpr (!Master_matrix::Option_list::is_z2){ + operators_ = &(colSettings->operators); + } +} + +template +template +inline Vector_column::Vector_column(const Container_type& nonZeroRowIndices, + Column_settings* colSettings) + : ra_opt(), + dim_opt(nonZeroRowIndices.size() == 0 ? 0 : nonZeroRowIndices.size() - 1), + chain_opt(), + column_(nonZeroRowIndices.size(), nullptr), + operators_(nullptr), + cellPool_(&(colSettings->cellConstructor)) +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Constructor not available for chain columns, please specify the dimension of the chain."); + + if constexpr (!Master_matrix::Option_list::is_z2){ + operators_ = &(colSettings->operators); + } + + index i = 0; + if constexpr (Master_matrix::Option_list::is_z2) { + for (id_index id : nonZeroRowIndices) { + _update_cell(id, i++); + } + } else { + for (const auto& p : nonZeroRowIndices) { + _update_cell(operators_->get_value(p.second), p.first, i++); + } + } +} + +template +template +inline Vector_column::Vector_column(index columnIndex, + const Container_type& nonZeroRowIndices, + Row_container_type* rowContainer, + Column_settings* colSettings) + : ra_opt(columnIndex, rowContainer), + dim_opt(nonZeroRowIndices.size() == 0 ? 0 : nonZeroRowIndices.size() - 1), + chain_opt([&] { + if constexpr (Master_matrix::Option_list::is_z2) { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + } else { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + } + }()), + column_(nonZeroRowIndices.size(), nullptr), + operators_(nullptr), + cellPool_(&(colSettings->cellConstructor)) +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Constructor not available for chain columns, please specify the dimension of the chain."); + + if constexpr (!Master_matrix::Option_list::is_z2){ + operators_ = &(colSettings->operators); + } + + index i = 0; + if constexpr (Master_matrix::Option_list::is_z2) { + for (id_index id : nonZeroRowIndices) { + _update_cell(id, i++); + } + } else { + for (const auto& p : nonZeroRowIndices) { + _update_cell(operators_->get_value(p.second), p.first, i++); + } + } +} + +template +template +inline Vector_column::Vector_column(const Container_type& nonZeroRowIndices, + dimension_type dimension, + Column_settings* colSettings) + : ra_opt(), + dim_opt(dimension), + chain_opt([&] { + if constexpr (Master_matrix::Option_list::is_z2) { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + } else { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + } + }()), + column_(nonZeroRowIndices.size(), nullptr), + operators_(nullptr), + cellPool_(&(colSettings->cellConstructor)) +{ + if constexpr (!Master_matrix::Option_list::is_z2){ + operators_ = &(colSettings->operators); + } + + index i = 0; + if constexpr (Master_matrix::Option_list::is_z2) { + for (id_index id : nonZeroRowIndices) { + _update_cell(id, i++); + } + } else { + for (const auto& p : nonZeroRowIndices) { + _update_cell(operators_->get_value(p.second), p.first, i++); + } + } +} + +template +template +inline Vector_column::Vector_column( + index columnIndex, + const Container_type& nonZeroRowIndices, + dimension_type dimension, + Row_container_type* rowContainer, + Column_settings* colSettings) + : ra_opt(columnIndex, rowContainer), + dim_opt(dimension), + chain_opt([&] { + if constexpr (Master_matrix::Option_list::is_z2) { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : *std::prev(nonZeroRowIndices.end()); + } else { + return nonZeroRowIndices.begin() == nonZeroRowIndices.end() ? -1 : std::prev(nonZeroRowIndices.end())->first; + } + }()), + column_(nonZeroRowIndices.size(), nullptr), + operators_(nullptr), + cellPool_(&(colSettings->cellConstructor)) +{ + if constexpr (!Master_matrix::Option_list::is_z2){ + operators_ = &(colSettings->operators); + } + + index i = 0; + if constexpr (Master_matrix::Option_list::is_z2) { + for (id_index id : nonZeroRowIndices) { + _update_cell(id, i++); + } + } else { + for (const auto& p : nonZeroRowIndices) { + _update_cell(operators_->get_value(p.second), p.first, i++); + } + } +} + +template +inline Vector_column::Vector_column(const Vector_column& column, + Column_settings* colSettings) + : ra_opt(), + dim_opt(static_cast(column)), + chain_opt(static_cast(column)), + column_(column.column_.size(), nullptr), + erasedValues_(column.erasedValues_), + operators_(colSettings == nullptr ? column.operators_ : nullptr), + cellPool_(colSettings == nullptr ? column.cellPool_ : &(colSettings->cellConstructor)) +{ + static_assert(!Master_matrix::Option_list::has_row_access, + "Simple copy constructor not available when row access option enabled. Please specify the new column " + "index and the row container."); + + if constexpr (!Master_matrix::Option_list::is_z2){ + if (colSettings != nullptr) operators_ = &(colSettings->operators); + } + + index i = 0; + for (const Cell* cell : column.column_) { + if constexpr (Master_matrix::Option_list::is_z2) { + _update_cell(cell->get_row_index(), i++); + } else { + _update_cell(cell->get_element(), cell->get_row_index(), i++); + } + } +} + +template +template +inline Vector_column::Vector_column(const Vector_column& column, index columnIndex, + Row_container_type* rowContainer, + Column_settings* colSettings) + : ra_opt(columnIndex, rowContainer), + dim_opt(static_cast(column)), + chain_opt(static_cast(column)), + column_(column.column_.size(), nullptr), + erasedValues_(column.erasedValues_), + operators_(colSettings == nullptr ? column.operators_ : nullptr), + cellPool_(colSettings == nullptr ? column.cellPool_ : &(colSettings->cellConstructor)) +{ + if constexpr (!Master_matrix::Option_list::is_z2){ + if (colSettings != nullptr) operators_ = &(colSettings->operators); + } + + index i = 0; + for (const Cell* cell : column.column_) { + if constexpr (Master_matrix::Option_list::is_z2) { + _update_cell(cell->get_row_index(), i++); + } else { + _update_cell(cell->get_element(), cell->get_row_index(), i++); + } + } +} + +template +inline Vector_column::Vector_column(Vector_column&& column) noexcept + : ra_opt(std::move(static_cast(column))), + dim_opt(std::move(static_cast(column))), + chain_opt(std::move(static_cast(column))), + column_(std::move(column.column_)), + erasedValues_(std::move(column.erasedValues_)), + operators_(std::exchange(column.operators_, nullptr)), + cellPool_(std::exchange(column.cellPool_, nullptr)) +{} + +template +inline Vector_column::~Vector_column() +{ + for (auto* cell : column_) { + _delete_cell(cell); + } +} + +template +inline std::vector::Field_element_type> +Vector_column::get_content(int columnLength) const +{ + if (columnLength < 0 && column_.size() > 0) + columnLength = column_.back()->get_row_index() + 1; + else if (columnLength < 0) + return std::vector(); + + std::vector container(columnLength, 0); + for (auto it = column_.begin(); it != column_.end() && (*it)->get_row_index() < static_cast(columnLength); + ++it) { + if constexpr (!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type) { + if (erasedValues_.find((*it)->get_row_index()) != erasedValues_.end()) continue; + } + if constexpr (Master_matrix::Option_list::is_z2) { + container[(*it)->get_row_index()] = 1; + } else { + container[(*it)->get_row_index()] = (*it)->get_element(); + } + } + return container; +} + +template +inline bool Vector_column::is_non_zero(id_index rowIndex) const +{ + if constexpr (!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type) + if (erasedValues_.find(rowIndex) != erasedValues_.end()) return false; + + Cell cell(rowIndex); + return std::binary_search(column_.begin(), column_.end(), &cell, + [](const Cell* a, const Cell* b) { return a->get_row_index() < b->get_row_index(); }); +} + +template +inline bool Vector_column::is_empty() const +{ + if constexpr (!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type) { + return column_.size() == erasedValues_.size(); // assumes that erasedValues is always a subset of column_, which is + // wrong if someone cleared an non exitsing value... + } else { + return column_.empty(); + } +} + +template +inline std::size_t Vector_column::size() const +{ + if constexpr (!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type) { + return column_.size() - erasedValues_.size(); // assumes that erasedValues is always a subset of column_, which is + // wrong if someone cleared an non exitsing value... + } else { + return column_.size(); + } +} + +template +template +inline void Vector_column::reorder(const Map_type& valueMap, + [[maybe_unused]] index columnIndex) +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Method not available for chain columns."); + + if (erasedValues_.empty()) { // to avoid useless push_backs. + for (Cell* cell : column_) { + if constexpr (Master_matrix::Option_list::has_row_access) { + ra_opt::unlink(cell); + if (columnIndex != static_cast(-1)) cell->set_column_index(columnIndex); + } + cell->set_row_index(valueMap.at(cell->get_row_index())); + if constexpr (Master_matrix::Option_list::has_intrusive_rows && Master_matrix::Option_list::has_row_access) + ra_opt::insert_cell(cell->get_row_index(), cell); + } + + // all cells have to be deleted first, to avoid problem with insertion when row is a set + if constexpr (!Master_matrix::Option_list::has_intrusive_rows && Master_matrix::Option_list::has_row_access) { + for (Cell* cell : column_) { + ra_opt::insert_cell(cell->get_row_index(), cell); + } + } + + std::sort(column_.begin(), column_.end(), [](const Cell* c1, const Cell* c2) { return *c1 < *c2; }); + } else { + Column_type newColumn; + for (Cell* cell : column_) { + if (erasedValues_.find(cell->get_row_index()) == erasedValues_.end()) { + if constexpr (Master_matrix::Option_list::has_row_access) { + ra_opt::unlink(cell); + if (columnIndex != static_cast(-1)) cell->set_column_index(columnIndex); + } + cell->set_row_index(valueMap.at(cell->get_row_index())); + newColumn.push_back(cell); + if constexpr (Master_matrix::Option_list::has_intrusive_rows && Master_matrix::Option_list::has_row_access) + ra_opt::insert_cell(cell->get_row_index(), cell); + } else { + _delete_cell(cell); + } + } + // all cells have to be deleted first, to avoid problem with insertion when row is a set + if constexpr (!Master_matrix::Option_list::has_intrusive_rows && Master_matrix::Option_list::has_row_access) { + for (Cell* cell : column_) { + ra_opt::insert_cell(cell->get_row_index(), cell); + } + } + std::sort(newColumn.begin(), newColumn.end(), [](const Cell* c1, const Cell* c2) { return *c1 < *c2; }); + erasedValues_.clear(); + column_.swap(newColumn); + } +} + +template +inline void Vector_column::clear() +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Method not available for chain columns as a base element should not be empty."); + + for (auto* cell : column_) { + if constexpr (Master_matrix::Option_list::has_row_access) ra_opt::unlink(cell); + cellPool_->destroy(cell); + } + + column_.clear(); + erasedValues_.clear(); +} + +template +inline void Vector_column::clear(id_index rowIndex) +{ + static_assert(!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type, + "Method not available for chain columns."); + + erasedValues_.insert(rowIndex); +} + +template +inline typename Vector_column::id_index +Vector_column::get_pivot() +{ + static_assert(Master_matrix::isNonBasic, + "Method not available for base columns."); // could technically be, but is the notion usefull then? + + if constexpr (Master_matrix::Option_list::is_of_boundary_type) { + if (column_.empty()) return -1; + if (erasedValues_.empty()) return column_.back()->get_row_index(); + + auto it = erasedValues_.find(column_.back()->get_row_index()); + while (!column_.empty() && it != erasedValues_.end()) { + erasedValues_.erase(it); + _delete_cell(column_.back()); + column_.pop_back(); + if (!column_.empty()) it = erasedValues_.find(column_.back()->get_row_index()); + } + + if (column_.empty()) return -1; + return column_.back()->get_row_index(); + } else { + return chain_opt::get_pivot(); + } +} + +template +inline typename Vector_column::Field_element_type +Vector_column::get_pivot_value() +{ + static_assert(Master_matrix::isNonBasic, + "Method not available for base columns."); // could technically be, but is the notion usefull then? + + if constexpr (Master_matrix::Option_list::is_z2) { + return 1; + } else { + if constexpr (Master_matrix::Option_list::is_of_boundary_type) { + if (column_.empty()) return 0; + if (erasedValues_.empty()) return column_.back()->get_element(); + + auto it = erasedValues_.find(column_.back()->get_row_index()); + while (!column_.empty() && it != erasedValues_.end()) { + erasedValues_.erase(it); + _delete_cell(column_.back()); + column_.pop_back(); + if (!column_.empty()) it = erasedValues_.find(column_.back()->get_row_index()); + } + + if (column_.empty()) return 0; + return column_.back()->get_element(); + } else { + if (chain_opt::get_pivot() == static_cast(-1)) return Field_element_type(); + for (const Cell* cell : column_) { + if (cell->get_row_index() == chain_opt::get_pivot()) return cell->get_element(); + } + return Field_element_type(); // should never happen if chain column is used properly + } + } +} + +template +inline typename Vector_column::iterator +Vector_column::begin() noexcept +{ + return column_.begin(); +} + +template +inline typename Vector_column::const_iterator +Vector_column::begin() const noexcept +{ + return column_.begin(); +} + +template +inline typename Vector_column::iterator +Vector_column::end() noexcept +{ + return column_.end(); +} + +template +inline typename Vector_column::const_iterator +Vector_column::end() const noexcept +{ + return column_.end(); +} + +template +inline typename Vector_column::reverse_iterator +Vector_column::rbegin() noexcept +{ + return column_.rbegin(); +} + +template +inline typename Vector_column::const_reverse_iterator +Vector_column::rbegin() const noexcept +{ + return column_.rbegin(); +} + +template +inline typename Vector_column::reverse_iterator +Vector_column::rend() noexcept +{ + return column_.rend(); +} + +template +inline typename Vector_column::const_reverse_iterator +Vector_column::rend() const noexcept +{ + return column_.rend(); +} + +template +template +inline Vector_column& Vector_column::operator+=( + const Cell_range& column) +{ + static_assert((!Master_matrix::isNonBasic || std::is_same_v), + "For boundary columns, the range has to be a column of same type to help ensure the validity of the " + "base element."); // could be removed, if we give the responsability to the user. + static_assert((!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type), + "For chain columns, the given column cannot be constant."); + + _add(column); + + return *this; +} + +template +inline Vector_column& Vector_column::operator+=( + Vector_column& column) +{ + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + // assumes that the addition never zeros out this column. + if (_add(column)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } else { + _add(column); + } + + return *this; +} + +template +inline Vector_column& Vector_column::operator*=( + unsigned int v) +{ + if constexpr (Master_matrix::Option_list::is_z2) { + if (v % 2 == 0) { + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + throw std::invalid_argument("A chain column should not be multiplied by 0."); + } else { + clear(); + } + } + } else { + Field_element_type val = operators_->get_value(v); + + if (val == Field_operators::get_additive_identity()) { + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + throw std::invalid_argument("A chain column should not be multiplied by 0."); + } else { + clear(); + } + return *this; + } + + if (val == Field_operators::get_multiplicative_identity()) return *this; + + for (Cell* cell : column_) { + operators_->multiply_inplace(cell->get_element(), val); + if constexpr (Master_matrix::Option_list::has_row_access) ra_opt::update_cell(*cell); + } + } + + return *this; +} + +template +template +inline Vector_column& Vector_column::multiply_target_and_add( + const Field_element_type& val, const Cell_range& column) +{ + static_assert((!Master_matrix::isNonBasic || std::is_same_v), + "For boundary columns, the range has to be a column of same type to help ensure the validity of the " + "base element."); // could be removed, if we give the responsability to the user. + static_assert((!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type), + "For chain columns, the given column cannot be constant."); + + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + _add(column); + } else { + clear(); + _add(column); + } + } else { + _multiply_target_and_add(val, column); + } + + return *this; +} + +template +inline Vector_column& Vector_column::multiply_target_and_add( + const Field_element_type& val, Vector_column& column) +{ + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + // assumes that the addition never zeros out this column. + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + if (_add(column)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } else { + throw std::invalid_argument("A chain column should not be multiplied by 0."); + } + } else { + if (_multiply_target_and_add(val, column)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } + } else { + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + _add(column); + } else { + clear(); + _add(column); + } + } else { + _multiply_target_and_add(val, column); + } + } + + return *this; +} + +template +template +inline Vector_column& Vector_column::multiply_source_and_add( + const Cell_range& column, const Field_element_type& val) +{ + static_assert((!Master_matrix::isNonBasic || std::is_same_v), + "For boundary columns, the range has to be a column of same type to help ensure the validity of the " + "base element."); // could be removed, if we give the responsability to the user. + static_assert((!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type), + "For chain columns, the given column cannot be constant."); + + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + _add(column); + } + } else { + _multiply_source_and_add(column, val); + } + + return *this; +} + +template +inline Vector_column& Vector_column::multiply_source_and_add( + Vector_column& column, const Field_element_type& val) +{ + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + // assumes that the addition never zeros out this column. + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + if (_add(column)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } + } else { + if (_multiply_source_and_add(column, val)) { + chain_opt::swap_pivots(column); + dim_opt::swap_dimension(column); + } + } + } else { + if constexpr (Master_matrix::Option_list::is_z2) { + if (val) { + _add(column); + } + } else { + _multiply_source_and_add(column, val); + } + } + + return *this; +} + +template +inline Vector_column& Vector_column::operator=( + const Vector_column& other) +{ + static_assert(!Master_matrix::Option_list::has_row_access, "= assignement not enabled with row access option."); + + dim_opt::operator=(other); + chain_opt::operator=(other); + + auto tmpPool = cellPool_; + cellPool_ = other.cellPool_; + + while (column_.size() > other.column_.size()) { + if (column_.back() != nullptr) { + if constexpr (Master_matrix::Option_list::has_row_access) ra_opt::unlink(column_.back()); + tmpPool->destroy(column_.back()); + } + column_.pop_back(); + } + + column_.resize(other.column_.size(), nullptr); + index i = 0; + for (const Cell* cell : other.column_) { + if (column_[i] != nullptr) { + if constexpr (Master_matrix::Option_list::has_row_access) ra_opt::unlink(column_[i]); + tmpPool->destroy(column_[i]); + } + if constexpr (Master_matrix::Option_list::is_z2) { + _update_cell(cell->get_row_index(), i++); + } else { + _update_cell(cell->get_element(), cell->get_row_index(), i++); + } + } + erasedValues_ = other.erasedValues_; + operators_ = other.operators_; + + return *this; +} + +template +inline std::size_t Vector_column::compute_hash_value() +{ + std::size_t seed = 0; + for (Cell* cell : column_) { + if (erasedValues_.find(cell->get_row_index()) == erasedValues_.end()){ + seed ^= std::hash()(cell->get_row_index() * static_cast(cell->get_element())) + + 0x9e3779b9 + (seed << 6) + (seed >> 2); + } + } + return seed; +} + +template +inline void Vector_column::_delete_cell(Cell* cell) +{ + if constexpr (Master_matrix::Option_list::has_row_access) ra_opt::unlink(cell); + cellPool_->destroy(cell); +} + +template +inline void Vector_column::_delete_cell(typename Column_type::iterator& it) +{ + _delete_cell(*it); + ++it; +} + +template +inline typename Vector_column::Cell* Vector_column::_insert_cell( + const Field_element_type& value, id_index rowIndex, Column_type& column) +{ + if constexpr (Master_matrix::Option_list::has_row_access) { + Cell* newCell = cellPool_->construct(ra_opt::columnIndex_, rowIndex); + newCell->set_element(value); + column.push_back(newCell); + ra_opt::insert_cell(rowIndex, newCell); + return newCell; + } else { + Cell* newCell = cellPool_->construct(rowIndex); + newCell->set_element(value); + column.push_back(newCell); + return newCell; + } +} + +template +inline void Vector_column::_insert_cell(id_index rowIndex, Column_type& column) +{ + if constexpr (Master_matrix::Option_list::has_row_access) { + Cell* newCell = cellPool_->construct(ra_opt::columnIndex_, rowIndex); + column.push_back(newCell); + ra_opt::insert_cell(rowIndex, newCell); + } else { + Cell* newCell = cellPool_->construct(rowIndex); + column.push_back(newCell); + } +} + +template +inline void Vector_column::_update_cell(const Field_element_type& value, + id_index rowIndex, index position) +{ + if constexpr (Master_matrix::Option_list::has_row_access) { + Cell* newCell = cellPool_->construct(ra_opt::columnIndex_, rowIndex); + newCell->set_element(value); + column_[position] = newCell; + ra_opt::insert_cell(rowIndex, newCell); + } else { + column_[position] = cellPool_->construct(rowIndex); + column_[position]->set_element(value); + } +} + +template +inline void Vector_column::_update_cell(id_index rowIndex, index position) +{ + if constexpr (Master_matrix::Option_list::has_row_access) { + Cell* newCell = cellPool_->construct(ra_opt::columnIndex_, rowIndex); + column_[position] = newCell; + ra_opt::insert_cell(rowIndex, newCell); + } else { + column_[position] = cellPool_->construct(rowIndex); + } +} + +template +template +inline bool Vector_column::_add(const Cell_range& column) +{ + if (column.begin() == column.end()) return false; + if (column_.empty()) { // chain should never enter here. + column_.resize(column.size()); + index i = 0; + for (const Cell& cell : column) { + if constexpr (Master_matrix::Option_list::is_z2) { + _update_cell(cell.get_row_index(), i++); + } else { + _update_cell(cell.get_element(), cell.get_row_index(), i++); + } + } + return true; + } + + Column_type newColumn; + newColumn.reserve(column_.size() + column.size()); // safe upper bound + + auto pivotIsZeroed = _generic_add( + column, + [&](Cell* cellTarget) { newColumn.push_back(cellTarget); }, + [&](typename Cell_range::const_iterator& itSource, + [[maybe_unused]] const typename Column_type::iterator& itTarget) { + if constexpr (Master_matrix::Option_list::is_z2) { + _insert_cell(itSource->get_row_index(), newColumn); + } else { + _insert_cell(itSource->get_element(), itSource->get_row_index(), newColumn); + } + }, + [&](Field_element_type& targetElement, typename Cell_range::const_iterator& itSource) { + if constexpr (!Master_matrix::Option_list::is_z2) + operators_->add_inplace(targetElement, itSource->get_element()); + }, + [&](Cell* cellTarget) { newColumn.push_back(cellTarget); }); + + column_.swap(newColumn); + + return pivotIsZeroed; +} + +template +template +inline bool Vector_column::_multiply_target_and_add(const Field_element_type& val, + const Cell_range& column) +{ + if (val == 0u) { + if constexpr (Master_matrix::isNonBasic && !Master_matrix::Option_list::is_of_boundary_type) { + throw std::invalid_argument("A chain column should not be multiplied by 0."); + // this would not only mess up the base, but also the pivots stored. + } else { + clear(); + } + } + if (column_.empty()) { // chain should never enter here. + column_.resize(column.size()); + index i = 0; + for (const Cell& cell : column) { + if constexpr (Master_matrix::Option_list::is_z2) { + _update_cell(cell.get_row_index(), i++); + } else { + _update_cell(cell.get_element(), cell.get_row_index(), i++); + } + } + if constexpr (std::is_same_v >) erasedValues_ = column.erasedValues_; + return true; + } + + Column_type newColumn; + newColumn.reserve(column_.size() + column.size()); // safe upper bound + + auto pivotIsZeroed = _generic_add( + column, + [&](Cell* cellTarget) { + operators_->multiply_inplace(cellTarget->get_element(), val); + if constexpr (Master_matrix::Option_list::has_row_access) ra_opt::update_cell(*cellTarget); + newColumn.push_back(cellTarget); + }, + [&](typename Cell_range::const_iterator& itSource, const typename Column_type::iterator& itTarget) { + _insert_cell(itSource->get_element(), itSource->get_row_index(), newColumn); + }, + [&](Field_element_type& targetElement, typename Cell_range::const_iterator& itSource) { + operators_->multiply_and_add_inplace_front(targetElement, val, itSource->get_element()); + }, + [&](Cell* cellTarget) { newColumn.push_back(cellTarget); }); + + column_.swap(newColumn); + + return pivotIsZeroed; +} + +template +template +inline bool Vector_column::_multiply_source_and_add(const Cell_range& column, + const Field_element_type& val) +{ + if (val == 0u || column.begin() == column.end()) { + return false; + } + + Column_type newColumn; + newColumn.reserve(column_.size() + column.size()); // safe upper bound + + auto pivotIsZeroed = _generic_add( + column, + [&](Cell* cellTarget) { newColumn.push_back(cellTarget); }, + [&](typename Cell_range::const_iterator& itSource, const typename Column_type::iterator& itTarget) { + Cell* newCell = _insert_cell(itSource->get_element(), itSource->get_row_index(), newColumn); + operators_->multiply_inplace(newCell->get_element(), val); + if constexpr (Master_matrix::Option_list::has_row_access) ra_opt::update_cell(*newCell); + }, + [&](Field_element_type& targetElement, typename Cell_range::const_iterator& itSource) { + operators_->multiply_and_add_inplace_back(itSource->get_element(), val, targetElement); + }, + [&](Cell* cellTarget) { newColumn.push_back(cellTarget); }); + + column_.swap(newColumn); + + return pivotIsZeroed; +} + +template +template +inline bool Vector_column::_generic_add(const Cell_range& column, + F1&& process_target, + F2&& process_source, + F3&& update_target1, + F4&& update_target2) +{ + auto updateTargetIterator = [&](typename Column_type::iterator& itTarget){ + if constexpr (!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type){ + while (itTarget != column_.end() && erasedValues_.find((*itTarget)->get_row_index()) != erasedValues_.end()) { + _delete_cell(*itTarget); + ++itTarget; + } + } + }; + auto updateSourceIterator = [&](typename Cell_range::const_iterator& itSource){ + if constexpr (std::is_same_v > && + (!Master_matrix::isNonBasic || Master_matrix::Option_list::is_of_boundary_type)) { + while (itSource != column.end() && + column.erasedValues_.find(itSource->get_row_index()) != column.erasedValues_.end()) + ++itSource; + } + }; + + bool pivotIsZeroed = false; + + auto itTarget = column_.begin(); + auto itSource = column.begin(); + while (itTarget != column_.end() && itSource != column.end()) { + updateTargetIterator(itTarget); + updateSourceIterator(itSource); + if (itTarget == column_.end() || itSource == column.end()) break; + + _generic_merge_cell_to_column(*this, + itSource, itTarget, + process_target, process_source, update_target1, update_target2, + pivotIsZeroed); + } + + while (itSource != column.end()) { + updateSourceIterator(itSource); + if (itSource == column.end()) break; + + process_source(itSource, column_.end()); + ++itSource; + } + + while (itTarget != column_.end()) { + updateTargetIterator(itTarget); + if (itTarget == column_.end()) break; + + process_target(*itTarget); + ++itTarget; + } + + erasedValues_.clear(); + + return pivotIsZeroed; +} + +} // namespace persistence_matrix +} // namespace Gudhi + +/** + * @ingroup persistence_matrix + * + * @brief Hash method for @ref Gudhi::persistence_matrix::Vector_column. + * + * @tparam Master_matrix Template parameter of @ref Gudhi::persistence_matrix::Vector_column. + * @tparam Cell_constructor Template parameter of @ref Gudhi::persistence_matrix::Vector_column. + */ +template +struct std::hash > +{ + std::size_t operator()(const Gudhi::persistence_matrix::Vector_column& column) const { + return column.compute_hash_value(); + } +}; + +#endif // PM_VECTOR_COLUMN_H diff --git a/src/Persistence_matrix/include/gudhi/Persistence_matrix/matrix_dimension_holders.h b/src/Persistence_matrix/include/gudhi/Persistence_matrix/matrix_dimension_holders.h new file mode 100644 index 0000000000..113c7cc755 --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Persistence_matrix/matrix_dimension_holders.h @@ -0,0 +1,184 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file matrix_dimension_holders.h + * @author Hannah Schreiber + * @brief Contains the @ref Matrix_max_dimension_holder @ref Matrix_all_dimension_holder classes + * and the @ref Dummy_matrix_dimension_holder structure. + */ + +#ifndef PM_MATRIX_DIM_HOLDER_H +#define PM_MATRIX_DIM_HOLDER_H + +#include //std::swap, std::move & std::exchange +#include + +namespace Gudhi { +namespace persistence_matrix { + +/** + * @ingroup persistence_matrix + * + * @brief Empty structure. + * Inheritated instead of @ref Matrix_max_dimension_holder or @ref Matrix_all_dimension_holder, when the maximal + * dimension of a matrix is not stored. + */ +struct Dummy_matrix_dimension_holder { + template + Dummy_matrix_dimension_holder([[maybe_unused]] dimension_type maximalDimension) {} + + friend void swap([[maybe_unused]] Dummy_matrix_dimension_holder& d1, + [[maybe_unused]] Dummy_matrix_dimension_holder& d2) {} +}; + +/** + * @ingroup persistence_matrix + * + * @brief Class managing the maximal dimension of a face represented in the inheritating matrix, when the option of + * face removal is not enabled. + * + * @tparam dimension_type Dimension value type. Has to be an integer type. + * If unsigned, the maximal value of the type should not be attained during a run. + */ +template +class Matrix_max_dimension_holder +{ + public: + /** + * @brief Default constructor. If a dimension is specified, stores it as the maximal value. + * + * @param maximalDimension Value of the maximal dimension. Has to be either positive or -1. Default value: -1. + */ + Matrix_max_dimension_holder(dimension_type maximalDimension = -1) : maxDim_(maximalDimension){}; + /** + * @brief Copy constructor. + * + * @param toCopy Matrix to copy. + */ + Matrix_max_dimension_holder(const Matrix_max_dimension_holder& toCopy) : maxDim_(toCopy.maxDim_){}; + /** + * @brief Move constructor. + * + * @param other Matrix to move. + */ + Matrix_max_dimension_holder(Matrix_max_dimension_holder&& other) noexcept + : maxDim_(std::exchange(other.maxDim_, -1)){}; + + /** + * @brief Returns the maximal dimension of a face represented in the matrix. + * + * @return The maximal dimension. + */ + dimension_type get_max_dimension() const { return maxDim_; }; + + /** + * @brief Assign operator. + */ + Matrix_max_dimension_holder& operator=(const Matrix_max_dimension_holder& other) { + std::swap(maxDim_, other.maxDim_); + return *this; + }; + /** + * @brief Swap operator. + */ + friend void swap(Matrix_max_dimension_holder& matrix1, Matrix_max_dimension_holder& matrix2) { + std::swap(matrix1.maxDim_, matrix2.maxDim_); + } + + protected: + dimension_type maxDim_; /**< Current maximal dimension. */ + + void update_up(dimension_type dimension) { + if (maxDim_ == -1 || maxDim_ < dimension) maxDim_ = dimension; + }; +}; + +/** + * @ingroup persistence_matrix + * + * @brief Class managing the maximal dimension of a face represented in the inheritating matrix, when the option of + * face removal is enabled. + * + * @tparam dimension_type Dimension value type. Has to be an integer type. + * If unsigned, the maximal value of the type should not be attained during a run. + */ +template +class Matrix_all_dimension_holder +{ + public: + /** + * @brief Default constructor. If a dimension is specified, stores it as the maximal value. + * + * @param maximalDimension Value of the maximal dimension. Has to be either positive or -1. Default value: -1. + */ + Matrix_all_dimension_holder(dimension_type maximalDimension = -1) + : dimensions_(maximalDimension < 0 ? 0 : maximalDimension + 1, 0), maxDim_(maximalDimension) { + if (maxDim_ != -1) dimensions_[maxDim_] = 1; + }; + /** + * @brief Copy constructor. + * + * @param toCopy Matrix to copy. + */ + Matrix_all_dimension_holder(const Matrix_all_dimension_holder& toCopy) + : dimensions_(toCopy.dimensions_), maxDim_(toCopy.maxDim_){}; + /** + * @brief Move constructor. + * + * @param other Matrix to move. + */ + Matrix_all_dimension_holder(Matrix_all_dimension_holder&& other) noexcept + : dimensions_(std::move(other.dimensions_)), maxDim_(std::exchange(other.maxDim_, -1)){}; + + /** + * @brief Returns the maximal dimension of a face represented in the matrix. + * + * @return The maximal dimension. + */ + dimension_type get_max_dimension() const { return maxDim_; }; + + /** + * @brief Assign operator. + */ + Matrix_all_dimension_holder& operator=(Matrix_all_dimension_holder other) { + std::swap(maxDim_, other.maxDim_); + dimensions_.swap(other.dimensions_); + return *this; + }; + /** + * @brief Swap operator. + */ + friend void swap(Matrix_all_dimension_holder& matrix1, Matrix_all_dimension_holder& matrix2) { + std::swap(matrix1.maxDim_, matrix2.maxDim_); + matrix1.dimensions_.swap(matrix2.dimensions_); + } + + protected: + std::vector dimensions_; /**< Number of faces by dimension. */ + dimension_type maxDim_; /**< Current maximal dimension. */ + + void update_up(unsigned int dimension) { + if (dimensions_.size() <= dimension) dimensions_.resize(dimension + 1, 0); + ++(dimensions_[dimension]); + maxDim_ = dimensions_.size() - 1; + }; + + void update_down(unsigned int dimension) { + --(dimensions_[dimension]); // assumes dimension already exists and is not 0 + while (!dimensions_.empty() && dimensions_.back() == 0) dimensions_.pop_back(); + maxDim_ = dimensions_.size() - 1; + }; +}; + +} // namespace persistence_matrix +} // namespace Gudhi + +#endif // PM_MATRIX_DIM_HOLDER_H diff --git a/src/Persistence_matrix/include/gudhi/Persistence_matrix/matrix_row_access.h b/src/Persistence_matrix/include/gudhi/Persistence_matrix/matrix_row_access.h new file mode 100644 index 0000000000..1bdf868ed0 --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Persistence_matrix/matrix_row_access.h @@ -0,0 +1,162 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file matrix_row_access.h + * @author Hannah Schreiber + * @brief Contains the @ref Matrix_row_access class and @ref Dummy_matrix_row_access structure. + */ + +#ifndef PM_BASE_MATRIX_RA_H +#define PM_BASE_MATRIX_RA_H + +#include //std::move + +namespace Gudhi { +namespace persistence_matrix { + +/** + * @ingroup persistence_matrix + * + * @brief Empty structure. + * Inheritated instead of @ref Matrix_row_access, when the the row access is not enabled. + */ +struct Dummy_matrix_row_access { + Dummy_matrix_row_access([[maybe_unused]] unsigned int numberOfRows = 0){}; + + friend void swap([[maybe_unused]] Dummy_matrix_row_access& d1, [[maybe_unused]] Dummy_matrix_row_access& d2) {} +}; + +/** + * @class Matrix_row_access matrix_row_access.h gudhi/Persistence_matrix/matrix_row_access.h + * @ingroup persistence_matrix + * + * @brief Class managing the row access for the inheritating matrix. + * + * @tparam Row_type Either boost::intrusive::list if @ref PersistenceMatrixOptions::has_intrusive_rows + * is true, or std::set otherwise. + * @tparam Row_container_type Either std::map if @ref PersistenceMatrixOptions::has_removable_rows is + * true, or std::vector otherwise. + * @tparam has_removable_rows Value of @ref PersistenceMatrixOptions::has_removable_rows. + * @tparam id_index @ref IDIdx index type. + */ +template +class Matrix_row_access +{ + public: + /** + * @brief Default constructor. + */ + Matrix_row_access() : rows_(new Row_container_type()){}; + /** + * @brief Constructor reserving space for the given number of rows. + * + * Has only an effect if @ref PersistenceMatrixOptions::has_removable_rows is false. + * + * @param numberOfRows Number of rows to reserve space for. + */ + Matrix_row_access(unsigned int numberOfRows) : rows_(new Row_container_type()) { + if constexpr (!has_removable_rows) { + rows_->resize(numberOfRows); + } + } + /** + * @brief Copy constructor. + * + * @param toCopy Matrix to copy. + */ + Matrix_row_access(const Matrix_row_access& toCopy) + : rows_(new Row_container_type()) // as the matrix is rebuild, the rows should not be copied. + { + if constexpr (!has_removable_rows) { + rows_->resize(toCopy.rows_->size()); + } + } + /** + * @brief Move constructor. + * + * @param other Matrix to move. + */ + Matrix_row_access(Matrix_row_access&& other) noexcept : rows_(std::exchange(other.rows_, nullptr)) {} + /** + * @brief Destructor. + */ + ~Matrix_row_access() { delete rows_; } + + /** + * @brief Returns the row at the given @ref rowindex "row index". + * The type of the row depends on the choosen options, see @ref PersistenceMatrixOptions::has_intrusive_rows. + * + * @param rowIndex @ref rowindex "Row index" of the row to return: @ref IDIdx for @ref chainmatrix "chain matrices" + * or updated @ref IDIdx for @ref boundarymatrix "boundary matrices" if swaps occured. + * @return Reference to the row. + */ + Row_type& get_row(id_index rowIndex) { + if constexpr (has_removable_rows) { + return rows_->at(rowIndex); + } else { + return rows_->operator[](rowIndex); + } + } + /** + * @brief Returns the row at the given @ref rowindex "row index". + * The type of the row depends on the choosen options, see @ref PersistenceMatrixOptions::has_intrusive_rows. + * + * @param rowIndex @ref rowindex "Row index" of the row to return: @ref IDIdx for @ref chainmatrix "chain matrices" + * or updated @ref IDIdx for @ref boundarymatrix "boundary matrices" if swaps occured. + * @return Const reference to the row. + */ + const Row_type& get_row(id_index rowIndex) const { + if constexpr (has_removable_rows) { + return rows_->at(rowIndex); + } else { + return rows_->operator[](rowIndex); + } + } + /** + * @brief Only available if @ref PersistenceMatrixOptions::has_removable_rows is true. Removes the given row + * from the row container if the row exists and is empty. + * + * @param rowIndex @ref rowindex "Row index" of the row to remove. + */ + void erase_empty_row(id_index rowIndex) { + static_assert(has_removable_rows, "'erase_empty_row' is not implemented for the chosen options."); + + auto it = rows_->find(rowIndex); + if (it != rows_->end() && it->second.empty()) rows_->erase(it); + } + + /** + * @brief Assign operator. + */ + Matrix_row_access& operator=(const Matrix_row_access& other) { + if constexpr (has_removable_rows) + rows_->reserve(other.rows_->size()); + else + rows_->resize(other.rows_->size()); + return *this; + } + /** + * @brief Swap operator. + */ + friend void swap(Matrix_row_access& matrix1, Matrix_row_access& matrix2) { std::swap(matrix1.rows_, matrix2.rows_); } + + protected: + /** + * @brief Row container. A pointer to faciliate column swaps when two matrices are swapped. + * Has to be destroyed after matrix_, therefore has to be inherited. + */ + Row_container_type* rows_; +}; + +} // namespace persistence_matrix +} // namespace Gudhi + +#endif // PM_BASE_MATRIX_RA_H diff --git a/src/Persistence_matrix/include/gudhi/Persistence_matrix/overlay_ididx_to_matidx.h b/src/Persistence_matrix/include/gudhi/Persistence_matrix/overlay_ididx_to_matidx.h new file mode 100644 index 0000000000..f7df2690ef --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Persistence_matrix/overlay_ididx_to_matidx.h @@ -0,0 +1,1091 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file overlay_ididx_to_matidx.h + * @author Hannah Schreiber + * @brief Contains the @ref Id_to_index_overlay class. + */ + +#ifndef PM_ID_TO_POS_TRANSLATION_H +#define PM_ID_TO_POS_TRANSLATION_H + +#include +#include +#include +#include //std::swap, std::move & std::exchange +#include //std::transform +#include //std::invalid_argument + +namespace Gudhi { +namespace persistence_matrix { + +/** + * @class Id_to_index_overlay overlay_ididx_to_matidx.h gudhi/Persistence_matrix/overlay_ididx_to_matidx.h + * @ingroup persistence_matrix + * + * @brief Overlay for @ref mp_matrices "non-basic matrices" replacing all input and output @ref MatIdx indices of + * the original methods with @ref IDIdx indices. + * + * @tparam Matrix_type %Matrix type taking the overlay. + * @tparam Master_matrix_type An instanciation of @ref Matrix from which all types and options are deduced. + */ +template +class Id_to_index_overlay +{ + public: + using index = typename Master_matrix_type::index; /**< @ref MatIdx index type. */ + using id_index = typename Master_matrix_type::id_index; /**< @ref IDIdx index type. */ + using dimension_type = typename Master_matrix_type::dimension_type; /**< Dimension value type. */ + /** + * @brief Field operators class. Necessary only if @ref PersistenceMatrixOptions::is_z2 is false. + */ + using Field_operators = typename Master_matrix_type::Field_operators; + using Field_element_type = typename Master_matrix_type::element_type; /**< Type of an field element. */ + using boundary_type = typename Master_matrix_type::boundary_type; /**< Type of an input column. */ + using Column_type = typename Master_matrix_type::Column_type; /**< Column type. */ + using Row_type = typename Master_matrix_type::Row_type; /**< Row type, + only necessary with row access option. */ + using bar_type = typename Master_matrix_type::Bar; /**< Bar type. */ + using barcode_type = typename Master_matrix_type::barcode_type; /**< Barcode type. */ + using cycle_type = typename Master_matrix_type::cycle_type; /**< Cycle type. */ + using Cell_constructor = typename Master_matrix_type::Cell_constructor; /**< Factory of @ref Cell classes. */ + using Column_settings = typename Master_matrix_type::Column_settings; /**< Structure giving access to the columns to + necessary external classes. */ + + /** + * @brief Constructs an empty matrix. + * + * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all + * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + */ + Id_to_index_overlay(Column_settings* colSettings); + /** + * @brief Constructs a new matrix from the given ranges of @ref Matrix::cell_rep_type. Each range corresponds to a + * column (the order of the ranges are preserved). The content of the ranges is assumed to be sorted by increasing + * IDs. The IDs of the simplices are also assumed to be consecutifs, ordered by filtration value, starting with 0. + * + * @tparam Boundary_type Range type for @ref Matrix::cell_rep_type ranges. + * Assumed to have a begin(), end() and size() method. + * @param orderedBoundaries Range of boundaries: @p orderedBoundaries is interpreted as a boundary matrix of a + * filtered **simplicial** complex, whose boundaries are ordered by filtration order. + * Therefore, `orderedBoundaries[i]` should store the boundary of the \f$ i^{th} \f$ simplex in the filtration, + * as an ordered list of indices of its facets (again those indices correspond to their respective position + * in the matrix). That is why the indices of the simplices are assumed to be consecutifs and starting with 0 + * (an empty boundary is interpreted as a vertex boundary and not as a non existing simplex). + * All dimensions up to the maximal dimension of interest have to be present. If only a higher dimension is of + * interest and not everything should be stored, then use the @ref insert_boundary method instead + * (after creating the matrix with the @ref Id_to_index_overlay(unsigned int, Column_settings*) + * constructor preferably). + * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all + * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + */ + template + Id_to_index_overlay(const std::vector& orderedBoundaries, + Column_settings* colSettings); + /** + * @brief Constructs a new empty matrix and reserves space for the given number of columns. + * + * @param numberOfColumns Number of columns to reserve space for. + * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all + * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + */ + Id_to_index_overlay(unsigned int numberOfColumns, Column_settings* colSettings); + /** + * @brief Only available for @ref chainmatrix "chain matrices". Constructs an empty matrix and stores the given + * comparators. + * + * @warning If @ref PersistenceMatrixOptions::has_vine_update is false, the comparators are not used. + * And if @ref PersistenceMatrixOptions::has_vine_update is true, but + * @ref PersistenceMatrixOptions::has_column_pairings is also true, the comparators are ignored and + * the current barcode is used to compare birth and deaths. Therefore it is useless to provide them in those cases. + * + * @tparam BirthComparatorFunction Type of the birth comparator: (@ref pos_index, @ref pos_index) -> bool + * @tparam DeathComparatorFunction Type of the death comparator: (@ref pos_index, @ref pos_index) -> bool + * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all + * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * @param birthComparator Method taking two @ref PosIdx indices as input and returning true if and only if + * the birth associated to the first position is strictly less than birth associated to + * the second one with respect to some self defined order. It is used while swapping two unpaired or + * two negative columns. + * @param deathComparator Method taking two @ref PosIdx indices as input and returning true if and only if + * the death associated to the first position is strictly less than death associated to + * the second one with respect to some self defined order. It is used while swapping two positive but paired + * columns. + */ + template + Id_to_index_overlay(Column_settings* colSettings, + const BirthComparatorFunction& birthComparator, + const DeathComparatorFunction& deathComparator); + /** + * @brief Only available for @ref chainmatrix "chain matrices". + * Constructs a new matrix from the given ranges of @ref Matrix::cell_rep_type. Each range corresponds to a column + * (the order of the ranges are preserved). The content of the ranges is assumed to be sorted by increasing IDs. + * The IDs of the simplices are also assumed to be consecutifs, ordered by filtration value, starting with 0. + * + * @warning If @ref PersistenceMatrixOptions::has_vine_update is false, the comparators are not used. + * And if @ref PersistenceMatrixOptions::has_vine_update is true, but + * @ref PersistenceMatrixOptions::has_column_pairings is also true, the comparators are ignored and + * the current barcode is used to compare birth and deaths. Therefore it is useless to provide them in those cases. + * + * @tparam BirthComparatorFunction Type of the birth comparator: (@ref pos_index, @ref pos_index) -> bool + * @tparam DeathComparatorFunction Type of the death comparator: (@ref pos_index, @ref pos_index) -> bool + * @tparam Boundary_type Range type for @ref Matrix::cell_rep_type ranges. + * Assumed to have a begin(), end() and size() method. + * @param orderedBoundaries Range of boundaries: @p orderedBoundaries is interpreted as a boundary matrix of a + * filtered **simplicial** complex, whose boundaries are ordered by filtration order. + * Therefore, `orderedBoundaries[i]` should store the boundary of the \f$ i^{th} \f$ simplex in the filtration, + * as an ordered list of indices of its facets (again those indices correspond to their respective position + * in the matrix). That is why the indices of the simplices are assumed to be consecutifs and starting with 0 + * (an empty boundary is interpreted as a vertex boundary and not as a non existing simplex). + * All dimensions up to the maximal dimension of interest have to be present. If only a higher dimension is of + * interest and not everything should be stored, then use the @ref insert_boundary method instead + * (after creating the matrix with the @ref Id_to_index_overlay(unsigned int, Column_settings*, + * const BirthComparatorFunction&, const DeathComparatorFunction&) constructor preferably). + * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all + * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * @param birthComparator Method taking two @ref PosIdx indices as input and returning true if and only if + * the birth associated to the first position is strictly less than birth associated to + * the second one with respect to some self defined order. It is used while swapping two unpaired or + * two negative columns. + * @param deathComparator Method taking two @ref PosIdx indices as input and returning true if and only if + * the death associated to the first position is strictly less than death associated to + * the second one with respect to some self defined order. It is used while swapping two positive but paired + * columns. + */ + template + Id_to_index_overlay(const std::vector& orderedBoundaries, + Column_settings* colSettings, + const BirthComparatorFunction& birthComparator, + const DeathComparatorFunction& deathComparator); + /** + * @brief Only available for @ref chainmatrix "chain matrices". + * Constructs a new empty matrix and reserves space for the given number of columns. + * + * @warning If @ref PersistenceMatrixOptions::has_vine_update is false, the comparators are not used. + * And if @ref PersistenceMatrixOptions::has_vine_update is true, but + * @ref PersistenceMatrixOptions::has_column_pairings is also true, the comparators are ignored and + * the current barcode is used to compare birth and deaths. Therefore it is useless to provide them in those cases. + * + * @tparam BirthComparatorFunction Type of the birth comparator: (@ref pos_index, @ref pos_index) -> bool + * @tparam DeathComparatorFunction Type of the death comparator: (@ref pos_index, @ref pos_index) -> bool + * @param numberOfColumns Number of columns to reserve space for. + * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all + * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * @param birthComparator Method taking two @ref PosIdx indices as input and returning true if and only if + * the birth associated to the first position is strictly less than birth associated to + * the second one with respect to some self defined order. It is used while swapping two unpaired or + * two negative columns. + * @param deathComparator Method taking two @ref PosIdx indices as input and returning true if and only if + * the death associated to the first position is strictly less than death associated to + * the second one with respect to some self defined order. It is used while swapping two positive but paired + * columns. + */ + template + Id_to_index_overlay(unsigned int numberOfColumns, + Column_settings* colSettings, + const BirthComparatorFunction& birthComparator, + const DeathComparatorFunction& deathComparator); + /** + * @brief Copy constructor. If @p operators or @p cellConstructor is not a null pointer, its value is kept + * instead of the one in the copied matrix. + * + * @param matrixToCopy Matrix to copy. + * @param colSettings Either a pointer to an existing setting structure for the columns or a null pointer. + * The structure should contain all the necessary external classes specifically necessary for the choosen column type, + * such as custom allocators. If null pointer, the pointer stored in @p matrixToCopy is used instead. + */ + Id_to_index_overlay(const Id_to_index_overlay& matrixToCopy, + Column_settings* colSettings = nullptr); + /** + * @brief Move constructor. + * + * @param other Matrix to move. + */ + Id_to_index_overlay(Id_to_index_overlay&& other) noexcept; + /** + * @brief Destructor. + */ + ~Id_to_index_overlay(); + + /** + * @brief Inserts at the end of the matrix a new ordered column corresponding to the given boundary. + * This means that it is assumed that this method is called on boundaries in the order of the filtration. + * It also assumes that the faces in the given boundary are identified by their relative position in the filtration, + * starting at 0. If it is not the case, use the other + * @ref insert_boundary(id_index, const Boundary_type&, dimension_type) "insert_boundary" instead by indicating the + * face ID used in the boundaries when the face is inserted. + * + * Different to the constructor, the boundaries do not have to come from a simplicial complex, but also from + * a more general cell complex. This includes cubical complexes or Morse complexes for example. + * + * The content of the new column will vary depending on the underlying @ref mp_matrices "type of the matrix": + * - If it is a boundary type matrix and only \f$ R \f$ is stored, the boundary is just copied. The column will only + * be reduced later when the barcode is requested in order to apply some optimisations with the additional + * knowledge. Hence, the barcode will also not be updated. + * - If it is a boundary type matrix and both \f$ R \f$ and \f$ U \f$ are stored, the new boundary is stored in its + * reduced form and the barcode, if active, is also updated. + * - If it is a chain type matrix, the new column is of the form + * `IDIdx + linear combination of older column IDIdxs`, where the combination is deduced while reducing the + * given boundary. If the barcode is stored, it will also be updated. + * + * @tparam Boundary_type Range of @ref Matrix::cell_rep_type. Assumed to have a begin(), end() and size() method. + * @param boundary Boundary generating the new column. The content should be ordered by ID. + * @param dim Dimension of the face whose boundary is given. If the complex is simplicial, + * this parameter can be omitted as it can be deduced from the size of the boundary. + */ + template + void insert_boundary(const Boundary_type& boundary, dimension_type dim = -1); + /** + * @brief It does the same as the other version, but allows the boundary faces to be identified without restrictions + * except that all IDs have to be strictly increasing in the order of filtration. Note that you should avoid then + * to use the other insertion method to avoid overwriting IDs. + * + * As a face has to be inserted before one of its cofaces in a valid filtration (recall that it is assumed that + * the faces are inserted by order of filtration), it is sufficient to indicate the ID of the face being inserted. + * + * @tparam Boundary_type Range of @ref Matrix::cell_rep_type. Assumed to have a begin(), end() and size() method. + * @param faceIndex @ref IDIdx index to use to indentify the new face. + * @param boundary Boundary generating the new column. The indices of the boundary have to correspond to the + * @p faceIndex values of precedent calls of the method for the corresponding faces and should be ordered in + * increasing order. + * @param dim Dimension of the face whose boundary is given. If the complex is simplicial, + * this parameter can be omitted as it can be deduced from the size of the boundary. + */ + template + void insert_boundary(id_index faceIndex, const Boundary_type& boundary, dimension_type dim = -1); + /** + * @brief Returns the column at the given @ref IDIdx index. + * For @ref boundarymatrix "RU matrices", the returned column is from \f$ R \f$. + * The type of the column depends on the choosen options, see @ref PersistenceMatrixOptions::column_type. + * + * @param faceID @ref IDIdx index of the column to return. + * @return Reference to the column. + */ + Column_type& get_column(id_index faceID); + /** + * @brief Only available if @ref PersistenceMatrixOptions::has_row_access is true. + * Returns the row at the given @ref rowindex "row index". + * For @ref boundarymatrix "RU matrices", the returned row is from \f$ R \f$. + * The type of the row depends on the choosen options, see @ref PersistenceMatrixOptions::has_intrusive_rows. + * + * @warning The @ref get_column_index method of the row cells returns the original @ref PosIdx indices (before any + * swaps) for @ref boundarymatrix "boundary matrices" and @ref MatIdx indices for @ref chainmatrix "chain matrices". + * + * @param rowIndex @ref rowindex "Row index" of the row to return: @ref IDIdx for @ref chainmatrix "chain matrices" + * or updated @ref IDIdx for @ref boundarymatrix "boundary matrices" if swaps occured. + * @return Reference to the row. + */ + Row_type& get_row(id_index rowIndex); + /** + * @brief The effect varies depending on the matrices and the options: + * - @ref boundarymatrix "boundary matrix" with only \f$ R \f$ stored: + * - @ref PersistenceMatrixOptions::has_map_column_container and + * @ref PersistenceMatrixOptions::has_column_and_row_swaps are true: + * cleans up maps used for the lazy row swaps. + * - @ref PersistenceMatrixOptions::has_row_access and @ref PersistenceMatrixOptions::has_removable_rows are true: + * assumes that the row is empty and removes it. + * - Otherwise, does nothing. + * - @ref boundarymatrix "boundary matrix" with \f$ U \f$ stored: only \f$ R \f$ is affected by the above. + * If properly used, \f$ U \f$ will never have empty rows. + * - @ref chainmatrix "chain matrix": only available if @ref PersistenceMatrixOptions::has_row_access and + * @ref PersistenceMatrixOptions::has_removable_rows are true. + * Assumes that the row is empty and removes it. + * + * @warning The removed rows are always assumed to be empty. If it is not the case, the deleted row cells are not + * removed from their columns. And in the case of intrusive rows, this will generate a segmentation fault when + * the column cells are destroyed later. The row access is just meant as a "read only" access to the rows and the + * @ref erase_empty_row method just as a way to specify that a row is empty and can therefore be removed from + * dictionnaries. This allows to avoid testing the emptiness of a row at each column cell removal, what can be + * quite frequent. + * + * @param rowIndex @ref rowindex "Row index" of the empty row to remove. + */ + void erase_empty_row(id_index rowIndex); + /** + * @brief Only available for RU and @ref chainmatrix "chain matrices" and if + * @ref PersistenceMatrixOptions::has_removable_columns and @ref PersistenceMatrixOptions::has_vine_update are true. + * For @ref chainmatrix "chain matrices", @ref PersistenceMatrixOptions::has_map_column_container and + * @ref PersistenceMatrixOptions::has_column_pairings also need to be true. + * Assumes that the face is maximal in the current complex and removes it such that the matrix remains consistent + * (i.e., RU is still an upper triangular decomposition of the @ref boundarymatrix "boundary matrix" and chain is + * still a compatible bases of the chain complex in the sense of @cite zigzag). + * The maximality of the face is not verified. + * Also updates the barcode if it was computed. + * + * For @ref chainmatrix "chain matrices", using the other version of the method could perform better depending on + * how the data is maintained on the side of the user. Then, @ref has_column_pairings also do not need to be true. + * + * See also @ref remove_last. + * + * @param faceID @ref IDIdx index of the face to remove. + */ + void remove_maximal_face(id_index faceID); + /** + * @brief Only available for @ref chainmatrix "chain matrices" and if + * @ref PersistenceMatrixOptions::has_removable_columns, @ref PersistenceMatrixOptions::has_vine_update + * and @ref PersistenceMatrixOptions::has_map_column_container are true. + * Assumes that the face is maximal in the current complex and removes it such that the matrix remains consistent + * (i.e., it is still a compatible bases of the chain complex in the sense of @cite zigzag). + * The maximality of the face is not verified. + * Also updates the barcode if it was computed. + * + * To maintain the compatibility, vine swaps are done to move the face up to the end of the filtration. Once at + * the end, the removal is trivial. But for @ref chainmatrix "chain matrices", swaps do not actually swap the position + * of the column every time, so the faces appearing after @p faceIndex in the filtration have to be searched first + * within the matrix. If the user has an easy access to the @ref IDIdx of the faces in the order of filtration, + * passing them by argument with @p columnsToSwap allows to skip a linear search process. Typically, if the user knows + * that the face he wants to remove is already the last face of the filtration, calling + * @ref remove_maximal_face(id_index, const std::vector&) "remove_maximal_face(faceID, {})" + * will be faster than @ref remove_last(). + * + * See also @ref remove_last. + * + * @param faceID @ref IDIdx index of the face to remove. + * @param columnsToSwap Vector of @ref IDIdx indices of the faces coming after @p faceID in the filtration. + */ + void remove_maximal_face(id_index faceID, const std::vector& columnsToSwap); + /** + * @brief Only available if @ref PersistenceMatrixOptions::has_removable_columns is true. Additionnaly, if the + * matrix is a @ref chainmatrix "chain matrix", either @ref PersistenceMatrixOptions::has_map_column_container has to + * be true or @ref PersistenceMatrixOptions::has_vine_update has to be false. + * Removes the last face in the filtration from the matrix and updates the barcode if it is stored. + * + * See also @ref remove_maximal_face. + * + * For @ref chainmatrix "chain matrices", if @ref PersistenceMatrixOptions::has_vine_update is true, the last face + * does not have to be at the end of the matrix and therefore has to be searched first. In this case, if the user + * already knows the @ref IDIdx of the last face, calling + * @ref remove_maximal_face(id_index, const std::vector&) "remove_maximal_face(faceID, {})" + * instead allows to skip the search. + */ + void remove_last(); + + /** + * @brief Returns the maximal dimension of a face stored in the matrix. Only available + * if @ref PersistenceMatrixOptions::has_matrix_maximal_dimension_access is true. + * + * @return The maximal dimension. + */ + dimension_type get_max_dimension() const; + /** + * @brief Returns the current number of columns in the matrix. + * + * @return The number of columns. + */ + index get_number_of_columns() const; + /** + * @brief Returns the dimension of the given face. Only available for @ref mp_matrices "non-basic matrices". + * + * @param faceID @ref IDIdx index of the face. + * @return Dimension of the face. + */ + dimension_type get_column_dimension(id_index faceID) const; + + /** + * @brief Adds column corresponding to @p sourceFaceID onto the column corresponding to @p targetFaceID. + * + * @warning They will be no verification to ensure that the addition makes sense for the validity of the matrix. + * For example, a right-to-left addition could corrupt the computation of the barcode if done blindly. + * So should be used with care. + * + * @param sourceFaceID @ref IDIdx index of the source column. + * @param targetFaceID @ref IDIdx index of the target column. + */ + void add_to(id_index sourceFaceID, id_index targetFaceID); + /** + * @brief Multiplies the target column with the coefficiant and then adds the source column to it. + * That is: `targetColumn = (targetColumn * coefficient) + sourceColumn`. + * + * @warning They will be no verification to ensure that the addition makes sense for the validity of the matrix. + * For example, a right-to-left addition could corrupt the computation of the barcode if done blindly. + * So should be used with care. + * + * @param sourceFaceID @ref IDIdx index of the source column. + * @param coefficient Value to multiply. + * @param targetFaceID @ref IDIdx index of the target column. + */ + void multiply_target_and_add_to(id_index sourceFaceID, const Field_element_type& coefficient, id_index targetFaceID); + /** + * @brief Multiplies the source column with the coefficiant before adding it to the target column. + * That is: `targetColumn += (coefficient * sourceColumn)`. The source column will **not** be modified. + * + * @warning They will be no verification to ensure that the addition makes sense for the validity of the matrix. + * For example, a right-to-left addition could corrupt the computation of the barcode if done blindly. + * So should be used with care. + * + * @param coefficient Value to multiply. + * @param sourceFaceID @ref IDIdx index of the source column. + * @param targetFaceID @ref IDIdx index of the target column. + */ + void multiply_source_and_add_to(const Field_element_type& coefficient, id_index sourceFaceID, id_index targetFaceID); + + /** + * @brief Zeroes the cell at the given coordinates. Not available for @ref chainmatrix "chain matrices". + * In general, should be used with care to not destroy the validity + * of the persistence related properties of the matrix. + * + * For @ref boundarymatrix "RU matrices", zeros only the cell in \f$ R \f$. + * + * @param faceID @ref IDIdx index of the face corresponding to the column of the cell. + * @param rowIndex @ref rowindex "Row index" of the row of the cell. + */ + void zero_cell(id_index faceID, id_index rowIndex); + /** + * @brief Zeroes the column at the given index. Not available for @ref chainmatrix "chain matrices". + * In general, should be used with care to not destroy the validity + * of the persistence related properties of the matrix. + * + * For @ref boundarymatrix "RU matrices", zeros only the column in \f$ R \f$. + * + * @param faceID @ref IDIdx index of the face corresponding to the column. + */ + void zero_column(id_index faceID); + /** + * @brief Indicates if the cell at given coordinates has value zero. + * + * For @ref boundarymatrix "RU matrices", looks into \f$ R \f$. + * + * @param faceID @ref IDIdx index of the face corresponding to the column of the cell. + * @param rowIndex @ref rowindex "Row index" of the row of the cell. + * @return true If the cell has value zero. + * @return false Otherwise. + */ + bool is_zero_cell(id_index faceID, id_index rowIndex) const; + /** + * @brief Indicates if the column at given index has value zero. + * + * For @ref boundarymatrix "RU matrices", looks into \f$ R \f$. + * + * Note that for @ref chainmatrix "chain matrices", this method should always return false, as a valid + * @ref chainmatrix "chain matrix" never has empty columns. + * + * @param faceID @ref IDIdx index of the face corresponding to the column. + * @return true If the column has value zero. + * @return false Otherwise. + */ + bool is_zero_column(id_index faceID); + + /** + * @brief Returns the @ref IDIdx index of the column which has the given @ref rowindex "row index" as pivot. + * Assumes that the pivot exists. For @ref boundarymatrix "RU matrices", the column is returned from \f$ R \f$. + * + * Recall that the row indices for @ref chainmatrix "chain matrices" correspond to the @ref IDIdx indices and that + * the row indices for a @ref boundarymatrix "RU matrix" correspond to the updated @ref IDIdx indices which got + * potentially swapped by a vine swap. + * + * @param faceIndex @ref rowindex "Row index" of the pivot. + * @return @ref IDIdx index of the column with the given pivot. + */ + id_index get_column_with_pivot(id_index faceIndex) const; + /** + * @brief Returns the @ref rowindex "row index" of the pivot of the given column. + * + * @param faceID @ref IDIdx index of the face corresponding to the column. + * @return The @ref rowindex "row index" of the pivot. + */ + id_index get_pivot(id_index faceID); + + /** + * @brief Resets the matrix to an empty matrix. + * + * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all + * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + */ + void reset(Column_settings* colSettings) { + matrix_.reset(colSettings); + nextIndex_ = 0; + } + + // void set_operators(Field_operators* operators) { matrix_.set_operators(operators); } + + /** + * @brief Assign operator. + */ + Id_to_index_overlay& operator=(const Id_to_index_overlay& other); + /** + * @brief Swap operator. + */ + friend void swap(Id_to_index_overlay& matrix1, Id_to_index_overlay& matrix2) { + swap(matrix1.matrix_, matrix2.matrix_); + if (Master_matrix_type::Option_list::is_of_boundary_type) std::swap(matrix1.idToIndex_, matrix2.idToIndex_); + std::swap(matrix1.nextIndex_, matrix2.nextIndex_); + } + + void print(); // for debug + + // access to optionnal methods + + /** + * @brief Returns the current barcode of the matrix. + * Available only if @ref PersistenceMatrixOptions::has_column_pairings is true. + * + * Recall that we assume that the boundaries were inserted in the order of filtration for the barcode to be valid. + * + * @warning For simple @ref boundarymatrix "boundary matrices" (only storing \f$ R \f$), we assume that + * @ref get_current_barcode is only called once, when the matrix is completed. + * + * @return A reference to the barcode. The barcode is a vector of @ref Matrix::Bar. A bar stores three informations: + * the @ref PosIdx birth index, the @ref PosIdx death index and the dimension of the bar. + */ + const barcode_type& get_current_barcode(); + + /** + * @brief Only available for simple @ref boundarymatrix "boundary matrices" (only storing \f$ R \f$) and if + * @ref PersistenceMatrixOptions::has_column_and_row_swaps is true. + * Swaps the two given columns. Note that it really just swaps two columns and do not updates + * anything else, nor performs additions to maintain some properties on the matrix. + * + * @param faceID1 First column @ref IDIdx index to swap. + * @param faceID2 Second column @ref IDIdx index to swap. + */ + void swap_columns(id_index faceID1, id_index faceID2); + /** + * @brief Only available for simple @ref boundarymatrix "boundary matrices" (only storing R) + * and if @ref PersistenceMatrixOptions::has_column_and_row_swaps is true. + * Swaps the two given rows. Note that it really just swaps two rows and do not updates + * anything else, nor performs additions to maintain some properties on the matrix. + * + * @param rowIndex1 First @ref rowindex "row index" to swap. + * @param rowIndex2 Second @ref rowindex "row index" to swap. + */ + void swap_rows(index rowIndex1, index rowIndex2); + /** + * @brief Only available if @ref PersistenceMatrixOptions::has_vine_update is true. + * Does the same than @ref vine_swap, but assumes that the swap is non trivial and + * therefore skips a part of the case study. + * + * @param faceID1 @ref IDIdx index of the first face. + * @param faceID2 @ref IDIdx index of the second face. It is assumed that the @ref PosIdx of both only differs by one. + * @return Let \f$ pos1 \f$ be the @ref PosIdx index of @p columnIndex1 and \f$ pos2 \f$ be the @ref PosIdx index of + * @p columnIndex2. The method returns the @ref MatIdx of the column which has now, after the swap, the @ref PosIdx + * \f$ max(pos1, pos2) \f$. + */ + id_index vine_swap_with_z_eq_1_case(id_index faceID1, id_index faceID2); + /** + * @brief Only available if @ref PersistenceMatrixOptions::has_vine_update is true. + * Does a vine swap between two faces which are consecutives in the filtration. Roughly, if \f$ F \f$ is + * the current filtration represented by the matrix, the method modifies the matrix such that the new state + * corresponds to a valid state for the filtration \f$ F' \f$ equal to \f$ F \f$ but with the two given faces + * at swapped positions. Of course, the two faces should not have a face/coface relation which each other ; + * \f$ F' \f$ has to be a valid filtration. + * See @cite vineyards for more information about vine and vineyards. + * + * @param faceID1 @ref IDIdx index of the first face. + * @param faceID2 @ref IDIdx index of the second face. It is assumed that the @ref PosIdx of both only differs by one. + * @return Let \f$ pos1 \f$ be the @ref PosIdx index of @p columnIndex1 and \f$ pos2 \f$ be the @ref PosIdx index of + * @p columnIndex2. The method returns the @ref MatIdx of the column which has now, after the swap, the @ref PosIdx + * \f$ max(pos1, pos2) \f$. + */ + id_index vine_swap(id_index faceID1, id_index faceID2); + + /** + * @brief Only available if @ref PersistenceMatrixOptions::can_retrieve_representative_cycles is true. Precomputes the + * representative cycles of the current state of the filtration represented by the matrix. + * It does not need to be called before `get_representative_cycles` is called for the first time, but needs to be + * called before calling `get_representative_cycles` again if the matrix was modified in between. Otherwise the + * old cycles will be returned. + */ + void update_representative_cycles(); + /** + * @brief Only available if @ref PersistenceMatrixOptions::can_retrieve_representative_cycles is true. + * Returns all representative cycles of the current filtration. + * + * @return A const reference to the vector of representative cycles. + */ + const std::vector& get_representative_cycles(); + /** + * @brief Only available if @ref PersistenceMatrixOptions::can_retrieve_representative_cycles is true. + * Returns the cycle representing the given bar. + * + * @param bar A bar from the current barcode. + * @return A const reference to the cycle representing @p bar. + */ + const cycle_type& get_representative_cycle(const bar_type& bar); + + private: + using dictionnary_type = typename Master_matrix_type::template dictionnary_type; + + Matrix_type matrix_; /**< Interfaced matrix. */ + dictionnary_type* idToIndex_; /**< Map from @ref IDIdx index to @ref MatIdx index. */ + index nextIndex_; /**< Next unused index. */ + + void _initialize_map(unsigned int size); + index _id_to_index(id_index id) const; +}; + +template +inline Id_to_index_overlay::Id_to_index_overlay(Column_settings* colSettings) + : matrix_(colSettings), idToIndex_(nullptr), nextIndex_(0) +{ + _initialize_map(0); +} + +template +template +inline Id_to_index_overlay::Id_to_index_overlay( + const std::vector& orderedBoundaries, Column_settings* colSettings) + : matrix_(orderedBoundaries, colSettings), idToIndex_(nullptr), nextIndex_(orderedBoundaries.size()) +{ + _initialize_map(orderedBoundaries.size()); + if constexpr (Master_matrix_type::Option_list::is_of_boundary_type) { + for (unsigned int i = 0; i < orderedBoundaries.size(); i++) { + idToIndex_->operator[](i) = i; + } + } +} + +template +inline Id_to_index_overlay::Id_to_index_overlay(unsigned int numberOfColumns, + Column_settings* colSettings) + : matrix_(numberOfColumns, colSettings), idToIndex_(nullptr), nextIndex_(0) +{ + _initialize_map(numberOfColumns); +} + +template +template +inline Id_to_index_overlay::Id_to_index_overlay( + Column_settings* colSettings, + const BirthComparatorFunction& birthComparator, + const DeathComparatorFunction& deathComparator) + : matrix_(colSettings, birthComparator, deathComparator), idToIndex_(nullptr), nextIndex_(0) +{ + _initialize_map(0); +} + +template +template +inline Id_to_index_overlay::Id_to_index_overlay( + const std::vector& orderedBoundaries, + Column_settings* colSettings, + const BirthComparatorFunction& birthComparator, + const DeathComparatorFunction& deathComparator) + : matrix_(orderedBoundaries, colSettings, birthComparator, deathComparator), + idToIndex_(nullptr), + nextIndex_(orderedBoundaries.size()) +{ + _initialize_map(orderedBoundaries.size()); + if constexpr (Master_matrix_type::Option_list::is_of_boundary_type) { + for (unsigned int i = 0; i < orderedBoundaries.size(); i++) { + idToIndex_->operator[](i) = i; + } + } +} + +template +template +inline Id_to_index_overlay::Id_to_index_overlay( + unsigned int numberOfColumns, + Column_settings* colSettings, + const BirthComparatorFunction& birthComparator, + const DeathComparatorFunction& deathComparator) + : matrix_(numberOfColumns, colSettings, birthComparator, deathComparator), + idToIndex_(nullptr), + nextIndex_(0) +{ + _initialize_map(numberOfColumns); +} + +template +inline Id_to_index_overlay::Id_to_index_overlay( + const Id_to_index_overlay& matrixToCopy, Column_settings* colSettings) + : matrix_(matrixToCopy.matrix_, colSettings), + idToIndex_(nullptr), + nextIndex_(matrixToCopy.nextIndex_) +{ + if constexpr (Master_matrix_type::Option_list::is_of_boundary_type) { + idToIndex_ = new dictionnary_type(*matrixToCopy.idToIndex_); + } else { + idToIndex_ = &matrix_.pivotToColumnIndex_; + } +} + +template +inline Id_to_index_overlay::Id_to_index_overlay(Id_to_index_overlay&& other) noexcept + : matrix_(std::move(other.matrix_)), + idToIndex_(std::exchange(other.idToIndex_, nullptr)), + nextIndex_(std::exchange(other.nextIndex_, 0)) +{} + +template +inline Id_to_index_overlay::~Id_to_index_overlay() +{ + if constexpr (Master_matrix_type::Option_list::is_of_boundary_type) { + if (idToIndex_ != nullptr) delete idToIndex_; + } +} + +template +template +inline void Id_to_index_overlay::insert_boundary(const Boundary_type& boundary, + dimension_type dim) +{ + matrix_.insert_boundary(boundary, dim); + if constexpr (Master_matrix_type::Option_list::is_of_boundary_type) { + if constexpr (Master_matrix_type::Option_list::has_map_column_container) { + idToIndex_->emplace(nextIndex_, nextIndex_); + } else { + if (idToIndex_->size() == nextIndex_) { + idToIndex_->push_back(nextIndex_); + } else { + idToIndex_->operator[](nextIndex_) = nextIndex_; + } + } + ++nextIndex_; + } +} + +template +template +inline void Id_to_index_overlay::insert_boundary(id_index faceIndex, + const Boundary_type& boundary, + dimension_type dim) +{ + if constexpr (Master_matrix_type::Option_list::has_map_column_container) { + GUDHI_CHECK(idToIndex_->find(faceIndex) == idToIndex_->end(), + std::invalid_argument("Id_to_index_overlay::insert_boundary - Index for simplex already chosen!")); + } else { + GUDHI_CHECK((idToIndex_->size() <= faceIndex || idToIndex_[faceIndex] == static_cast(-1)), + std::invalid_argument("Id_to_index_overlay::insert_boundary - Index for simplex already chosen!")); + } + matrix_.insert_boundary(faceIndex, boundary, dim); + if constexpr (Master_matrix_type::Option_list::is_of_boundary_type) { + if constexpr (Master_matrix_type::Option_list::has_map_column_container) { + idToIndex_->emplace(faceIndex, nextIndex_); + } else { + if (idToIndex_->size() <= faceIndex) { + idToIndex_->resize(faceIndex + 1, -1); + } + idToIndex_->operator[](faceIndex) = nextIndex_; + } + ++nextIndex_; + } +} + +template +inline typename Id_to_index_overlay::Column_type& +Id_to_index_overlay::get_column(id_index faceID) +{ + return matrix_.get_column(_id_to_index(faceID)); +} + +template +inline typename Id_to_index_overlay::Row_type& +Id_to_index_overlay::get_row(id_index rowIndex) +{ + return matrix_.get_row(rowIndex); +} + +template +inline void Id_to_index_overlay::erase_empty_row(id_index rowIndex) +{ + return matrix_.erase_empty_row(rowIndex); +} + +template +inline void Id_to_index_overlay::remove_maximal_face(id_index faceID) +{ + if constexpr (Master_matrix_type::Option_list::is_of_boundary_type) { + std::vector indexToID(nextIndex_); + if constexpr (Master_matrix_type::Option_list::has_map_column_container) { + for (auto& p : *idToIndex_) { + indexToID[p.second] = p.first; + } + } else { + for (id_index i = 0; i < idToIndex_->size(); ++i) { + if (idToIndex_->operator[](i) != static_cast(-1)) indexToID[idToIndex_->operator[](i)] = i; + } + } + --nextIndex_; + for (index curr = _id_to_index(faceID); curr < nextIndex_; ++curr) { + matrix_.vine_swap(curr); + std::swap(idToIndex_->at(indexToID[curr]), idToIndex_->at(indexToID[curr + 1])); + } + matrix_.remove_last(); + GUDHI_CHECK(_id_to_index(faceID) == nextIndex_, + std::logic_error("Id_to_index_overlay::remove_maximal_face - Indexation problem.")); + + if constexpr (Master_matrix_type::Option_list::has_map_column_container) { + idToIndex_->erase(faceID); + } else { + idToIndex_->operator[](faceID) = -1; + } + } else { + matrix_.remove_maximal_face(faceID); + } +} + +template +inline void Id_to_index_overlay::remove_maximal_face( + id_index faceID, const std::vector& columnsToSwap) +{ + static_assert(!Master_matrix_type::Option_list::is_of_boundary_type, + "'remove_maximal_face(id_index,const std::vector&)' is not available for the chosen options."); + std::vector translatedIndices; + std::transform(columnsToSwap.cbegin(), columnsToSwap.cend(), std::back_inserter(translatedIndices), + [&](id_index id) { return _id_to_index(id); }); + matrix_.remove_maximal_face(faceID, translatedIndices); +} + +template +inline void Id_to_index_overlay::remove_last() +{ + if (idToIndex_->empty()) return; //empty matrix + + matrix_.remove_last(); + + if constexpr (Master_matrix_type::Option_list::is_of_boundary_type) { + --nextIndex_; + if constexpr (Master_matrix_type::Option_list::has_map_column_container) { + auto it = idToIndex_->begin(); + while (it->second != nextIndex_) ++it; //should never reach idToIndex_->end() + idToIndex_->erase(it); + } else { + index id = idToIndex_->size() - 1; + while (idToIndex_->operator[](id) == static_cast(-1)) --id; // should always stop before reaching -1 + GUDHI_CHECK(idToIndex_->operator[](id) == nextIndex_, + std::logic_error("Id_to_index_overlay::remove_last - Indexation problem.")); + idToIndex_->operator[](id) = -1; + } + } +} + +template +inline typename Id_to_index_overlay::dimension_type +Id_to_index_overlay::get_max_dimension() const +{ + return matrix_.get_max_dimension(); +} + +template +inline typename Id_to_index_overlay::index +Id_to_index_overlay::get_number_of_columns() const +{ + return matrix_.get_number_of_columns(); +} + +template +inline typename Id_to_index_overlay::dimension_type +Id_to_index_overlay::get_column_dimension(id_index faceID) const +{ + return matrix_.get_column_dimension(_id_to_index(faceID)); +} + +template +inline void Id_to_index_overlay::add_to(id_index sourceFaceID, id_index targetFaceID) +{ + return matrix_.add_to(_id_to_index(sourceFaceID), _id_to_index(targetFaceID)); +} + +template +inline void Id_to_index_overlay::multiply_target_and_add_to( + id_index sourceFaceID, const Field_element_type& coefficient, id_index targetFaceID) +{ + return matrix_.multiply_target_and_add_to(_id_to_index(sourceFaceID), coefficient, _id_to_index(targetFaceID)); +} + +template +inline void Id_to_index_overlay::multiply_source_and_add_to( + const Field_element_type& coefficient, id_index sourceFaceID, id_index targetFaceID) +{ + return matrix_.multiply_source_and_add_to(coefficient, _id_to_index(sourceFaceID), _id_to_index(targetFaceID)); +} + +template +inline void Id_to_index_overlay::zero_cell(id_index faceID, id_index rowIndex) +{ + return matrix_.zero_cell(_id_to_index(faceID), rowIndex); +} + +template +inline void Id_to_index_overlay::zero_column(id_index faceID) +{ + return matrix_.zero_column(_id_to_index(faceID)); +} + +template +inline bool Id_to_index_overlay::is_zero_cell(id_index faceID, + id_index rowIndex) const +{ + return matrix_.is_zero_cell(_id_to_index(faceID), rowIndex); +} + +template +inline bool Id_to_index_overlay::is_zero_column(id_index faceID) +{ + return matrix_.is_zero_column(_id_to_index(faceID)); +} + +template +inline typename Id_to_index_overlay::id_index +Id_to_index_overlay::get_column_with_pivot(id_index simplexIndex) const +{ + if constexpr (Master_matrix_type::Option_list::is_of_boundary_type) { + index pos = matrix_.get_column_with_pivot(simplexIndex); + id_index i = 0; + while (_id_to_index(i) != pos) ++i; + return i; + } else { + return simplexIndex; + } +} + +template +inline typename Id_to_index_overlay::id_index +Id_to_index_overlay::get_pivot(id_index faceID) +{ + if constexpr (Master_matrix_type::Option_list::is_of_boundary_type) { + return matrix_.get_pivot(_id_to_index(faceID)); + } else { + return faceID; + } +} + +template +inline Id_to_index_overlay& +Id_to_index_overlay::operator=(const Id_to_index_overlay& other) +{ + matrix_ = other.matrix_; + if (Master_matrix_type::Option_list::is_of_boundary_type) + idToIndex_ = other.idToIndex_; + else + idToIndex_ = &matrix_.pivotToColumnIndex_; + nextIndex_ = other.nextIndex_; + + return *this; +} + +template +inline void Id_to_index_overlay::print() +{ + return matrix_.print(); +} + +template +inline const typename Id_to_index_overlay::barcode_type& +Id_to_index_overlay::get_current_barcode() +{ + return matrix_.get_current_barcode(); +} + +template +inline void Id_to_index_overlay::update_representative_cycles() +{ + matrix_.update_representative_cycles(); +} + +template +inline const std::vector::cycle_type>& +Id_to_index_overlay::get_representative_cycles() +{ + return matrix_.get_representative_cycles(); +} + +template +inline const typename Id_to_index_overlay::cycle_type& +Id_to_index_overlay::get_representative_cycle(const bar_type& bar) +{ + return matrix_.get_representative_cycle(bar); +} + +template +inline void Id_to_index_overlay::swap_columns(id_index faceID1, id_index faceID2) +{ + matrix_.swap_columns(_id_to_index(faceID1), _id_to_index(faceID2)); + std::swap(idToIndex_->at(faceID1), idToIndex_->at(faceID2)); +} + +template +inline void Id_to_index_overlay::swap_rows(index rowIndex1, index rowIndex2) +{ + matrix_.swap_rows(rowIndex1, rowIndex2); +} + +template +inline typename Id_to_index_overlay::id_index +Id_to_index_overlay::vine_swap_with_z_eq_1_case(id_index faceID1, id_index faceID2) +{ + index first = _id_to_index(faceID1); + index second = _id_to_index(faceID2); + if (first > second) std::swap(first, second); + + if constexpr (Master_matrix_type::Option_list::is_of_boundary_type) { + GUDHI_CHECK(second - first == 1, + std::invalid_argument( + "Id_to_index_overlay::vine_swap_with_z_eq_1_case - The columns to swap are not contiguous.")); + + bool change = matrix_.vine_swap_with_z_eq_1_case(first); + + std::swap(idToIndex_->at(faceID1), idToIndex_->at(faceID2)); + + if (change) { + return faceID1; + } + return faceID2; + } else { + return matrix_.vine_swap_with_z_eq_1_case(first, second); + } +} + +template +inline typename Id_to_index_overlay::id_index +Id_to_index_overlay::vine_swap(id_index faceID1, id_index faceID2) +{ + index first = _id_to_index(faceID1); + index second = _id_to_index(faceID2); + if (first > second) std::swap(first, second); + + if constexpr (Master_matrix_type::Option_list::is_of_boundary_type) { + GUDHI_CHECK(second - first == 1, + std::invalid_argument("Id_to_index_overlay::vine_swap - The columns to swap are not contiguous.")); + + bool change = matrix_.vine_swap(first); + + std::swap(idToIndex_->at(faceID1), idToIndex_->at(faceID2)); + + if (change) { + return faceID1; + } + return faceID2; + } else { + return matrix_.vine_swap(first, second); + } +} + +template +inline void Id_to_index_overlay::_initialize_map([[maybe_unused]] unsigned int size) +{ + if constexpr (Master_matrix_type::Option_list::is_of_boundary_type) { + if constexpr (Master_matrix_type::Option_list::has_map_column_container) { + idToIndex_ = new dictionnary_type(size); + } else { + idToIndex_ = new dictionnary_type(size, -1); + } + } else { + idToIndex_ = &matrix_.pivotToColumnIndex_; + } +} + +template +inline typename Id_to_index_overlay::index +Id_to_index_overlay::_id_to_index(id_index id) const +{ + if constexpr (Master_matrix_type::Option_list::has_map_column_container) { + return idToIndex_->at(id); + } else { + return idToIndex_->operator[](id); + } +} + +} // namespace persistence_matrix +} // namespace Gudhi + +#endif // PM_ID_TO_POS_TRANSLATION_H diff --git a/src/Persistence_matrix/include/gudhi/Persistence_matrix/overlay_posidx_to_matidx.h b/src/Persistence_matrix/include/gudhi/Persistence_matrix/overlay_posidx_to_matidx.h new file mode 100644 index 0000000000..1b11d89bd6 --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Persistence_matrix/overlay_posidx_to_matidx.h @@ -0,0 +1,856 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file overlay_posidx_to_matidx.h + * @author Hannah Schreiber + * @brief Contains the @ref Position_to_index_overlay class. + */ + +#ifndef PM_POS_TO_ID_TRANSLATION_H +#define PM_POS_TO_ID_TRANSLATION_H + +#include +#include //std::swap, std::move & std::exchange +#include //std::transform + +namespace Gudhi { +namespace persistence_matrix { + +/** + * @class Position_to_index_overlay overlay_posidx_to_matidx.h gudhi/Persistence_matrix/overlay_posidx_to_matidx.h + * @ingroup persistence_matrix + * + * @brief Overlay for @ref chainmatrix "chain matrices" replacing all input and output @ref MatIdx indices of the + * original methods with @ref PosIdx indices. The overlay is useless for @ref boundarymatrix "boundary matrices" + * as @ref MatIdx == @ref PosIdx for them. + * + * @tparam %Matrix_type Matrix type taking the overlay. + * @tparam Master_matrix_type An instanciation of @ref Matrix from which all types and options are deduced. + */ +template +class Position_to_index_overlay +{ + public: + using index = typename Master_matrix_type::index; /**< @ref MatIdx index type. */ + using id_index = typename Master_matrix_type::id_index; /**< @ref IDIdx index type. */ + using pos_index = typename Master_matrix_type::pos_index; /**< @ref PosIdx index type. */ + using dimension_type = typename Master_matrix_type::dimension_type; /**< Dimension value type. */ + /** + * @brief Field operators class. Necessary only if @ref PersistenceMatrixOptions::is_z2 is false. + */ + using Field_operators = typename Master_matrix_type::Field_operators; + using Field_element_type = typename Master_matrix_type::element_type; /**< Type of an field element. */ + using boundary_type = typename Master_matrix_type::boundary_type; /**< Type of an input column. */ + using Column_type = typename Master_matrix_type::Column_type; /**< Column type. */ + using Row_type = typename Master_matrix_type::Row_type; /**< Row type, + only necessary with row access option. */ + using bar_type = typename Master_matrix_type::Bar; /**< Bar type. */ + using barcode_type = typename Master_matrix_type::barcode_type; /**< Barcode type. */ + using cycle_type = typename Master_matrix_type::cycle_type; /**< Cycle type. */ + using cell_rep_type = typename Master_matrix_type::cell_rep_type; /**< %Cell content representative. */ + using Cell_constructor = typename Master_matrix_type::Cell_constructor; /**< Factory of @ref Cell classes. */ + using Column_settings = typename Master_matrix_type::Column_settings; /**< Structure giving access to the columns to + necessary external classes. */ + + /** + * @brief Constructs an empty matrix. + * + * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all + * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + */ + Position_to_index_overlay(Column_settings* colSettings); + /** + * @brief Constructs a new matrix from the given ranges of @ref Matrix::cell_rep_type. Each range corresponds to a + * column (the order of the ranges are preserved). The content of the ranges is assumed to be sorted by increasing + * IDs. The IDs of the simplices are also assumed to be consecutifs, ordered by filtration value, starting with 0. + * + * @tparam Boundary_type Range type for @ref Matrix::cell_rep_type ranges. + * Assumed to have a begin(), end() and size() method. + * @param orderedBoundaries Range of boundaries: @p orderedBoundaries is interpreted as a boundary matrix of a + * filtered **simplicial** complex, whose boundaries are ordered by filtration order. + * Therefore, `orderedBoundaries[i]` should store the boundary of the \f$ i^{th} \f$ simplex in the filtration, + * as an ordered list of indices of its facets (again those indices correspond to their respective position + * in the matrix). That is why the indices of the simplices are assumed to be consecutifs and starting with 0 + * (an empty boundary is interpreted as a vertex boundary and not as a non existing simplex). + * All dimensions up to the maximal dimension of interest have to be present. If only a higher dimension is of + * interest and not everything should be stored, then use the @ref insert_boundary method instead (after creating the + * matrix with the @ref Position_to_index_overlay(unsigned int, Column_settings*) + * constructor preferably). + * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all + * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + */ + template + Position_to_index_overlay(const std::vector& orderedBoundaries, + Column_settings* colSettings); + /** + * @brief Constructs a new empty matrix and reserves space for the given number of columns. + * + * @param numberOfColumns Number of columns to reserve space for. + * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all + * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + */ + Position_to_index_overlay(unsigned int numberOfColumns, + Column_settings* colSettings); + /** + * @brief Only available for @ref chainmatrix "chain matrices". Constructs an empty matrix and stores the given + * comparators. + * + * @warning If @ref PersistenceMatrixOptions::has_vine_update is false, the comparators are not used. + * And if @ref PersistenceMatrixOptions::has_vine_update is true, but + * @ref PersistenceMatrixOptions::has_column_pairings is also true, the comparators are ignored and + * the current barcode is used to compare birth and deaths. Therefore it is useless to provide them in those cases. + * + * @tparam BirthComparatorFunction Type of the birth comparator: (@ref pos_index, @ref pos_index) -> bool + * @tparam DeathComparatorFunction Type of the death comparator: (@ref pos_index, @ref pos_index) -> bool + * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all + * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * @param birthComparator Method taking two @ref PosIdx indices as input and returning true if and only if + * the birth associated to the first position is strictly less than birth associated to + * the second one with respect to some self defined order. It is used while swapping two unpaired or + * two negative columns. + * @param deathComparator Method taking two @ref PosIdx indices as input and returning true if and only if + * the death associated to the first position is strictly less than death associated to + * the second one with respect to some self defined order. It is used while swapping two positive but paired + * columns. + */ + template + Position_to_index_overlay(Column_settings* colSettings, + const BirthComparatorFunction& birthComparator, + const DeathComparatorFunction& deathComparator); + /** + * @brief Only available for @ref chainmatrix "chain matrices". + * Constructs a new matrix from the given ranges of @ref Matrix::cell_rep_type. Each range corresponds to a column + * (the order of the ranges are preserved). The content of the ranges is assumed to be sorted by increasing IDs. + * The IDs of the simplices are also assumed to be consecutifs, ordered by filtration value, starting with 0. + * + * @warning If @ref PersistenceMatrixOptions::has_vine_update is false, the comparators are not used. + * And if @ref PersistenceMatrixOptions::has_vine_update is true, but + * @ref PersistenceMatrixOptions::has_column_pairings is also true, the comparators are ignored and + * the current barcode is used to compare birth and deaths. Therefore it is useless to provide them in those cases. + * + * @tparam BirthComparatorFunction Type of the birth comparator: (@ref pos_index, @ref pos_index) -> bool + * @tparam DeathComparatorFunction Type of the death comparator: (@ref pos_index, @ref pos_index) -> bool + * @tparam Boundary_type Range type for @ref Matrix::cell_rep_type ranges. + * Assumed to have a begin(), end() and size() method. + * @param orderedBoundaries Range of boundaries: @p orderedBoundaries is interpreted as a boundary matrix of a + * filtered **simplicial** complex, whose boundaries are ordered by filtration order. + * Therefore, `orderedBoundaries[i]` should store the boundary of the \f$ i^{th} \f$ simplex in the filtration, + * as an ordered list of indices of its facets (again those indices correspond to their respective position + * in the matrix). That is why the indices of the simplices are assumed to be consecutifs and starting with 0 + * (an empty boundary is interpreted as a vertex boundary and not as a non existing simplex). + * All dimensions up to the maximal dimension of interest have to be present. If only a higher dimension is of + * interest and not everything should be stored, then use the @ref insert_boundary method instead + * (after creating the matrix with the @ref Position_to_index_overlay(unsigned int, Column_settings*, + * const BirthComparatorFunction&, const DeathComparatorFunction&) constructor preferably). + * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all + * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * @param birthComparator Method taking two @ref PosIdx indices as input and returning true if and only if + * the birth associated to the first position is strictly less than birth associated to + * the second one with respect to some self defined order. It is used while swapping two unpaired or + * two negative columns. + * @param deathComparator Method taking two @ref PosIdx indices as input and returning true if and only if + * the death associated to the first position is strictly less than death associated to + * the second one with respect to some self defined order. It is used while swapping two positive but paired + * columns. + */ + template + Position_to_index_overlay(const std::vector& orderedBoundaries, + Column_settings* colSettings, + const BirthComparatorFunction& birthComparator, + const DeathComparatorFunction& deathComparator); + /** + * @brief Only available for @ref chainmatrix "chain matrices". + * Constructs a new empty matrix and reserves space for the given number of columns. + * + * @warning If @ref PersistenceMatrixOptions::has_vine_update is false, the comparators are not used. + * And if @ref PersistenceMatrixOptions::has_vine_update is true, but + * @ref PersistenceMatrixOptions::has_column_pairings is also true, the comparators are ignored and + * the current barcode is used to compare birth and deaths. Therefore it is useless to provide them in those cases. + * + * @tparam BirthComparatorFunction Type of the birth comparator: (@ref pos_index, @ref pos_index) -> bool + * @tparam DeathComparatorFunction Type of the death comparator: (@ref pos_index, @ref pos_index) -> bool + * @param numberOfColumns Number of columns to reserve space for. + * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all + * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + * @param birthComparator Method taking two @ref PosIdx indices as input and returning true if and only if + * the birth associated to the first position is strictly less than birth associated to + * the second one with respect to some self defined order. It is used while swapping two unpaired or + * two negative columns. + * @param deathComparator Method taking two @ref PosIdx indices as input and returning true if and only if + * the death associated to the first position is strictly less than death associated to + * the second one with respect to some self defined order. It is used while swapping two positive but paired + * columns. + */ + template + Position_to_index_overlay(unsigned int numberOfColumns, + Column_settings* colSettings, + const BirthComparatorFunction& birthComparator, + const DeathComparatorFunction& deathComparator); + /** + * @brief Copy constructor. If @p colSettings is not a null pointer, its value is kept + * instead of the one in the copied matrix. + * + * @param matrixToCopy Matrix to copy. + * @param colSettings Either a pointer to an existing setting structure for the columns or a null pointer. + * The structure should contain all the necessary external classes specifically necessary for the choosen column type, + * such as custom allocators. If null pointer, the pointer stored in @p matrixToCopy is used instead. + */ + Position_to_index_overlay(const Position_to_index_overlay& matrixToCopy, + Column_settings* colSettings = nullptr); + /** + * @brief Move constructor. + * + * @param other Matrix to move. + */ + Position_to_index_overlay(Position_to_index_overlay&& other) noexcept; + + /** + * @brief Inserts at the end of the matrix a new ordered column corresponding to the given boundary. + * This means that it is assumed that this method is called on boundaries in the order of the filtration. + * It also assumes that the faces in the given boundary are identified by their relative position in the filtration, + * starting at 0. If it is not the case, use the other + * @ref insert_boundary(id_index, const Boundary_type&, dimension_type) "insert_boundary" instead by indicating the + * face ID used in the boundaries when the face is inserted. + * + * Different to the constructor, the boundaries do not have to come from a simplicial complex, but also from + * a more general cell complex. This includes cubical complexes or Morse complexes for example. + * + * When inserted, the given boundary is reduced and from the reduction process, the column is deduced in the form of: + * `IDIdx + linear combination of older column IDIdxs`. If the barcode is stored, it will be updated. + * + * @tparam Boundary_type Range of @ref Matrix::cell_rep_type. Assumed to have a begin(), end() and size() method. + * @param boundary Boundary generating the new column. The content should be ordered by ID. + * @param dim Dimension of the face whose boundary is given. If the complex is simplicial, + * this parameter can be omitted as it can be deduced from the size of the boundary. + */ + template + void insert_boundary(const Boundary_type& boundary, dimension_type dim = -1); + /** + * @brief It does the same as the other version, but allows the boundary faces to be identified without restrictions + * except that all IDs have to be strictly increasing in the order of filtration. Note that you should avoid then + * to use the other insertion method to avoid overwriting IDs. + * + * As a face has to be inserted before one of its cofaces in a valid filtration (recall that it is assumed that + * the faces are inserted by order of filtration), it is sufficient to indicate the ID of the face being inserted. + * + * @tparam Boundary_type Range of @ref Matrix::cell_rep_type. Assumed to have a begin(), end() and size() method. + * @param faceID @ref IDIdx index to use to indentify the new face. + * @param boundary Boundary generating the new column. The indices of the boundary have to correspond to the + * @p faceID values of precedent calls of the method for the corresponding faces and should be ordered in + * increasing order. + * @param dim Dimension of the face whose boundary is given. If the complex is simplicial, + * this parameter can be omitted as it can be deduced from the size of the boundary. + */ + template + void insert_boundary(id_index faceIndex, const Boundary_type& boundary, dimension_type dim = -1); + /** + * @brief Returns the column at the given @ref PosIdx index. + * The type of the column depends on the choosen options, see @ref PersistenceMatrixOptions::column_type. + * + * @param position @ref PosIdx index of the column to return. + * @return Reference to the column. + */ + Column_type& get_column(pos_index position); + /** + * @brief Returns the column at the given @ref PosIdx index. + * The type of the column depends on the choosen options, see @ref PersistenceMatrixOptions::column_type. + * + * @param position @ref PosIdx index of the column to return. + * @return Const reference to the column. + */ + const Column_type& get_column(pos_index position) const; + /** + * @brief Only available if @ref PersistenceMatrixOptions::has_row_access is true. + * Returns the row at the given @ref rowindex "row index". + * The type of the row depends on the choosen options, see @ref PersistenceMatrixOptions::has_intrusive_rows. + * + * @param rowIndex @ref rowindex "Row index" of the row to return. + * @return Reference to the row. + */ + Row_type& get_row(id_index rowIndex); + /** + * @brief Only available if @ref PersistenceMatrixOptions::has_row_access is true. + * Returns the row at the given @ref rowindex "row index". + * The type of the row depends on the choosen options, see @ref PersistenceMatrixOptions::has_intrusive_rows. + * + * @param rowIndex @ref rowindex "Row index" of the row to return. + * @return Const reference to the row. + */ + const Row_type& get_row(id_index rowIndex) const; + /** + * @brief Only available if @ref PersistenceMatrixOptions::has_row_access and + * @ref PersistenceMatrixOptions::has_removable_rows are true. + * Assumes that the row is empty and removes it. + * + * @warning The removed rows are always assumed to be empty. If it is not the case, the deleted row cells are not + * removed from their columns. And in the case of intrusive rows, this will generate a segmentation fault when + * the column cells are destroyed later. The row access is just meant as a "read only" access to the rows and the + * @ref erase_empty_row method just as a way to specify that a row is empty and can therefore be removed from + * dictionnaries. This allows to avoid testing the emptiness of a row at each column cell removal, what can be + * quite frequent. + * + * @param rowIndex @ref rowindex "Row index" of the empty row to remove. + */ + void erase_empty_row(id_index rowIndex); + /** + * @brief Only available if @ref PersistenceMatrixOptions::has_removable_columns, + * @ref PersistenceMatrixOptions::has_vine_update and @ref PersistenceMatrixOptions::has_map_column_container + * are true. + * Assumes that the face is maximal in the current complex and removes it such that the matrix remains consistent + * (i.e., the matrix is still a compatible bases of the chain complex in the sense of @cite zigzag). + * The maximality of the face is not verified. + * Also updates the barcode if it was computed. + * + * See also @ref remove_last. + * + * @param position @ref PosIdx index of the face to remove. + */ + void remove_maximal_face(pos_index position); + /** + * @brief Only available if @ref PersistenceMatrixOptions::has_removable_columns is true and, + * if @ref PersistenceMatrixOptions::has_map_column_container is true or + * @ref PersistenceMatrixOptions::has_vine_update is false. + * Removes the last face in the filtration from the matrix and updates the barcode if it is stored. + * + * See also @ref remove_maximal_face. + */ + void remove_last(); + + /** + * @brief Returns the maximal dimension of a face stored in the matrix. Only available + * if @ref PersistenceMatrixOptions::has_matrix_maximal_dimension_access is true. + * + * @return The maximal dimension. + */ + dimension_type get_max_dimension() const; + /** + * @brief Returns the current number of columns in the matrix. + * + * @return The number of columns. + */ + index get_number_of_columns() const; + /** + * @brief Returns the dimension of the given face. + * + * @param position @ref PosIdx index of the face. + * @return Dimension of the face. + */ + dimension_type get_column_dimension(pos_index position) const; + + /** + * @brief Adds column corresponding to @p sourcePosition onto the column corresponding to @p targetPosition. + * + * @warning They will be no verification to ensure that the addition makes sense for the validity of the matrix. + * For example, a right-to-left addition could corrupt the computation of the barcode if done blindly. + * So should be used with care. + * + * @param sourcePosition @ref PosIdx index of the source column. + * @param targetPosition @ref PosIdx index of the target column. + */ + void add_to(pos_index sourcePosition, pos_index targetPosition); + /** + * @brief Multiplies the target column with the coefficiant and then adds the source column to it. + * That is: `targetColumn = (targetColumn * coefficient) + sourceColumn`. + * + * @warning They will be no verification to ensure that the addition makes sense for the validity of the matrix. + * For example, a right-to-left addition could corrupt the computation of the barcode if done blindly. + * So should be used with care. + * + * @param sourcePosition @ref PosIdx index of the source column. + * @param coefficient Value to multiply. + * @param targetPosition @ref PosIdx index of the target column. + */ + void multiply_target_and_add_to(pos_index sourcePosition, + const Field_element_type& coefficient, + pos_index targetPosition); + /** + * @brief Multiplies the source column with the coefficiant before adding it to the target column. + * That is: `targetColumn += (coefficient * sourceColumn)`. The source column will **not** be modified. + * + * @warning They will be no verification to ensure that the addition makes sense for the validity of the matrix. + * For example, a right-to-left addition could corrupt the computation of the barcode if done blindly. + * So should be used with care. + * + * @param coefficient Value to multiply. + * @param sourcePosition @ref PosIdx index of the source column. + * @param targetPosition @ref PosIdx index of the target column. + */ + void multiply_source_and_add_to(const Field_element_type& coefficient, + pos_index sourcePosition, + pos_index targetPosition); + + /** + * @brief Indicates if the cell at given coordinates has value zero. + * + * @param position @ref PosIdx index of the face corresponding to the column of the cell. + * @param rowIndex @ref rowindex "Row index" of the row of the cell. + * @return true If the cell has value zero. + * @return false Otherwise. + */ + bool is_zero_cell(pos_index position, id_index rowIndex) const; + /** + * @brief Indicates if the column at given index has value zero. + * + * Note that this method should always return false, as a valid @ref chainmatrix "chain matrix" never has + * empty columns. + * + * @param position @ref PosIdx index of the face corresponding to the column. + * @return true If the column has value zero. + * @return false Otherwise. + */ + bool is_zero_column(pos_index position); + + /** + * @brief Returns the @ref PosIdx index of the column which has the given @ref rowindex "row index" as pivot. + * Assumes that the pivot exists. + * + * @param faceIndex @ref rowindex "Row index" of the pivot. + * @return @ref PosIdx index of the column with the given pivot. + */ + pos_index get_column_with_pivot(id_index faceIndex) const; // assumes that pivot exists + /** + * @brief Returns the @ref rowindex "row index" of the pivot of the given column. + * + * @param position @ref PosIdx index of the face corresponding to the column. + * @return The @ref rowindex "row index" of the pivot. + */ + id_index get_pivot(pos_index position); + + /** + * @brief Resets the matrix to an empty matrix. + * + * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all + * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + */ + void reset(Column_settings* colSettings) { + matrix_.reset(colSettings); + positionToIndex_.clear(); + nextPosition_ = 0; + nextIndex_ = 0; + } + + // void set_operators(Field_operators* operators) { matrix_.set_operators(operators); } + + /** + * @brief Assign operator. + */ + Position_to_index_overlay& operator=(const Position_to_index_overlay& other); + /** + * @brief Swap operator. + */ + friend void swap(Position_to_index_overlay& matrix1, Position_to_index_overlay& matrix2) { + swap(matrix1.matrix_, matrix2.matrix_); + matrix1.positionToIndex_.swap(matrix2.positionToIndex_); + std::swap(matrix1.nextPosition_, matrix2.nextPosition_); + std::swap(matrix1.nextIndex_, matrix2.nextIndex_); + } + + void print(); // for debug + + // access to optionnal methods + + /** + * @brief Returns the current barcode of the matrix. + * Available only if @ref PersistenceMatrixOptions::has_column_pairings is true. + * + * Recall that we assume that the boundaries were inserted in the order of filtration for the barcode to be valid. + * + * @return A reference to the barcode. The barcode is a vector of @ref Matrix::Bar. A bar stores three informations: + * the @ref PosIdx birth index, the @ref PosIdx death index and the dimension of the bar. + */ + const barcode_type& get_current_barcode() const; + + /** + * @brief Only available if @ref PersistenceMatrixOptions::can_retrieve_representative_cycles is true. Precomputes + * the representative cycles of the current state of the filtration represented by the matrix. + * It does not need to be called before `get_representative_cycles` is called for the first time, but needs to be + * called before calling `get_representative_cycles` again if the matrix was modified in between. Otherwise the + * old cycles will be returned. + */ + void update_representative_cycles(); + /** + * @brief Only available if @ref PersistenceMatrixOptions::can_retrieve_representative_cycles is true. + * Returns all representative cycles of the current filtration. + * + * @return A const reference to the vector of representative cycles. + */ + const std::vector& get_representative_cycles(); + /** + * @brief Only available if @ref PersistenceMatrixOptions::can_retrieve_representative_cycles is true. + * Returns the cycle representing the given bar. + * + * @param bar A bar from the current barcode. + * @return A const reference to the cycle representing @p bar. + */ + const cycle_type& get_representative_cycle(const bar_type& bar); + + /** + * @brief Only available if @ref PersistenceMatrixOptions::has_vine_update is true. + * Does the same than @ref vine_swap, but assumes that the swap is non trivial and + * therefore skips a part of the case study. + * + * @param position @ref PosIdx index of the first face to swap. The second one has to be at `position + 1`. + * @return true If the barcode changed from the swap. + * @return false Otherwise. + */ + bool vine_swap_with_z_eq_1_case(pos_index position); + /** + * @brief Only available if @ref PersistenceMatrixOptions::has_vine_update is true. + * Does a vine swap between two faces which are consecutives in the filtration. Roughly, if \f$ F \f$ is the current + * filtration represented by the matrix, the method modifies the matrix such that the new state corresponds to + * a valid state for the filtration \f$ F' \f$ equal to \f$ F \f$ but with the two faces at position `position` + * and `position + 1` swapped. Of course, the two faces should not have a face/coface relation which each other ; + * \f$ F' \f$ has to be a valid filtration. + * See @cite vineyards for more information about vine and vineyards. + * + * @param position @ref PosIdx index of the first face to swap. The second one has to be at `position + 1`. + * @return true If the barcode changed from the swap. + * @return false Otherwise. + */ + bool vine_swap(pos_index position); + + private: + Matrix_type matrix_; /**< Interfaced matrix. */ + std::vector positionToIndex_; /**< Map from @ref PosIdx index to @ref MatIdx index. */ + pos_index nextPosition_; /**< Next unused position. */ + index nextIndex_; /**< Next unused index. */ +}; + +template +inline Position_to_index_overlay::Position_to_index_overlay( + Column_settings* colSettings) + : matrix_(colSettings), nextPosition_(0), nextIndex_(0) +{} + +template +template +inline Position_to_index_overlay::Position_to_index_overlay( + const std::vector& orderedBoundaries, Column_settings* colSettings) + : matrix_(orderedBoundaries, colSettings), + positionToIndex_(orderedBoundaries.size()), + nextPosition_(orderedBoundaries.size()), + nextIndex_(orderedBoundaries.size()) +{ + for (index i = 0; i < orderedBoundaries.size(); i++) { + positionToIndex_[i] = i; + } +} + +template +inline Position_to_index_overlay::Position_to_index_overlay( + unsigned int numberOfColumns, Column_settings* colSettings) + : matrix_(numberOfColumns, colSettings), + positionToIndex_(numberOfColumns), + nextPosition_(0), + nextIndex_(0) +{} + +template +template +inline Position_to_index_overlay::Position_to_index_overlay( + Column_settings* colSettings, + const BirthComparatorFunction& birthComparator, + const DeathComparatorFunction& deathComparator) + : matrix_(colSettings, birthComparator, deathComparator), nextPosition_(0), nextIndex_(0) +{} + +template +template +inline Position_to_index_overlay::Position_to_index_overlay( + const std::vector& orderedBoundaries, + Column_settings* colSettings, + const BirthComparatorFunction& birthComparator, + const DeathComparatorFunction& deathComparator) + : matrix_(orderedBoundaries, colSettings, birthComparator, deathComparator), + positionToIndex_(orderedBoundaries.size()), + nextPosition_(orderedBoundaries.size()), + nextIndex_(orderedBoundaries.size()) +{ + for (index i = 0; i < orderedBoundaries.size(); i++) { + positionToIndex_[i] = i; + } +} + +template +template +inline Position_to_index_overlay::Position_to_index_overlay( + unsigned int numberOfColumns, + Column_settings* colSettings, + const BirthComparatorFunction& birthComparator, + const DeathComparatorFunction& deathComparator) + : matrix_(numberOfColumns, colSettings, birthComparator, deathComparator), + positionToIndex_(numberOfColumns), + nextPosition_(0), + nextIndex_(0) +{} + +template +inline Position_to_index_overlay::Position_to_index_overlay( + const Position_to_index_overlay& matrixToCopy, Column_settings* colSettings) + : matrix_(matrixToCopy.matrix_, colSettings), + positionToIndex_(matrixToCopy.positionToIndex_), + nextPosition_(matrixToCopy.nextPosition_), + nextIndex_(matrixToCopy.nextIndex_) +{} + +template +inline Position_to_index_overlay::Position_to_index_overlay( + Position_to_index_overlay&& other) noexcept + : matrix_(std::move(other.matrix_)), + positionToIndex_(std::move(other.positionToIndex_)), + nextPosition_(std::exchange(other.nextPosition_, 0)), + nextIndex_(std::exchange(other.nextIndex_, 0)) +{} + +template +template +inline void Position_to_index_overlay::insert_boundary(const Boundary_type& boundary, + dimension_type dim) +{ + if (positionToIndex_.size() <= nextPosition_) { + positionToIndex_.resize(nextPosition_ * 2 + 1); + } + + positionToIndex_[nextPosition_++] = nextIndex_++; + + matrix_.insert_boundary(boundary, dim); +} + +template +template +inline void Position_to_index_overlay::insert_boundary(id_index faceIndex, + const Boundary_type& boundary, + dimension_type dim) +{ + if (positionToIndex_.size() <= nextPosition_) { + positionToIndex_.resize(nextPosition_ * 2 + 1); + } + + positionToIndex_[nextPosition_++] = nextIndex_++; + + matrix_.insert_boundary(faceIndex, boundary, dim); +} + +template +inline typename Position_to_index_overlay::Column_type& +Position_to_index_overlay::get_column(pos_index position) +{ + return matrix_.get_column(positionToIndex_[position]); +} + +template +inline const typename Position_to_index_overlay::Column_type& +Position_to_index_overlay::get_column(pos_index position) const +{ + return matrix_.get_column(positionToIndex_[position]); +} + +template +inline typename Position_to_index_overlay::Row_type& +Position_to_index_overlay::get_row(id_index rowIndex) +{ + return matrix_.get_row(rowIndex); +} + +template +inline const typename Position_to_index_overlay::Row_type& +Position_to_index_overlay::get_row(id_index rowIndex) const +{ + return matrix_.get_row(rowIndex); +} + +template +inline void Position_to_index_overlay::erase_empty_row(id_index rowIndex) +{ + return matrix_.erase_empty_row(rowIndex); +} + +template +inline void Position_to_index_overlay::remove_maximal_face(pos_index position) +{ + --nextPosition_; + + id_index pivot = matrix_.get_pivot(positionToIndex_[position]); + std::vector columnsToSwap(nextPosition_ - position); + + if (nextPosition_ != position) { + positionToIndex_[position] = positionToIndex_[position + 1]; + for (pos_index p = position + 1; p < nextPosition_; ++p) { + columnsToSwap[p - position - 1] = positionToIndex_[p]; + positionToIndex_[p] = positionToIndex_[p + 1]; + } + columnsToSwap.back() = positionToIndex_[nextPosition_]; + } + + matrix_.remove_maximal_face(pivot, columnsToSwap); +} + +template +inline void Position_to_index_overlay::remove_last() +{ + --nextPosition_; + if constexpr (Master_matrix_type::Option_list::has_vine_update) { + std::vector columnsToSwap; + matrix_.remove_maximal_face(matrix_.get_pivot(positionToIndex_[nextPosition_]), columnsToSwap); + } else { + matrix_.remove_last(); // linear with vine updates, so it is better to use remove_maximal_face + } +} + +template +inline typename Position_to_index_overlay::dimension_type +Position_to_index_overlay::get_max_dimension() const +{ + return matrix_.get_max_dimension(); +} + +template +inline typename Position_to_index_overlay::index +Position_to_index_overlay::get_number_of_columns() const +{ + return matrix_.get_number_of_columns(); +} + +template +inline typename Position_to_index_overlay::dimension_type +Position_to_index_overlay::get_column_dimension(pos_index position) const +{ + return matrix_.get_column_dimension(positionToIndex_[position]); +} + +template +inline void Position_to_index_overlay::add_to(pos_index sourcePosition, + pos_index targetPosition) +{ + return matrix_.add_to(positionToIndex_[sourcePosition], positionToIndex_[targetPosition]); +} + +template +inline void Position_to_index_overlay::multiply_target_and_add_to( + pos_index sourcePosition, const Field_element_type& coefficient, pos_index targetPosition) +{ + return matrix_.multiply_target_and_add_to(positionToIndex_[sourcePosition], + coefficient, + positionToIndex_[targetPosition]); +} + +template +inline void Position_to_index_overlay::multiply_source_and_add_to( + const Field_element_type& coefficient, pos_index sourcePosition, pos_index targetPosition) +{ + return matrix_.multiply_source_and_add_to(coefficient, + positionToIndex_[sourcePosition], + positionToIndex_[targetPosition]); +} + +template +inline bool Position_to_index_overlay::is_zero_cell(pos_index position, + id_index rowIndex) const +{ + return matrix_.is_zero_cell(positionToIndex_[position], rowIndex); +} + +template +inline bool Position_to_index_overlay::is_zero_column(pos_index position) +{ + return matrix_.is_zero_column(positionToIndex_[position]); +} + +template +inline typename Position_to_index_overlay::pos_index +Position_to_index_overlay::get_column_with_pivot(id_index faceIndex) const +{ + index id = matrix_.get_column_with_pivot(faceIndex); + pos_index i = 0; + while (positionToIndex_[i] != id) ++i; + return i; +} + +template +inline typename Position_to_index_overlay::id_index +Position_to_index_overlay::get_pivot(pos_index position) +{ + return matrix_.get_pivot(positionToIndex_[position]); +} + +template +inline Position_to_index_overlay& +Position_to_index_overlay::operator=(const Position_to_index_overlay& other) +{ + matrix_ = other.matrix_; + positionToIndex_ = other.positionToIndex_; + nextPosition_ = other.nextPosition_; + nextIndex_ = other.nextIndex_; + + return *this; +} + +template +inline void Position_to_index_overlay::print() +{ + return matrix_.print(); +} + +template +inline const typename Position_to_index_overlay::barcode_type& +Position_to_index_overlay::get_current_barcode() const +{ + return matrix_.get_current_barcode(); +} + +template +inline void Position_to_index_overlay::update_representative_cycles() +{ + matrix_.update_representative_cycles(); +} + +template +inline const std::vector::cycle_type>& +Position_to_index_overlay::get_representative_cycles() +{ + return matrix_.get_representative_cycles(); +} + +template +inline const typename Position_to_index_overlay::cycle_type& +Position_to_index_overlay::get_representative_cycle(const bar_type& bar) +{ + return matrix_.get_representative_cycle(bar); +} + +template +inline bool Position_to_index_overlay::vine_swap_with_z_eq_1_case(pos_index position) +{ + index next = matrix_.vine_swap_with_z_eq_1_case(positionToIndex_[position], positionToIndex_[position + 1]); + if (next == positionToIndex_[position]) { + std::swap(positionToIndex_[position], positionToIndex_[position + 1]); + return true; + } + + return false; +} + +template +inline bool Position_to_index_overlay::vine_swap(pos_index position) +{ + index next = matrix_.vine_swap(positionToIndex_[position], positionToIndex_[position + 1]); + if (next == positionToIndex_[position]) { + std::swap(positionToIndex_[position], positionToIndex_[position + 1]); + return true; + } + + return false; +} + +} // namespace persistence_matrix +} // namespace Gudhi + +#endif // PM_POS_TO_ID_TRANSLATION_H diff --git a/src/Persistence_matrix/include/gudhi/Persistence_matrix/ru_matrix.h b/src/Persistence_matrix/include/gudhi/Persistence_matrix/ru_matrix.h new file mode 100644 index 0000000000..8bfa76a253 --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Persistence_matrix/ru_matrix.h @@ -0,0 +1,930 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file ru_matrix.h + * @author Hannah Schreiber + * @brief Contains the @ref RU_matrix class. + */ + +#ifndef PM_RU_MATRIX_H +#define PM_RU_MATRIX_H + +#include +#include //std::swap, std::move & std::exchange +#include //print() only + +namespace Gudhi { +namespace persistence_matrix { + +/** + * @class RU_matrix ru_matrix.h gudhi/Persistence_matrix/ru_matrix.h + * @ingroup persistence_matrix + * + * @brief %Matrix structure to store the ordered @ref boundarymatrix "boundary matrix" \f$ R \cdot U \f$ of a filtered + * complex in order to compute its persistent homology, as well as representative cycles. + * Supports vineyards (see @cite vineyards) and the removal of maximal faces while maintaining + * a valid barcode. Provides an access to its columns and rows. + * + * @tparam Master_matrix An instanciation of @ref Matrix from which all types and options are deduced. + */ +template +class RU_matrix : public Master_matrix::RU_pairing_option, + public Master_matrix::RU_vine_swap_option, + public Master_matrix::RU_representative_cycles_option +{ + public: + /** + * @brief Field operators class. Necessary only if @ref PersistenceMatrixOptions::is_z2 is false. + */ + using Field_operators = typename Master_matrix::Field_operators; + using Field_element_type = typename Master_matrix::element_type; /**< Type of an field element. */ + using Column_type = typename Master_matrix::Column_type; /**< Column type. */ + using Row_type = typename Master_matrix::Row_type; /**< Row type, + only necessary with row access option. */ + using Cell_constructor = typename Master_matrix::Cell_constructor; /**< Factory of @ref Cell classes. */ + using Column_settings = typename Master_matrix::Column_settings; /**< Structure giving access to the columns to + necessary external classes. */ + using boundary_type = typename Master_matrix::boundary_type; /**< Type of an input column. */ + using index = typename Master_matrix::index; /**< @ref MatIdx index type. */ + using id_index = typename Master_matrix::id_index; /**< @ref IDIdx index type. */ + using pos_index = typename Master_matrix::pos_index; /**< @ref PosIdx index type. */ + using dimension_type = typename Master_matrix::dimension_type; /**< Dimension value type. */ + + /** + * @brief Constructs an empty matrix. + * + * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all + * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + */ + RU_matrix(Column_settings* colSettings); + /** + * @brief Constructs a new matrix from the given ranges of @ref Matrix::cell_rep_type. Each range corresponds to a + * column (the order of the ranges are preserved). The content of the ranges is assumed to be sorted by increasing + * IDs. The IDs of the simplices are also assumed to be consecutifs, ordered by filtration value, starting with 0. + * + * @tparam Boundary_type Range type for @ref Matrix::cell_rep_type ranges. + * Assumed to have a begin(), end() and size() method. + * @param orderedBoundaries Range of boundaries: @p orderedBoundaries is interpreted as a boundary matrix of a + * filtered **simplicial** complex, whose boundaries are ordered by filtration order. + * Therefore, `orderedBoundaries[i]` should store the boundary of the \f$ i^{th} \f$ simplex in the filtration, + * as an ordered list of indices of its facets (again those indices correspond to their respective position + * in the matrix). That is why the indices of the simplices are assumed to be consecutifs and starting with 0 + * (an empty boundary is interpreted as a vertex boundary and not as a non existing simplex). + * All dimensions up to the maximal dimension of interest have to be present. If only a higher dimension is of + * interest and not everything should be stored, then use the @ref insert_boundary method instead (after creating the + * matrix with the @ref RU_matrix(unsigned int, Column_settings*) constructor preferably). + * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all + * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + */ + template + RU_matrix(const std::vector& orderedBoundaries, + Column_settings* colSettings); + /** + * @brief Constructs a new empty matrix and reserves space for the given number of columns. + * + * @param numberOfColumns Number of columns to reserve space for. + * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all + * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + */ + RU_matrix(unsigned int numberOfColumns, Column_settings* colSettings); + /** + * @brief Copy constructor. If @p colSettings is not a null pointer, its value is kept + * instead of the one in the copied matrix. + * + * @param matrixToCopy Matrix to copy. + * @param colSettings Either a pointer to an existing setting structure for the columns or a null pointer. + * The structure should contain all the necessary external classes specifically necessary for the choosen column type, + * such as custom allocators. If null pointer, the pointer stored in @p matrixToCopy is used instead. + */ + RU_matrix(const RU_matrix& matrixToCopy, + Column_settings* colSettings = nullptr); + /** + * @brief Move constructor. + * + * @param other Matrix to move. + */ + RU_matrix(RU_matrix&& other) noexcept; + + /** + * @brief Inserts at the end of the matrix a new ordered column corresponding to the given boundary. + * This means that it is assumed that this method is called on boundaries in the order of the filtration. + * It also assumes that the faces in the given boundary are identified by their relative position in the filtration, + * starting at 0. If it is not the case, use the other + * @ref insert_boundary(id_index, const Boundary_type&, dimension_type) "insert_boundary" instead by indicating the + * face ID used in the boundaries when the face is inserted. + * + * Different to the constructor, the boundaries do not have to come from a simplicial complex, but also from + * a more general cell complex. This includes cubical complexes or Morse complexes for example. + * + * At the insertion, the boundary is stored in its reduced form and the barcode, if enabled, is updated. + * + * @tparam Boundary_type Range of @ref Matrix::cell_rep_type. Assumed to have a begin(), end() and size() method. + * @param boundary Boundary generating the new column. The content should be ordered by ID. + * @param dim Dimension of the face whose boundary is given. If the complex is simplicial, + * this parameter can be omitted as it can be deduced from the size of the boundary. + */ + template + void insert_boundary(const Boundary_type& boundary, dimension_type dim = -1); + /** + * @brief It does the same as the other version, but allows the boundary faces to be identified without restrictions + * except that all IDs have to be strictly increasing in the order of filtration. Note that you should avoid then + * to use the other insertion method to avoid overwriting IDs. + * + * As a face has to be inserted before one of its cofaces in a valid filtration (recall that it is assumed that + * the faces are inserted by order of filtration), it is sufficient to indicate the ID of the face being inserted. + * + * @tparam Boundary_type Range of @ref Matrix::cell_rep_type. Assumed to have a begin(), end() and size() method. + * @param faceIndex @ref IDIdx index to use to indentify the new face. + * @param boundary Boundary generating the new column. The indices of the boundary have to correspond to the + * @p faceIndex values of precedent calls of the method for the corresponding faces and should be ordered in + * increasing order. + * @param dim Dimension of the face whose boundary is given. If the complex is simplicial, + * this parameter can be omitted as it can be deduced from the size of the boundary. + */ + template + void insert_boundary(id_index faceIndex, const Boundary_type& boundary, dimension_type dim = -1); + /** + * @brief Returns the column at the given @ref MatIdx index in \f$ R \f$ if @p inR is true and + * in \f$ U \f$ if @p inR is false. + * The type of the column depends on the choosen options, see @ref PersistenceMatrixOptions::column_type. + * + * Note that before returning the column, all column cells can eventually be reordered, if lazy swaps occurred. + * It is therefore recommended to avoid calling @ref get_column between vine swaps, otherwise the benefits + * of the the lazyness is lost. + * + * @param columnIndex @ref MatIdx index of the column to return. + * @param inR If true, returns the column in \f$ R \f$, if false, returns the column in \f$ U \f$. + * Default value: true. + * @return Reference to the column. + */ + Column_type& get_column(index columnIndex, bool inR = true); + /** + * @brief Returns the row at the given @ref rowindex "row index" in \f$ R \f$ if @p inR is true and + * in \f$ U \f$ if @p inR is false. + * The type of the row depends on the choosen options, see @ref PersistenceMatrixOptions::has_intrusive_rows. + * + * Note that before returning the row, all column cells can eventually be reordered, if lazy swaps occurred. + * It is therefore recommended to avoid calling @ref get_row between vine swaps, otherwise the benefits + * of the the lazyness is lost. + * + * @param rowIndex @ref rowindex "Row index" of the row to return. + * @param inR If true, returns the row in \f$ R \f$, if false, returns the row in \f$ U \f$. + * Default value: true. + * @return Reference to the row. + */ + Row_type& get_row(index rowIndex, bool inR = true); + /** + * @brief If @ref PersistenceMatrixOptions::has_row_access and @ref PersistenceMatrixOptions::has_removable_rows + * are true: assumes that the row is empty in \f$ R \f$ and removes it from \f$ R \f$. If the matrix is valid, + * a row will never be empty in \f$ U \f$, so \f$ U \f$ won't be affected. + * If @ref PersistenceMatrixOptions::has_map_column_container + * and @ref PersistenceMatrixOptions::has_column_and_row_swaps are true: cleans up maps used for the lazy row swaps. + * Otherwise, does nothing. + * + * @warning The removed rows are always assumed to be empty in \f$ R \f$. If it is not the case, the deleted row + * cells are not removed from their columns. And in the case of intrusive rows, this will generate a segmentation + * fault when the column cells are destroyed later. The row access is just meant as a "read only" access to the + * rows and the @ref erase_empty_row method just as a way to specify that a row is empty and can therefore be removed + * from dictionnaries. This allows to avoid testing the emptiness of a row at each column cell removal, what can + * be quite frequent. + * + * @param rowIndex @ref rowindex "Row index" of the empty row. + */ + void erase_empty_row(index rowIndex); + /** + * @brief Only available if @ref PersistenceMatrixOptions::has_removable_columns and + * @ref PersistenceMatrixOptions::has_vine_update are true. + * Assumes that the face is maximal in the current complex and removes it such that the matrix remains consistent + * (i.e., RU is still an upper triangular decomposition of the @ref boundarymatrix "boundary matrix"). + * The maximality of the face is not verified. + * Also updates the barcode if it is stored. + * + * See also @ref remove_last. + * + * @param columnIndex @ref MatIdx index of the face to remove. + */ + void remove_maximal_face(index columnIndex); + /** + * @brief Only available if @ref PersistenceMatrixOptions::has_removable_columns is true. + * Removes the last face in the filtration from the matrix and updates the barcode if it is stored. + * + * See also @ref remove_maximal_face. + */ + void remove_last(); + + /** + * @brief Returns the maximal dimension of a face stored in the matrix. + * Only available if @ref PersistenceMatrixOptions::has_matrix_maximal_dimension_access is true. + * + * @return The maximal dimension. + */ + dimension_type get_max_dimension() const; + /** + * @brief Returns the current number of columns in the matrix. + * + * @return The number of columns. + */ + index get_number_of_columns() const; + /** + * @brief Returns the dimension of the given column. + * + * @param columnIndex @ref MatIdx index of the column representing the face. + * @return Dimension of the face. + */ + dimension_type get_column_dimension(index columnIndex) const; + + /** + * @brief Adds column at @p sourceColumnIndex onto the column at @p targetColumnIndex in the matrix. + * + * @warning They will be no verification to ensure that the addition makes sense for the validity of a + * boundary matrix of a filtered complex. For example, a right-to-left addition could corrupt the computation + * of the barcode if done blindly. So should be used with care. + * + * @param sourceColumnIndex @ref MatIdx index of the source column. + * @param targetColumnIndex @ref MatIdx index of the target column. + */ + void add_to(index sourceColumnIndex, index targetColumnIndex); + /** + * @brief Multiplies the target column with the coefficiant and then adds the source column to it. + * That is: `targetColumn = (targetColumn * coefficient) + sourceColumn`. + * + * @warning They will be no verification to ensure that the addition makes sense for the validity of a + * boundary matrix of a filtered complex. For example, a right-to-left addition could corrupt the computation + * of the barcode if done blindly. So should be used with care. + * + * @param sourceColumnIndex @ref MatIdx index of the source column. + * @param coefficient Value to multiply. + * @param targetColumnIndex @ref MatIdx index of the target column. + */ + void multiply_target_and_add_to(index sourceColumnIndex, + const Field_element_type& coefficient, + index targetColumnIndex); + /** + * @brief Multiplies the source column with the coefficiant before adding it to the target column. + * That is: `targetColumn += (coefficient * sourceColumn)`. The source column will **not** be modified. + * + * @warning They will be no verification to ensure that the addition makes sense for the validity of a + * boundary matrix of a filtered complex. For example, a right-to-left addition could corrupt the computation + * of the barcode if done blindly. So should be used with care. + * + * @param coefficient Value to multiply. + * @param sourceColumnIndex @ref MatIdx index of the source column. + * @param targetColumnIndex @ref MatIdx index of the target column. + */ + void multiply_source_and_add_to(const Field_element_type& coefficient, + index sourceColumnIndex, + index targetColumnIndex); + + /** + * @brief Zeroes the cell at the given coordinates in \f$ R \f$ if @p inR is true or in + * \f$ U \f$ if @p inR is false. Should be used with care to not destroy the validity of the persistence + * related properties of the matrix. + * + * @param columnIndex @ref MatIdx index of the column of the cell. + * @param rowIndex @ref rowindex "Row index" of the row of the cell. + * @param inR Boolean indicating in which matrix to zero: if true in \f$ R \f$ and if false in \f$ U \f$. + * Default value: true. + */ + void zero_cell(index columnIndex, index rowIndex, bool inR = true); + /** + * @brief Zeroes the column at the given index in \f$ R \f$ if @p inR is true or in + * \f$ U \f$ if @p inR is false. Should be used with care to not destroy the validity of the persistence + * related properties of the matrix. + * + * @param columnIndex @ref MatIdx index of the column to zero. + * @param inR Boolean indicating in which matrix to zero: if true in \f$ R \f$ and if false in \f$ U \f$. + * Default value: true. + */ + void zero_column(index columnIndex, bool inR = true); + /** + * @brief Indicates if the cell at given coordinates has value zero in \f$ R \f$ + * if @p inR is true or in \f$ U \f$ if @p inR is false. + * + * @param columnIndex @ref MatIdx index of the column of the cell. + * @param rowIndex @ref rowindex "Row index" of the row of the cell. + * @param inR Boolean indicating in which matrix to look: if true in \f$ R \f$ and if false in \f$ U \f$. + * Default value: true. + * @return true If the cell has value zero. + * @return false Otherwise. + */ + bool is_zero_cell(index columnIndex, index rowIndex, bool inR = true) const; + /** + * @brief Indicates if the column at given index has value zero in \f$ R \f$ + * if @p inR is true or in \f$ U \f$ if @p inR is false. + * + * Note that if @p inR is false, this method should usually return false. + * + * @param columnIndex @ref MatIdx index of the column. + * @param inR Boolean indicating in which matrix to look: if true in \f$ R \f$ and if false in \f$ U \f$. + * Default value: true. + * @return true If the column has value zero. + * @return false Otherwise. + */ + bool is_zero_column(index columnIndex, bool inR = true); + + /** + * @brief Returns the @ref MatIdx index of the column which has the given @ref rowindex "row index" as pivot in + * \f$ R \f$. Assumes that the pivot exists. + * + * @param faceIndex @ref rowindex "Row index" of the pivot. + * @return @ref MatIdx index of the column in \f$ R \f$ with the given pivot. + */ + index get_column_with_pivot(index faceIndex) const; + /** + * @brief Returns the @ref rowindex "row index" of the pivot of the given column in \f$ R \f$. + * + * @param columnIndex @ref MatIdx index of the column in \f$ R \f$. + * @return The @ref rowindex "row index" of the pivot. + */ + index get_pivot(index columnIndex); + + /** + * @brief Resets the matrix to an empty matrix. + * + * @param colSettings Pointer to an existing setting structure for the columns. The structure should contain all + * the necessary external classes specifically necessary for the choosen column type, such as custom allocators. + */ + void reset(Column_settings* colSettings) { + reducedMatrixR_.reset(colSettings); + mirrorMatrixU_.reset(colSettings); + pivotToColumnIndex_.clear(); + nextEventIndex_ = 0; + if constexpr (!Master_matrix::Option_list::is_z2){ + operators_ = &(colSettings->operators); + } + } + + /** + * @brief Assign operator. + */ + RU_matrix& operator=(const RU_matrix& other); + /** + * @brief Swap operator. + */ + friend void swap(RU_matrix& matrix1, RU_matrix& matrix2) { + swap(static_cast(matrix1), + static_cast(matrix2)); + swap(static_cast(matrix1), + static_cast(matrix2)); + swap(static_cast(matrix1), + static_cast(matrix2)); + swap(matrix1.reducedMatrixR_, matrix2.reducedMatrixR_); + swap(matrix1.mirrorMatrixU_, matrix2.mirrorMatrixU_); + matrix1.pivotToColumnIndex_.swap(matrix2.pivotToColumnIndex_); + std::swap(matrix1.nextEventIndex_, matrix2.nextEventIndex_); + std::swap(matrix1.operators_, matrix2.operators_); + } + + void print(); // for debug + + private: + using swap_opt = typename Master_matrix::RU_vine_swap_option; + using pair_opt = typename Master_matrix::RU_pairing_option; + using rep_opt = typename Master_matrix::RU_representative_cycles_option; + using dictionnary_type = typename Master_matrix::template dictionnary_type; + using barcode_type = typename Master_matrix::barcode_type; + using bar_dictionnary_type = typename Master_matrix::bar_dictionnary_type; + using r_matrix_type = typename Master_matrix::Boundary_matrix_type; + using u_matrix_type = typename Master_matrix::Base_matrix_type; + + friend rep_opt; // direct access to the two matrices + friend swap_opt; // direct access to the two matrices + + r_matrix_type reducedMatrixR_; /**< R. */ + // TODO: make U not accessible by default and add option to enable access? Inaccessible, it + // needs less options and we could avoid some ifs. + u_matrix_type mirrorMatrixU_; /**< U. */ + dictionnary_type pivotToColumnIndex_; /**< Map from pivot row index to column @ref MatIdx index. */ + pos_index nextEventIndex_; /**< Next birth or death index. */ + Field_operators* operators_; /**< Field operators, + can be nullptr if @ref PersistenceMatrixOptions::is_z2 is true. */ + + void _insert_boundary(index currentIndex); + void _initialize_U(); + void _reduce(); + void _reduce_last_column(index lastIndex); + void _reduce_column(index target, index eventIndex); + void _reduce_column_by(index target, index source); + void _update_barcode(pos_index birth, pos_index death); + void _add_bar(dimension_type dim, pos_index birth); + + constexpr barcode_type& _barcode(); + constexpr bar_dictionnary_type& _indexToBar(); +}; + +template +inline RU_matrix::RU_matrix(Column_settings* colSettings) + : pair_opt(), + swap_opt(), + rep_opt(), + reducedMatrixR_(colSettings), + mirrorMatrixU_(colSettings), + nextEventIndex_(0), + operators_(nullptr) +{ + if constexpr (!Master_matrix::Option_list::is_z2){ + operators_ = &(colSettings->operators); + } +} + +template +template +inline RU_matrix::RU_matrix(const std::vector& orderedBoundaries, + Column_settings* colSettings) + : pair_opt(), + swap_opt(), + rep_opt(), + reducedMatrixR_(orderedBoundaries, colSettings), + mirrorMatrixU_(orderedBoundaries.size(), colSettings), + nextEventIndex_(orderedBoundaries.size()), + operators_(nullptr) +{ + if constexpr (!Master_matrix::Option_list::is_z2){ + operators_ = &(colSettings->operators); + } + + if constexpr (Master_matrix::Option_list::has_map_column_container) { + pivotToColumnIndex_.reserve(orderedBoundaries.size()); + } else { + pivotToColumnIndex_.resize(orderedBoundaries.size(), -1); + } + + _initialize_U(); + _reduce(); +} + +template +inline RU_matrix::RU_matrix(unsigned int numberOfColumns, + Column_settings* colSettings) + : pair_opt(), + swap_opt(), + rep_opt(), + reducedMatrixR_(numberOfColumns, colSettings), + mirrorMatrixU_(numberOfColumns, colSettings), + nextEventIndex_(0), + operators_(nullptr) +{ + if constexpr (!Master_matrix::Option_list::is_z2){ + operators_ = &(colSettings->operators); + } + + if constexpr (Master_matrix::Option_list::has_map_column_container) { + pivotToColumnIndex_.reserve(numberOfColumns); + } else { + pivotToColumnIndex_.resize(numberOfColumns, -1); + } + if constexpr (Master_matrix::Option_list::has_column_pairings) { + _indexToBar().reserve(numberOfColumns); + } + if constexpr (Master_matrix::Option_list::has_vine_update) { + swap_opt::positionToRowIdx_.reserve(numberOfColumns); + } +} + +template +inline RU_matrix::RU_matrix(const RU_matrix& matrixToCopy, + Column_settings* colSettings) + : pair_opt(static_cast(matrixToCopy)), + swap_opt(static_cast(matrixToCopy)), + rep_opt(static_cast(matrixToCopy)), + reducedMatrixR_(matrixToCopy.reducedMatrixR_, colSettings), + mirrorMatrixU_(matrixToCopy.mirrorMatrixU_, colSettings), + pivotToColumnIndex_(matrixToCopy.pivotToColumnIndex_), + nextEventIndex_(matrixToCopy.nextEventIndex_), + operators_(colSettings == nullptr ? matrixToCopy.operators_ : nullptr) +{ + if constexpr (!Master_matrix::Option_list::is_z2){ + if (colSettings != nullptr) operators_ = &(colSettings->operators); + } +} + +template +inline RU_matrix::RU_matrix(RU_matrix&& other) noexcept + : pair_opt(std::move(static_cast(other))), + swap_opt(std::move(static_cast(other))), + rep_opt(std::move(static_cast(other))), + reducedMatrixR_(std::move(other.reducedMatrixR_)), + mirrorMatrixU_(std::move(other.mirrorMatrixU_)), + pivotToColumnIndex_(std::move(other.pivotToColumnIndex_)), + nextEventIndex_(std::exchange(other.nextEventIndex_, 0)), + operators_(std::exchange(other.operators_, nullptr)) +{} + +template +template +inline void RU_matrix::insert_boundary(const Boundary_type& boundary, dimension_type dim) +{ + auto id = reducedMatrixR_.insert_boundary(boundary, dim); + if constexpr (Master_matrix::Option_list::has_vine_update) swap_opt::positionToRowIdx_.push_back(id); + _insert_boundary(id); +} + +template +template +inline void RU_matrix::insert_boundary(id_index faceIndex, + const Boundary_type& boundary, + dimension_type dim) +{ + if constexpr (Master_matrix::Option_list::has_vine_update) { + swap_opt::positionToRowIdx_.push_back(faceIndex); + } + _insert_boundary(reducedMatrixR_.insert_boundary(faceIndex, boundary, dim)); +} + +template +inline typename RU_matrix::Column_type& RU_matrix::get_column(index columnIndex, + bool inR) +{ + if (inR) { + return reducedMatrixR_.get_column(columnIndex); + } + return mirrorMatrixU_.get_column(columnIndex); +} + +template +inline typename RU_matrix::Row_type& RU_matrix::get_row(index rowIndex, bool inR) +{ + static_assert(Master_matrix::Option_list::has_row_access, "'get_row' is not implemented for the chosen options."); + + if (inR) { + return reducedMatrixR_.get_row(rowIndex); + } + return mirrorMatrixU_.get_row(rowIndex); +} + +template +inline void RU_matrix::erase_empty_row(index rowIndex) +{ + reducedMatrixR_.erase_empty_row(rowIndex); +} + +template +inline void RU_matrix::remove_maximal_face(index columnIndex) +{ + static_assert(Master_matrix::Option_list::has_removable_columns && Master_matrix::Option_list::has_vine_update, + "'remove_maximal_face' is not implemented for the chosen options."); + + // TODO: is there an easy test to verify maximality even without row access? + + for (index curr = columnIndex; curr < nextEventIndex_ - 1; ++curr) { + swap_opt::vine_swap(curr); + } + + remove_last(); +} + +template +inline void RU_matrix::remove_last() +{ + static_assert(Master_matrix::Option_list::has_removable_columns, + "'remove_last' is not implemented for the chosen options."); + + if (nextEventIndex_ == 0) return; // empty matrix + --nextEventIndex_; + + // assumes PosIdx == MatIdx for boundary matrices. + if constexpr (Master_matrix::Option_list::has_column_pairings) { + if constexpr (Master_matrix::hasFixedBarcode) { + auto& bar = _barcode()[_indexToBar()[nextEventIndex_]]; + if (bar.death == static_cast(-1)) { // birth + _barcode().pop_back(); // sorted by birth and nextEventIndex_ has to be the heighest one + } else { // death + bar.death = -1; + }; + _indexToBar().pop_back(); + } else { // birth order eventually shuffled by vine updates. No sort possible to keep the matchings. + auto it = _indexToBar().find(nextEventIndex_); + typename barcode_type::iterator bar = it->second; + + if (bar->death == static_cast(-1)) + _barcode().erase(bar); + else + bar->death = -1; + + _indexToBar().erase(it); + } + } + + mirrorMatrixU_.remove_last(); + if constexpr (Master_matrix::Option_list::has_map_column_container) { + pivotToColumnIndex_.erase(reducedMatrixR_.remove_last()); + } else { + id_index lastPivot = reducedMatrixR_.remove_last(); + if (lastPivot != static_cast(-1)) pivotToColumnIndex_[lastPivot] = -1; + } + + if constexpr (Master_matrix::Option_list::has_vine_update) { + swap_opt::positionToRowIdx_.pop_back(); + } +} + +template +inline typename RU_matrix::dimension_type RU_matrix::get_max_dimension() const +{ + return reducedMatrixR_.get_max_dimension(); +} + +template +inline typename RU_matrix::index RU_matrix::get_number_of_columns() const +{ + return reducedMatrixR_.get_number_of_columns(); +} + +template +inline typename RU_matrix::dimension_type RU_matrix::get_column_dimension( + index columnIndex) const +{ + return reducedMatrixR_.get_column_dimension(columnIndex); +} + +template +inline void RU_matrix::add_to(index sourceColumnIndex, index targetColumnIndex) +{ + reducedMatrixR_.add_to(sourceColumnIndex, targetColumnIndex); + //U transposed to avoid row operations + if constexpr (Master_matrix::Option_list::has_vine_update) + mirrorMatrixU_.add_to(targetColumnIndex, sourceColumnIndex); + else + mirrorMatrixU_.add_to(sourceColumnIndex, targetColumnIndex); +} + +template +inline void RU_matrix::multiply_target_and_add_to(index sourceColumnIndex, + const Field_element_type& coefficient, + index targetColumnIndex) +{ + reducedMatrixR_.multiply_target_and_add_to(sourceColumnIndex, coefficient, targetColumnIndex); + mirrorMatrixU_.multiply_target_and_add_to(sourceColumnIndex, coefficient, targetColumnIndex); +} + +template +inline void RU_matrix::multiply_source_and_add_to(const Field_element_type& coefficient, + index sourceColumnIndex, + index targetColumnIndex) +{ + reducedMatrixR_.multiply_source_and_add_to(coefficient, sourceColumnIndex, targetColumnIndex); + mirrorMatrixU_.multiply_source_and_add_to(coefficient, sourceColumnIndex, targetColumnIndex); +} + +template +inline void RU_matrix::zero_cell(index columnIndex, index rowIndex, bool inR) +{ + if (inR) { + return reducedMatrixR_.zero_cell(columnIndex, rowIndex); + } + return mirrorMatrixU_.zero_cell(columnIndex, rowIndex); +} + +template +inline void RU_matrix::zero_column(index columnIndex, bool inR) +{ + if (inR) { + return reducedMatrixR_.zero_column(columnIndex); + } + return mirrorMatrixU_.zero_column(columnIndex); +} + +template +inline bool RU_matrix::is_zero_cell(index columnIndex, index rowIndex, bool inR) const +{ + if (inR) { + return reducedMatrixR_.is_zero_cell(columnIndex, rowIndex); + } + return mirrorMatrixU_.is_zero_cell(columnIndex, rowIndex); +} + +template +inline bool RU_matrix::is_zero_column(index columnIndex, bool inR) +{ + if (inR) { + return reducedMatrixR_.is_zero_column(columnIndex); + } + return mirrorMatrixU_.is_zero_column(columnIndex); +} + +template +inline typename RU_matrix::index RU_matrix::get_column_with_pivot( + index faceIndex) const +{ + if constexpr (Master_matrix::Option_list::has_map_column_container) { + return pivotToColumnIndex_.at(faceIndex); + } else { + return pivotToColumnIndex_[faceIndex]; + } +} + +template +inline typename RU_matrix::index RU_matrix::get_pivot(index columnIndex) +{ + return reducedMatrixR_.get_column(columnIndex).get_pivot(); +} + +template +inline RU_matrix& RU_matrix::operator=(const RU_matrix& other) +{ + swap_opt::operator=(other); + pair_opt::operator=(other); + rep_opt::operator=(other); + reducedMatrixR_ = other.reducedMatrixR_; + mirrorMatrixU_ = other.mirrorMatrixU_; + pivotToColumnIndex_ = other.pivotToColumnIndex_; + nextEventIndex_ = other.nextEventIndex_; + operators_ = other.operators_; + return *this; +} + +template +inline void RU_matrix::print() +{ + std::cout << "R_matrix:\n"; + reducedMatrixR_.print(); + std::cout << "U_matrix:\n"; + mirrorMatrixU_.print(); +} + +template +inline void RU_matrix::_insert_boundary(index currentIndex) +{ + if constexpr (Master_matrix::Option_list::is_z2) { + mirrorMatrixU_.insert_column({currentIndex}); + } else { + mirrorMatrixU_.insert_column({{currentIndex, 1}}); + } + + if constexpr (!Master_matrix::Option_list::has_map_column_container) { + while (pivotToColumnIndex_.size() <= currentIndex) + pivotToColumnIndex_.resize((pivotToColumnIndex_.size() + 1) * 2, -1); + } + + _reduce_last_column(currentIndex); + ++nextEventIndex_; +} + +template +inline void RU_matrix::_initialize_U() +{ + typename std::conditional >::type id; + if constexpr (!Master_matrix::Option_list::is_z2) id.second = 1; + + for (id_index i = 0; i < reducedMatrixR_.get_number_of_columns(); i++) { + if constexpr (Master_matrix::Option_list::is_z2) + id = i; + else + id.first = i; + mirrorMatrixU_.insert_column({id}); + } +} + +template +inline void RU_matrix::_reduce() +{ + if constexpr (Master_matrix::Option_list::has_column_pairings) { + _indexToBar().reserve(reducedMatrixR_.get_number_of_columns()); + } + if constexpr (Master_matrix::Option_list::has_vine_update) { + swap_opt::positionToRowIdx_.reserve(reducedMatrixR_.get_number_of_columns()); + } + + for (index i = 0; i < reducedMatrixR_.get_number_of_columns(); i++) { + if constexpr (Master_matrix::Option_list::has_vine_update) { + swap_opt::positionToRowIdx_.push_back(i); + } + if (!(reducedMatrixR_.is_zero_column(i))) { + _reduce_column(i, i); + } else { + _add_bar(get_column_dimension(i), i); + } + } +} + +template +inline void RU_matrix::_reduce_last_column(index lastIndex) +{ + if (reducedMatrixR_.get_column(lastIndex).is_empty()) { + _add_bar(get_column_dimension(lastIndex), nextEventIndex_); + return; + } + + _reduce_column(lastIndex, nextEventIndex_); +} + +template +inline void RU_matrix::_reduce_column(index target, index eventIndex) +{ + auto get_column_with_pivot_ = [&](id_index pivot) -> index { + if (pivot == static_cast(-1)) return -1; + if constexpr (Master_matrix::Option_list::has_map_column_container) { + auto it = pivotToColumnIndex_.find(pivot); + if (it == pivotToColumnIndex_.end()) + return -1; + else + return it->second; + } else { + return pivotToColumnIndex_[pivot]; + } + }; + + Column_type& curr = reducedMatrixR_.get_column(target); + id_index pivot = curr.get_pivot(); + index currIndex = get_column_with_pivot_(pivot); + + while (pivot != static_cast(-1) && currIndex != static_cast(-1)) { + _reduce_column_by(target, currIndex); + pivot = curr.get_pivot(); + currIndex = get_column_with_pivot_(pivot); + } + + if (pivot != static_cast(-1)) { + if constexpr (Master_matrix::Option_list::has_map_column_container) { + pivotToColumnIndex_.try_emplace(pivot, target); + } else { + pivotToColumnIndex_[pivot] = target; + } + _update_barcode(pivot, eventIndex); + } else { + _add_bar(get_column_dimension(target), eventIndex); + } +} + +template +inline void RU_matrix::_reduce_column_by(index target, index source) +{ + Column_type& curr = reducedMatrixR_.get_column(target); + if constexpr (Master_matrix::Option_list::is_z2) { + curr += reducedMatrixR_.get_column(source); + //to avoid having to do line operations during vineyards, U is transposed + //TODO: explain this somewhere in the documentation... + if constexpr (Master_matrix::Option_list::has_vine_update) + mirrorMatrixU_.get_column(source) += mirrorMatrixU_.get_column(target); + else + mirrorMatrixU_.get_column(target) += mirrorMatrixU_.get_column(source); + } else { + Column_type& toadd = reducedMatrixR_.get_column(source); + Field_element_type coef = toadd.get_pivot_value(); + coef = operators_->get_inverse(coef); + operators_->multiply_inplace(coef, operators_->get_characteristic() - curr.get_pivot_value()); + + curr.multiply_source_and_add(toadd, coef); + mirrorMatrixU_.multiply_source_and_add_to(coef, source, target); + // mirrorMatrixU_.get_column(target).multiply_source_and_add(mirrorMatrixU_.get_column(source), coef); + } +} + +template +inline void RU_matrix::_update_barcode(pos_index birth, pos_index death) +{ + if constexpr (Master_matrix::Option_list::has_column_pairings) { + if constexpr (Master_matrix::hasFixedBarcode || !Master_matrix::Option_list::has_removable_columns) { + _barcode()[_indexToBar()[birth]].death = death; + _indexToBar().push_back(_indexToBar()[birth]); + } else { + auto& barIt = _indexToBar().at(birth); + barIt->death = death; + _indexToBar().try_emplace(death, barIt); // list so iterators are stable + } + } +} + +template +inline void RU_matrix::_add_bar(dimension_type dim, pos_index birth) +{ + if constexpr (Master_matrix::Option_list::has_column_pairings) { + _barcode().emplace_back(dim, birth, -1); + if constexpr (Master_matrix::hasFixedBarcode || !Master_matrix::Option_list::has_removable_columns) { + _indexToBar().push_back(_barcode().size() - 1); + } else { + _indexToBar().try_emplace(birth, --_barcode().end()); + } + } +} + +template +inline constexpr typename RU_matrix::barcode_type& RU_matrix::_barcode() +{ + if constexpr (Master_matrix::Option_list::has_vine_update) + return swap_opt::template RU_pairing::barcode_; + else + return pair_opt::barcode_; +} + +template +inline constexpr typename RU_matrix::bar_dictionnary_type& RU_matrix::_indexToBar() +{ + if constexpr (Master_matrix::Option_list::has_vine_update) + return swap_opt::template RU_pairing::indexToBar_; + else + return pair_opt::indexToBar_; +} + +} // namespace persistence_matrix +} // namespace Gudhi + +#endif // PM_RU_MATRIX_H diff --git a/src/Persistence_matrix/include/gudhi/Persistence_matrix/ru_pairing.h b/src/Persistence_matrix/include/gudhi/Persistence_matrix/ru_pairing.h new file mode 100644 index 0000000000..8a89576b9d --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Persistence_matrix/ru_pairing.h @@ -0,0 +1,124 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file ru_pairing.h + * @author Hannah Schreiber + * @brief Contains the @ref RU_pairing class and @ref Dummy_ru_pairing structure. + */ + +#ifndef PM_RU_PAIRING_H +#define PM_RU_PAIRING_H + +#include //std::move + +namespace Gudhi { +namespace persistence_matrix { + +/** + * @ingroup persistence_matrix + * + * @brief Empty structure. + * Inheritated instead of @ref RU_pairing, when the computation of the barcode was not enabled or if the pairing + * is already managed by the vine update classes. + */ +struct Dummy_ru_pairing { + friend void swap([[maybe_unused]] Dummy_ru_pairing& d1, [[maybe_unused]] Dummy_ru_pairing& d2) {} +}; + +/** + * @class RU_pairing ru_pairing.h gudhi/Persistence_matrix/ru_pairing.h + * @ingroup persistence_matrix + * + * @brief Class managing the barcode for @ref RU_matrix if the option was enabled. + * + * @tparam Master_matrix An instanciation of @ref Matrix from which all types and options are deduced. + */ +template +class RU_pairing +{ + public: + using barcode_type = typename Master_matrix::barcode_type; /**< Barcode type. */ + + /** + * @brief Default constructor. + */ + RU_pairing(); + /** + * @brief Copy constructor. + * + * @param matrixToCopy Matrix to copy. + */ + RU_pairing(const RU_pairing& matrixToCopy); + /** + * @brief Move constructor. + * + * @param other Matrix to move. + */ + RU_pairing(RU_pairing&& other) noexcept; + + /** + * @brief Returns the current barcode which is maintained at any insertion, removal or vine swap. + * + * @return Const reference to the barcode. + */ + const barcode_type& get_current_barcode() const; + + /** + * @brief Assign operator. + */ + RU_pairing& operator=(RU_pairing other); + /** + * @brief Swap operator. + */ + friend void swap(RU_pairing& pairing1, RU_pairing& pairing2) { + pairing1.barcode_.swap(pairing2.barcode_); + pairing1.indexToBar_.swap(pairing2.indexToBar_); + } + + protected: + using dictionnary_type = typename Master_matrix::bar_dictionnary_type; + + barcode_type barcode_; /**< Bar container. */ + dictionnary_type indexToBar_; /**< Map from @ref MatIdx index to bar index. */ +}; + +template +inline RU_pairing::RU_pairing() +{} + +template +inline RU_pairing::RU_pairing(const RU_pairing& matrixToCopy) + : barcode_(matrixToCopy.barcode_), indexToBar_(matrixToCopy.indexToBar_) +{} + +template +inline RU_pairing::RU_pairing(RU_pairing&& other) noexcept + : barcode_(std::move(other.barcode_)), indexToBar_(std::move(other.indexToBar_)) +{} + +template +inline const typename RU_pairing::barcode_type& RU_pairing::get_current_barcode() const +{ + return barcode_; +} + +template +inline RU_pairing& RU_pairing::operator=(RU_pairing other) +{ + barcode_.swap(other.barcode_); + indexToBar_.swap(other.indexToBar_); + return *this; +} + +} // namespace persistence_matrix +} // namespace Gudhi + +#endif // PM_RU_PAIRING_H diff --git a/src/Persistence_matrix/include/gudhi/Persistence_matrix/ru_rep_cycles.h b/src/Persistence_matrix/include/gudhi/Persistence_matrix/ru_rep_cycles.h new file mode 100644 index 0000000000..c8d6a6abbe --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Persistence_matrix/ru_rep_cycles.h @@ -0,0 +1,203 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file ru_rep_cycles.h + * @author Hannah Schreiber + * @brief Contains the @ref RU_representative_cycles class and @ref Dummy_ru_representative_cycles structure. + */ + +#ifndef PM_RU_REP_CYCLES_H +#define PM_RU_REP_CYCLES_H + +#include //std::move +#include //std::sort +#include + +namespace Gudhi { +namespace persistence_matrix { + +/** + * @ingroup persistence_matrix + * + * @brief Empty structure. + * Inheritated instead of @ref RU_representative_cycles, when the computation of the representative cycles + * were not enabled. + */ +struct Dummy_ru_representative_cycles { + friend void swap([[maybe_unused]] Dummy_ru_representative_cycles& d1, + [[maybe_unused]] Dummy_ru_representative_cycles& d2) {} +}; + +// TODO: add coefficients ? Only Z2 token into account for now. +/** + * @class RU_representative_cycles ru_rep_cycles.h gudhi/Persistence_matrix/ru_rep_cycles.h + * @ingroup persistence_matrix + * + * @brief Class managing the representative cycles for @ref RU_matrix if the option was enabled. + * + * @tparam Master_matrix An instanciation of @ref Matrix from which all types and options are deduced. + */ +template +class RU_representative_cycles +{ + public: + using index = typename Master_matrix::index; /**< @ref MatIdx index type. */ + using Bar = typename Master_matrix::Bar; /**< Bar type. */ + using cycle_type = typename Master_matrix::cycle_type; /**< Cycle type. */ + + /** + * @brief Default constructor. + */ + RU_representative_cycles(); + /** + * @brief Copy constructor. + * + * @param matrixToCopy Matrix to copy. + */ + RU_representative_cycles(const RU_representative_cycles& matrixToCopy); + /** + * @brief Move constructor. + * + * @param other Matrix to move. + */ + RU_representative_cycles(RU_representative_cycles&& other) noexcept; + + /** + * @brief Computes the current representative cycles of the matrix. + */ + void update_representative_cycles(); + + /** + * @brief Returns the current representative cycles. If the matrix is modified later after the first call, + * @ref update_representative_cycles has to be called to update the returned cycles. + * + * @return A const reference to a vector of @ref Matrix::cycle_type containing all representative cycles. + */ + const std::vector& get_representative_cycles(); + /** + * @brief Returns the representative cycle corresponding to the given bar. + * If the matrix is modified later after the first call, + * @ref update_representative_cycles has to be called to update the returned cycles. + * + * @param bar Bar corresponding to the wanted representative cycle. + * @return A const reference to the representative cycle. + */ + const cycle_type& get_representative_cycle(const Bar& bar); + + /** + * @brief Assign operator. + */ + RU_representative_cycles& operator=(RU_representative_cycles other); + /** + * @brief Swap operator. + */ + friend void swap(RU_representative_cycles& base1, RU_representative_cycles& base2) { + base1.representativeCycles_.swap(base2.representativeCycles_); + base1.birthToCycle_.swap(base2.birthToCycle_); + } + + private: + using ru_matrix = typename Master_matrix::RU_matrix_type; + + std::vector representativeCycles_; /**< Cycle container. */ + std::vector birthToCycle_; /**< Map from birth index to cycle index. */ + + constexpr ru_matrix* _matrix() { return static_cast(this); } + constexpr const ru_matrix* _matrix() const { return static_cast(this); } +}; + +template +inline RU_representative_cycles::RU_representative_cycles() +{} + +template +inline RU_representative_cycles::RU_representative_cycles( + const RU_representative_cycles& matrixToCopy) + : representativeCycles_(matrixToCopy.representativeCycles_), birthToCycle_(matrixToCopy.birthToCycle_) +{} + +template +inline RU_representative_cycles::RU_representative_cycles( + RU_representative_cycles&& other) noexcept + : representativeCycles_(std::move(other.representativeCycles_)), birthToCycle_(std::move(other.birthToCycle_)) +{} + +template +inline void RU_representative_cycles::update_representative_cycles() +{ + if constexpr (Master_matrix::Option_list::has_vine_update) { + birthToCycle_.clear(); + birthToCycle_.resize(_matrix()->reducedMatrixR_.get_number_of_columns(), -1); + index c = 0; + for (index i = 0; i < _matrix()->reducedMatrixR_.get_number_of_columns(); i++) { + if (_matrix()->reducedMatrixR_.is_zero_column(i)) { + birthToCycle_[i] = c; + ++c; + } + } + representativeCycles_.clear(); + representativeCycles_.resize(c); + for (index i = 0; i < _matrix()->mirrorMatrixU_.get_number_of_columns(); i++) { + for (const auto& cell : _matrix()->mirrorMatrixU_.get_column(i)) { + auto idx = birthToCycle_[cell.get_row_index()]; + if (idx != static_cast(-1)) { + representativeCycles_[idx].push_back(i); + } + } + } + } else { + birthToCycle_.clear(); + birthToCycle_.resize(_matrix()->reducedMatrixR_.get_number_of_columns(), -1); + for (index i = 0; i < _matrix()->reducedMatrixR_.get_number_of_columns(); i++) { + if (_matrix()->reducedMatrixR_.is_zero_column(i)) { + representativeCycles_.push_back(cycle_type()); + for (const auto& cell : _matrix()->mirrorMatrixU_.get_column(i)) { + representativeCycles_.back().push_back(cell.get_row_index()); + } + if constexpr (std::is_same_v || + std::is_same_v) + std::sort(representativeCycles_.back().begin(), representativeCycles_.back().end()); + birthToCycle_[i] = representativeCycles_.size() - 1; + } + } + } +} + +template +inline const std::vector::cycle_type>& +RU_representative_cycles::get_representative_cycles() +{ + if (representativeCycles_.empty()) update_representative_cycles(); + return representativeCycles_; +} + +template +inline const typename RU_representative_cycles::cycle_type& +RU_representative_cycles::get_representative_cycle(const Bar& bar) +{ + if (representativeCycles_.empty()) update_representative_cycles(); + return representativeCycles_[birthToCycle_[bar.birth]]; +} + +template +inline RU_representative_cycles& RU_representative_cycles::operator=( + RU_representative_cycles other) +{ + representativeCycles_.swap(other.representativeCycles_); + birthToCycle_.swap(other.birthToCycle_); + return *this; +} + +} // namespace persistence_matrix +} // namespace Gudhi + +#endif // PM_RU_REP_CYCLES_H diff --git a/src/Persistence_matrix/include/gudhi/Persistence_matrix/ru_vine_swap.h b/src/Persistence_matrix/include/gudhi/Persistence_matrix/ru_vine_swap.h new file mode 100644 index 0000000000..34eab5d3d6 --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/Persistence_matrix/ru_vine_swap.h @@ -0,0 +1,501 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file ru_vine_swap.h + * @author Hannah Schreiber + * @brief Contains the @ref RU_vine_swap class, as well as the + * @ref Dummy_ru_vine_swap and @ref Dummy_ru_vine_pairing structures. + */ + +#ifndef PM_RU_VINE_SWAP_H +#define PM_RU_VINE_SWAP_H + +#include //std::move +#include //std::conditional +#include +#include +#include //std::invalid_argument + +#include "ru_pairing.h" + +namespace Gudhi { +namespace persistence_matrix { + +/** + * @ingroup persistence_matrix + * + * @brief Empty structure. + * Inheritated instead of @ref RU_vine_swap, when vine swappes are not enabled. + */ +struct Dummy_ru_vine_swap { + friend void swap([[maybe_unused]] Dummy_ru_vine_swap& d1, [[maybe_unused]] Dummy_ru_vine_swap& d2) {} +}; + +/** + * @ingroup persistence_matrix + * + * @brief Empty structure. + * Inheritated instead of @ref RU_pairing, when the barcode is not stored. + */ +struct Dummy_ru_vine_pairing { + friend void swap([[maybe_unused]] Dummy_ru_vine_pairing& d1, [[maybe_unused]] Dummy_ru_vine_pairing& d2) {} +}; + +/** + * @class RU_vine_swap ru_vine_swap.h gudhi/Persistence_matrix/ru_vine_swap.h + * @ingroup persistence_matrix + * + * @brief Class managing the vine swaps for @ref RU_matrix. + * + * @tparam Master_matrix An instanciation of @ref Matrix from which all types and options are deduced. + */ +template +class RU_vine_swap : public std::conditional, + Dummy_ru_vine_pairing + >::type +{ + public: + using index = typename Master_matrix::index; /**< @ref MatIdx index type. */ + using id_index = typename Master_matrix::id_index; /**< @ref IDIdx index type. */ + using pos_index = typename Master_matrix::pos_index; /**< @ref PosIdx index type. */ + + /** + * @brief Default constructor. + */ + RU_vine_swap(); + /** + * @brief Copy constructor. + * + * @param matrixToCopy Matrix to copy. + */ + RU_vine_swap(const RU_vine_swap& matrixToCopy); + /** + * @brief Move constructor. + * + * @param other Matrix to move. + */ + RU_vine_swap(RU_vine_swap&& other) noexcept; + + /** + * @brief Does the same than @ref vine_swap, but assumes that the swap is non trivial and + * therefore skips a part of the case study. + * + * @param position @ref PosIdx index of the first face to swap. The second one has to be at `position + 1`. + * @return true If the barcode changed from the swap. + * @return false Otherwise. + */ + bool vine_swap_with_z_eq_1_case(pos_index index); + /** + * @brief Does a vine swap between two faces which are consecutives in the filtration. + * Roughly, if \f$ F \f$ is the current filtration represented by the matrix, the method modifies the matrix + * such that the new state corresponds to a valid state for the filtration \f$ F' \f$ equal to \f$ F \f$ but + * with the two faces at position `position` and `position + 1` swapped. Of course, the two faces should + * not have a face/coface relation which each other ; \f$ F' \f$ has to be a valid filtration. + * See @cite vineyards for more information about vine and vineyards. + * + * @param position @ref PosIdx index of the first face to swap. The second one has to be at `position + 1`. + * @return true If the barcode changed from the swap. + * @return false Otherwise. + */ + bool vine_swap(pos_index index); + + /** + * @brief Assign operator. + */ + RU_vine_swap& operator=(RU_vine_swap other); + /** + * @brief Swap operator. + */ + friend void swap(RU_vine_swap& swap1, RU_vine_swap& swap2) { + if constexpr (Master_matrix::Option_list::has_column_pairings) { + swap(static_cast&>(swap1), static_cast&>(swap2)); + } + swap1.positionToRowIdx_.swap(swap2.positionToRowIdx_); + } + + protected: + // only usefull when simplex id does not corresponds to position, so feels kinda useless most of the time... + // TODO: as it takes up some non trivial memory, see if this should not be optional + // or only remember the positions with a difference. but then a map is needed, ie find instead of []. + std::vector positionToRowIdx_; /**< Map from @ref PosIdx index to row index. */ + + private: + using RUP = typename std::conditional, + Dummy_ru_vine_pairing + >::type; + using ru_matrix = typename Master_matrix::RU_matrix_type; + + bool _is_paired(index columnIndex); + + void _swap_at_index(index columnIndex); + void _add_to(index sourceIndex, index targetIndex); + void _positive_transpose(index columnIndex); + void _negative_transpose(index columnIndex); + void _positive_negative_transpose(index columnIndex); + void _negative_positive_transpose(index columnIndex); + bool _positive_vine_swap(index columnIndex); + bool _negative_vine_swap(index columnIndex); + bool _positive_negative_vine_swap(index columnIndex); + bool _negative_positive_vine_swap(index columnIndex); + + pos_index& _death(pos_index simplexIndex); + pos_index& _birth(pos_index simplexIndex); + pos_index _get_death(index simplexIndex); + pos_index _get_birth(index simplexIndex); + + constexpr ru_matrix* _matrix() { return static_cast(this); } + constexpr const ru_matrix* _matrix() const { return static_cast(this); } +}; + +template +inline RU_vine_swap::RU_vine_swap() : RUP() +{} + +template +inline RU_vine_swap::RU_vine_swap(const RU_vine_swap& matrixToCopy) + : RUP(static_cast(matrixToCopy)), positionToRowIdx_(matrixToCopy.positionToRowIdx_) +{} + +template +inline RU_vine_swap::RU_vine_swap(RU_vine_swap&& other) noexcept + : RUP(std::move(static_cast(other))), positionToRowIdx_(std::move(other.positionToRowIdx_)) +{} + +template +inline bool RU_vine_swap::vine_swap_with_z_eq_1_case(pos_index index) +{ + GUDHI_CHECK(index < _matrix()->reducedMatrixR_.get_number_of_columns() - 1, + std::invalid_argument("RU_vine_swap::vine_swap_with_z_eq_1_case - Index to swap out of bound.")); + + bool iIsPositive = _matrix()->reducedMatrixR_.is_zero_column(index); + bool iiIsPositive = _matrix()->reducedMatrixR_.is_zero_column(index + 1); + + if (iIsPositive && iiIsPositive) { + _matrix()->mirrorMatrixU_.zero_cell(index, positionToRowIdx_[index + 1]); + return _positive_vine_swap(index); + } else if (!iIsPositive && !iiIsPositive) { + return _negative_vine_swap(index); + } else if (iIsPositive && !iiIsPositive) { + return _positive_negative_vine_swap(index); + } else { + return _negative_positive_vine_swap(index); + } +} + +template +inline bool RU_vine_swap::vine_swap(pos_index index) +{ + GUDHI_CHECK(index < _matrix()->reducedMatrixR_.get_number_of_columns() - 1, + std::invalid_argument("RU_vine_swap::vine_swap - Index to swap out of bound.")); + + bool iIsPositive = _matrix()->reducedMatrixR_.is_zero_column(index); + bool iiIsPositive = _matrix()->reducedMatrixR_.is_zero_column(index + 1); + + if (iIsPositive && iiIsPositive) { + if (_matrix()->reducedMatrixR_.get_column_dimension(index) != + _matrix()->reducedMatrixR_.get_column_dimension(index + 1)) { + _positive_transpose(index); + _swap_at_index(index); + return true; + } + if (!_matrix()->mirrorMatrixU_.is_zero_cell(index, positionToRowIdx_[index + 1])) { + _matrix()->mirrorMatrixU_.zero_cell(index, positionToRowIdx_[index + 1]); + } + return _positive_vine_swap(index); + } else if (!iIsPositive && !iiIsPositive) { + if (_matrix()->reducedMatrixR_.get_column_dimension(index) != + _matrix()->reducedMatrixR_.get_column_dimension(index + 1) || + _matrix()->mirrorMatrixU_.is_zero_cell(index, positionToRowIdx_[index + 1])) { + _negative_transpose(index); + _swap_at_index(index); + return true; + } + return _negative_vine_swap(index); + } else if (iIsPositive && !iiIsPositive) { + if (_matrix()->reducedMatrixR_.get_column_dimension(index) != + _matrix()->reducedMatrixR_.get_column_dimension(index + 1) || + _matrix()->mirrorMatrixU_.is_zero_cell(index, positionToRowIdx_[index + 1])) { + _positive_negative_transpose(index); + _swap_at_index(index); + return true; + } + return _positive_negative_vine_swap(index); + } else { + if (_matrix()->reducedMatrixR_.get_column_dimension(index) != + _matrix()->reducedMatrixR_.get_column_dimension(index + 1) || + _matrix()->mirrorMatrixU_.is_zero_cell(index, positionToRowIdx_[index + 1])) { + _negative_positive_transpose(index); + _swap_at_index(index); + return true; + } + return _negative_positive_vine_swap(index); + } +} + +template +inline RU_vine_swap& RU_vine_swap::operator=(RU_vine_swap other) +{ + RUP::operator=(other); + positionToRowIdx_.swap(other.positionToRowIdx_); + return *this; +} + +template +inline bool RU_vine_swap::_is_paired(index columnIndex) +{ + if constexpr (Master_matrix::Option_list::has_column_pairings) { + return _get_death(columnIndex) != static_cast(-1); + } else { + if (!_matrix()->reducedMatrixR_.is_zero_column(columnIndex)) return true; + + if constexpr (Master_matrix::Option_list::has_map_column_container) { + if (_matrix()->pivotToColumnIndex_.find(columnIndex) == _matrix()->pivotToColumnIndex_.end()) return false; + } else { + if (_matrix()->pivotToColumnIndex_.operator[](columnIndex) == static_cast(-1)) return false; + } + + return true; + } +} + +template +inline void RU_vine_swap::_swap_at_index(index columnIndex) +{ + _matrix()->reducedMatrixR_.swap_columns(columnIndex, columnIndex + 1); + _matrix()->reducedMatrixR_.swap_rows(positionToRowIdx_[columnIndex], positionToRowIdx_[columnIndex + 1]); + _matrix()->mirrorMatrixU_.swap_columns(columnIndex, columnIndex + 1); + _matrix()->mirrorMatrixU_.swap_rows(positionToRowIdx_[columnIndex], positionToRowIdx_[columnIndex + 1]); +} + +template +inline void RU_vine_swap::_add_to(index sourceIndex, index targetIndex) +{ + _matrix()->reducedMatrixR_.add_to(sourceIndex, targetIndex); + _matrix()->mirrorMatrixU_.add_to(targetIndex, sourceIndex); +} + +template +inline void RU_vine_swap::_positive_transpose(index columnIndex) +{ + if constexpr (Master_matrix::Option_list::has_map_column_container) { + if (_is_paired(columnIndex) && _is_paired(columnIndex + 1)) { + std::swap(_matrix()->pivotToColumnIndex_.at(columnIndex), _matrix()->pivotToColumnIndex_.at(columnIndex + 1)); + } else if (_is_paired(columnIndex)) { + _matrix()->pivotToColumnIndex_.emplace(columnIndex + 1, _matrix()->pivotToColumnIndex_.at(columnIndex)); + _matrix()->pivotToColumnIndex_.erase(columnIndex); + } else if (_is_paired(columnIndex + 1)) { + _matrix()->pivotToColumnIndex_.emplace(columnIndex, _matrix()->pivotToColumnIndex_.at(columnIndex + 1)); + _matrix()->pivotToColumnIndex_.erase(columnIndex + 1); + } + } else { + std::swap(_matrix()->pivotToColumnIndex_.operator[](columnIndex), + _matrix()->pivotToColumnIndex_.operator[](columnIndex + 1)); + } + + if constexpr (Master_matrix::Option_list::has_column_pairings) { + _birth(columnIndex) = columnIndex + 1; + _birth(columnIndex + 1) = columnIndex; + std::swap(RUP::indexToBar_.at(columnIndex), RUP::indexToBar_.at(columnIndex + 1)); + } +} + +template +inline void RU_vine_swap::_negative_transpose(index columnIndex) +{ + if constexpr (Master_matrix::Option_list::has_column_pairings) { + _death(columnIndex) = columnIndex + 1; + _death(columnIndex + 1) = columnIndex; + std::swap(RUP::indexToBar_.at(columnIndex), RUP::indexToBar_.at(columnIndex + 1)); + } + std::swap(_matrix()->pivotToColumnIndex_.at(_get_birth(columnIndex)), + _matrix()->pivotToColumnIndex_.at(_get_birth(columnIndex + 1))); +} + +template +inline void RU_vine_swap::_positive_negative_transpose(index columnIndex) +{ + _matrix()->pivotToColumnIndex_.at(_get_birth(columnIndex + 1)) = columnIndex; + if constexpr (Master_matrix::Option_list::has_map_column_container) { + if (_is_paired(columnIndex)) { + _matrix()->pivotToColumnIndex_.emplace(columnIndex + 1, _matrix()->pivotToColumnIndex_.at(columnIndex)); + _matrix()->pivotToColumnIndex_.erase(columnIndex); + } + } else { + _matrix()->pivotToColumnIndex_.operator[](columnIndex + 1) = _matrix()->pivotToColumnIndex_.operator[](columnIndex); + _matrix()->pivotToColumnIndex_.operator[](columnIndex) = -1; + } + + if constexpr (Master_matrix::Option_list::has_column_pairings) { + _birth(columnIndex) = columnIndex + 1; + _death(columnIndex + 1) = columnIndex; + std::swap(RUP::indexToBar_.at(columnIndex), RUP::indexToBar_.at(columnIndex + 1)); + } +} + +template +inline void RU_vine_swap::_negative_positive_transpose(index columnIndex) +{ + _matrix()->pivotToColumnIndex_.at(_get_birth(columnIndex)) = columnIndex + 1; + if constexpr (Master_matrix::Option_list::has_map_column_container) { + if (_is_paired(columnIndex + 1)) { + _matrix()->pivotToColumnIndex_.emplace(columnIndex, _matrix()->pivotToColumnIndex_.at(columnIndex + 1)); + _matrix()->pivotToColumnIndex_.erase(columnIndex + 1); + } + } else { + _matrix()->pivotToColumnIndex_.operator[](columnIndex) = _matrix()->pivotToColumnIndex_.operator[](columnIndex + 1); + _matrix()->pivotToColumnIndex_.operator[](columnIndex + 1) = -1; + } + + if constexpr (Master_matrix::Option_list::has_column_pairings) { + _death(columnIndex) = columnIndex + 1; + _birth(columnIndex + 1) = columnIndex; + std::swap(RUP::indexToBar_.at(columnIndex), RUP::indexToBar_.at(columnIndex + 1)); + } +} + +template +inline bool RU_vine_swap::_positive_vine_swap(index columnIndex) +{ + const pos_index iDeath = _get_death(columnIndex); + const pos_index iiDeath = _get_death(columnIndex + 1); + + if (iDeath != static_cast(-1) && iiDeath != static_cast(-1) && + !(_matrix()->reducedMatrixR_.is_zero_cell(iiDeath, positionToRowIdx_[columnIndex]))) { + if (iDeath < iiDeath) { + _swap_at_index(columnIndex); + _add_to(iDeath, iiDeath); + _positive_transpose(columnIndex); + return true; + } + + _swap_at_index(columnIndex); + _add_to(iiDeath, iDeath); + return false; + } + + _swap_at_index(columnIndex); + + if (iDeath != static_cast(-1) || iiDeath == static_cast(-1) || + _matrix()->reducedMatrixR_.is_zero_cell(iiDeath, positionToRowIdx_[columnIndex + 1])) { + _positive_transpose(columnIndex); + return true; + } + return false; +} + +template +inline bool RU_vine_swap::_negative_vine_swap(index columnIndex) +{ + const pos_index iBirth = _get_birth(columnIndex); + const pos_index iiBirth = _get_birth(columnIndex + 1); + + _add_to(columnIndex, columnIndex + 1); + _swap_at_index(columnIndex); + + if (iBirth < iiBirth) { + _negative_transpose(columnIndex); + return true; + } + + _add_to(columnIndex, columnIndex + 1); + + return false; +} + +template +inline bool RU_vine_swap::_positive_negative_vine_swap(index columnIndex) +{ + _matrix()->mirrorMatrixU_.zero_cell(columnIndex, positionToRowIdx_[columnIndex + 1]); + + _swap_at_index(columnIndex); + _positive_negative_transpose(columnIndex); + + return true; +} + +template +inline bool RU_vine_swap::_negative_positive_vine_swap(index columnIndex) +{ + _add_to(columnIndex, columnIndex + 1); // useless for R? + _swap_at_index(columnIndex); // if additions not made for R, do not swap R columns, just rows + _add_to(columnIndex, columnIndex + 1); // useless for R? + + return false; +} + +template +inline typename RU_vine_swap::pos_index& RU_vine_swap::_death(pos_index simplexIndex) +{ + static_assert(Master_matrix::Option_list::has_column_pairings, "Pairing necessary to modify death value."); + + if constexpr (Master_matrix::Option_list::has_removable_columns) { + return RUP::indexToBar_.at(simplexIndex)->death; + } else { + return RUP::barcode_.at(RUP::indexToBar_.at(simplexIndex)).death; + } +} + +template +inline typename RU_vine_swap::pos_index& RU_vine_swap::_birth(pos_index simplexIndex) +{ + static_assert(Master_matrix::Option_list::has_column_pairings, "Pairing necessary to modify birth value."); + + if constexpr (Master_matrix::Option_list::has_removable_columns) { + return RUP::indexToBar_.at(simplexIndex)->birth; + } else { + return RUP::barcode_.at(RUP::indexToBar_.at(simplexIndex)).birth; + } +} + +template +inline typename RU_vine_swap::pos_index RU_vine_swap::_get_death(index simplexIndex) +{ + if constexpr (Master_matrix::Option_list::has_column_pairings) { + if constexpr (Master_matrix::Option_list::has_removable_columns) { + return RUP::indexToBar_.at(simplexIndex)->death; + } else { + return RUP::barcode_.at(RUP::indexToBar_.at(simplexIndex)).death; + } + } else { + if (!_matrix()->reducedMatrixR_.is_zero_column(simplexIndex)) + return _matrix()->reducedMatrixR_.get_column(simplexIndex).get_pivot(); + + if constexpr (Master_matrix::Option_list::has_map_column_container) { + auto it = _matrix()->pivotToColumnIndex_.find(simplexIndex); + if (it == _matrix()->pivotToColumnIndex_.end()) return -1; + return it->second; + } else { + return _matrix()->pivotToColumnIndex_.operator[](simplexIndex); + } + } +} + +template +inline typename RU_vine_swap::pos_index RU_vine_swap::_get_birth( + index negativeSimplexIndex) +{ + if constexpr (Master_matrix::Option_list::has_column_pairings) { + if constexpr (Master_matrix::Option_list::has_removable_columns) { + return RUP::indexToBar_.at(negativeSimplexIndex)->birth; + } else { + return RUP::barcode_.at(RUP::indexToBar_.at(negativeSimplexIndex)).birth; + } + } else { + return _matrix()->reducedMatrixR_.get_pivot(negativeSimplexIndex); + } +} + +} // namespace persistence_matrix +} // namespace Gudhi + +#endif // PM_RU_VINE_SWAP_H diff --git a/src/Persistence_matrix/include/gudhi/matrix.h b/src/Persistence_matrix/include/gudhi/matrix.h new file mode 100644 index 0000000000..3154434634 --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/matrix.h @@ -0,0 +1,2106 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** @file matrix.h + * @author Hannah Schreiber + * @brief Contains @ref Gudhi::persistence_matrix::Matrix class. + */ + +#ifndef MASTER_MATRIX_H +#define MASTER_MATRIX_H + +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/// Gudhi namespace. +namespace Gudhi { +/// Persistence matrix namespace. +namespace persistence_matrix { + +/** + * @class Matrix persistence_matrix.h gudhi/persistence_matrix.h + * @ingroup persistence_matrix + * + * @brief Data structure for matrices, and in particular thought for matrices representing filtered complexes + * in order to compute persistence and/or representative cycles. + * + * @anchor mp_matrices __%Matrix types:__ + * + * The are roughly three types of matrices available and one is selected automatically depending on the options chosen: + * - @anchor basematrix a @ref basematrix "basic matrix" which can represent any matrix and therefore will not make any + * assumption on its content. It is the only matrix type with the option of column compression (as it is the only one + * where it makes sense). This type is choosen by default when none of the homology related options are set to true: + * @ref PersistenceMatrixOptions::has_column_pairings, @ref PersistenceMatrixOptions::has_vine_update and + * @ref PersistenceMatrixOptions::can_retrieve_representative_cycles. + * - @anchor boundarymatrix a @ref boundarymatrix "boundary matrix" @f$ B = R \cdot U @f$ which either stores only + * @f$ R @f$ or the whole decomposition @f$ R @f$ and @f$ U @f$ depending on the options. This type is selected if + * @ref PersistenceMatrixOptions::is_of_boundary_type is set to true and at least one of the following options: + * @ref PersistenceMatrixOptions::has_column_pairings, @ref PersistenceMatrixOptions::has_vine_update and + * @ref PersistenceMatrixOptions::can_retrieve_representative_cycles. If only + * @ref PersistenceMatrixOptions::has_column_pairings is true, then only @f$ R @f$ is stored, but if either + * @ref PersistenceMatrixOptions::has_vine_update or @ref PersistenceMatrixOptions::can_retrieve_representative_cycles + * is true, then @f$ U @f$ also needs to be stored. Note that the option + * @ref PersistenceMatrixOptions::column_indexation_type will produce a small overhead when set to + * @ref Column_indexation_types::IDENTIFIER. + * - @anchor chainmatrix a @ref chainmatrix "chain complex matrix" representing a "compatible base" of a filtered chain + * complex (see @cite zigzag). This matrix is deduced from the boundary matrix and therefore encodes more or less the + * same information but differently and can therefore be better suited for certain applications. This type can be used + * the same way than the precedent type, only the option @ref PersistenceMatrixOptions::is_of_boundary_type has to be + * set to false. Note that the option @ref PersistenceMatrixOptions::column_indexation_type will produce a small + * overhead when set to @ref Column_indexation_types::POSITION or @ref Column_indexation_types::IDENTIFIER. + * + * @anchor mp_indexation __Indexation scheme:__ + * + * The indexation system for columns of the different matrix types can be a bit tricky and different methods will not + * always take the same type of index as input (for optimization purposes). So, to avoid confusion, we will name and + * define here the different possibilities, such that we can directly refer to it in the descriptions of the methods. + * Note that every column and row in a @ref boundarymatrix "boundary" or @ref chainmatrix "chain matrix" is always + * associated to a single simplex/face, so in order to avoid repeating formulations like "of the simplex associated to + * the column" all the time, we will amalgamate both notions together. + * + * Let @f$ c @f$ be a column. + * - @anchor MatIdx @ref MatIdx "MatIdx": This will correspond to the position of @f$ c @f$ in the matrix, i.e., + * @f$ underlying\_container[MatIdx] = c @f$. This will be the only public indexing scheme for + * @ref basematrix "basic matrices". + * - @anchor PosIdx @ref PosIdx "PosIdx": This will correspond to the relative position of @f$ c @f$ in the current + * filtration compared to the other columns, starting the count at 0. For @ref boundarymatrix "boundary matrices", + * @ref PosIdx will always be equal to @ref MatIdx, but this is not true for @ref chainmatrix "chain matrices" when + * swaps or removals were performed. + * - @anchor IDIdx @ref IDIdx "IDIdx": This will correspond to the ID of @f$ c @f$ in the complex used to identify it in + * the boundaries. If at the insertion of @f$ c @f$, its ID was not specified and it was the @f$ n^{th} @f$ insertion, + * it is assumed that the ID is @f$ n @f$ (which means that @ref IDIdx and @ref PosIdx will only start to differ when + * swaps or removals are performed). If an ID is specified at the insertion of @f$ c @f$, the ID is stored as the + * @ref IDIdx of @f$ c @f$. IDs can be freely choosen with the only restriction that they have to be strictly + * increasing in the order of the filtration at initialisation. + * + * In conclusion, with default values, if no vine swaps or removals occurs, all three indexing schemes are the same. + * + * @anchor rowindex Let @f$ r @f$ be a row. Rows are indexed in two ways depending only if the matrix is a + * @ref chainmatrix "chain matrix" or not. If the matrix is a @ref chainmatrix "chain matrix", @f$ r @f$ is always + * indexed by its ID, so it correspond to the @ref IDIdx indexing scheme. If the matrix is not a + * @ref chainmatrix "chain matrix", @f$ r @f$ will originaly also be indexed by the ID, but when a swap occurs, + * the rows also swap IDs and the new ID has to be used to access @f$ r @f$. This means that when the default + * @ref IDIdx scheme is used (the faces are numerated in order of appearence in the filtration starting at 0), + * the indexation of the rows correspond to @ref PosIdx. + * + * @tparam PersistenceMatrixOptions Structure encoding all the options of the matrix. + * See description of @ref PersistenceMatrixOptions for more details. + */ +template > +class Matrix { + public: + using Option_list = PersistenceMatrixOptions; //to make it accessible from the other classes + using index = typename PersistenceMatrixOptions::index_type; /**< Type of @ref MatIdx index. */ + using id_index = typename PersistenceMatrixOptions::index_type; /**< Type of @ref IDIdx index. */ + using pos_index = typename PersistenceMatrixOptions::index_type; /**< Type of @ref PosIdx index. */ + using dimension_type = typename PersistenceMatrixOptions::dimension_type; /**< Type for dimension value. */ + + /** + * @brief Coefficiants field type. + */ + using Field_operators = + typename std::conditional::type; + /** + * @brief Type of a field element. + */ + using element_type = typename Field_operators::element_type; + using characteristic_type = typename Field_operators::characteristic_type; + + // TODO: move outside? unify with other bar types in Gudhi? + /** + * @brief Type for a bar in the computed barcode. Stores the birth, death and dimension of the bar. + */ + struct Bar { + Bar() : dim(-1), birth(-1), death(-1) {} + + Bar(dimension_type dim, pos_index birth, pos_index death) : dim(dim), birth(birth), death(death) {} + + dimension_type dim; /**< Dimension of the bar.*/ + pos_index birth; /**< Birth index in the current filtration. */ + pos_index death; /**< Death index in the current filtration. */ + + inline friend std::ostream &operator<<(std::ostream &stream, const Bar& bar) { + stream << "[" << bar.dim << "] "; + stream << bar.birth << ", " << bar.death; + stream << "\n"; + return stream; + } + }; + + //tags for boost to associate a row and a column to a same cell + struct matrix_row_tag; + struct matrix_column_tag; + + using base_hook_matrix_row = + boost::intrusive::list_base_hook, + boost::intrusive::link_mode >; + using base_hook_matrix_list_column = + boost::intrusive::list_base_hook, + boost::intrusive::link_mode >; + using base_hook_matrix_set_column = + boost::intrusive::set_base_hook, + boost::intrusive::link_mode >; + + //Two dummies are necessary to avoid double inheritance as a cell can inherit both a row and a column hook. + struct Dummy_row_hook {}; + struct Dummy_column_hook {}; + + using row_hook_type = typename std::conditional::type; + using column_hook_type = typename std::conditional< + PersistenceMatrixOptions::column_type == Column_types::INTRUSIVE_LIST, + base_hook_matrix_list_column, + typename std::conditional::type + >::type; + + //Option to store the column index within the cell (additionnaly to the row index). Necessary only with row access. + using Cell_column_index_option = + typename std::conditional, + Dummy_cell_column_index_mixin + >::type; + //Option to store the value of the cell. + //Unnecessary for values in Z_2 as there are always 1 (0-valued cells are never stored). + using Cell_field_element_option = + typename std::conditional + >::type; + /** + * @brief Type of a matrix cell. See @ref Cell for a more detailed description. + */ + using Cell_type = Cell >; + + /** + * @brief Default cell constructor/destructor, using classic new and delete. + * For now, only used as default value for columns constructed independently outside of the matrix by the user. + * Could be used in the futur when parallel options are implemented, as usual pools are not thread safe. + */ + inline static New_cell_constructor defaultCellConstructor; + /** + * @brief Cell constructor/destructor used by the matrix. Uses a pool of cells to accelerate memory management, + * as cells are constructed and destroyed a lot during reduction, swaps or additions. + */ + using Cell_constructor = Pool_cell_constructor; + + /** + * @brief Type used to identify a cell, for exemple when inserting a boundary. + * If @ref PersistenceMatrixOptions::is_z2 is true, the type is an @ref IDIdx and corresponds to the row index of the + * cell (the cell value is assumed to be 1). If @ref PersistenceMatrixOptions::is_z2 is false, the type is a pair + * whose first element is the row index of the cell and the second element is the value of the cell (which again is + * assumed to be non-zero). The column index of the row is always deduced from the context in which the type is used. + */ + using cell_rep_type = typename std::conditional + >::type; + + /** + * @brief Compaires two cells by their position in the row. They are assume to be in the same row. + */ + struct RowCellComp { + bool operator()(const Cell_type& c1, const Cell_type& c2) const { + return c1.get_column_index() < c2.get_column_index(); + } + }; + + /** + * @brief Type of the rows stored in the matrix. Is either an intrusive list of @ref Cell_type (not ordered) if + * @ref PersistenceMatrixOptions::has_intrusive_rows is true, or a set of @ref Cell_type (ordered by + * @ref get_column_index) otherwise. + */ + using Row_type = + typename std::conditional, + boost::intrusive::base_hook + >, + std::set + >::type; + + using row_container_type = + typename std::conditional, + std::vector + >::type; + + //Row access at column level + using Row_access_option = + typename std::conditional >, + Dummy_row_access + >::type; + //Row access at matrix level + using Matrix_row_access_option = + typename std::conditional, + Dummy_matrix_row_access + >::type; + + template + using dictionnary_type = + typename std::conditional, + std::vector + >::type; + + static const bool isNonBasic = PersistenceMatrixOptions::has_column_pairings || + PersistenceMatrixOptions::has_vine_update || + PersistenceMatrixOptions::can_retrieve_representative_cycles; + + using Column_dimension_option = + typename std::conditional >, + Dummy_dimension_holder + >::type; + //Extra information needed for a column when the matrix is a @ref chainmatrix "chain matrix". + using Chain_column_option = + typename std::conditional >, + Dummy_chain_properties + >::type; + + using Heap_column_type = Heap_column >; + using List_column_type = List_column >; + using Vector_column_type = Vector_column >; + using Naive_vector_column_type = Naive_vector_column >; + using Set_column_type = Set_column >; + using Unordered_set_column_type = Unordered_set_column >; + using Intrusive_list_column_type = Intrusive_list_column >; + using Intrusive_set_column_type = Intrusive_set_column >; + + /** + * @brief Type of the columns stored in the matrix. The type depends on the value of + * @ref PersistenceMatrixOptions::column_type defined in the given options. See @ref Column_types for a more detailed + * description. All columns follow the @ref PersistenceMatrixColumn concept. + */ + using Column_type = typename std::conditional< + PersistenceMatrixOptions::column_type == Column_types::HEAP, + Heap_column_type, + typename std::conditional< + PersistenceMatrixOptions::column_type == Column_types::LIST, + List_column_type, + typename std::conditional< + PersistenceMatrixOptions::column_type == Column_types::SET, + Set_column_type, + typename std::conditional< + PersistenceMatrixOptions::column_type == Column_types::UNORDERED_SET, + Unordered_set_column_type, + typename std::conditional< + PersistenceMatrixOptions::column_type == Column_types::VECTOR, + Vector_column_type, + typename std::conditional< + PersistenceMatrixOptions::column_type == Column_types::INTRUSIVE_LIST, + Intrusive_list_column_type, + typename std::conditional< + PersistenceMatrixOptions::column_type == Column_types::NAIVE_VECTOR, + Naive_vector_column_type, + Intrusive_set_column_type + >::type + >::type + >::type + >::type + >::type + >::type + >::type; + + struct Column_z2_settings{ + Column_z2_settings() : cellConstructor() {} + Column_z2_settings([[maybe_unused]] characteristic_type characteristic) : cellConstructor() {} + Column_z2_settings(const Column_z2_settings& toCopy) : cellConstructor() {} + + Cell_constructor cellConstructor; //will be replaced by more specific allocators depending on the column type. + }; + + struct Column_zp_settings { + Column_zp_settings() : operators(), cellConstructor() {} + //purposely triggers operators() instead of operators(characteristic) as the "dummy" values for the different + //operators can be different from -1. + Column_zp_settings(characteristic_type characteristic) : operators(), cellConstructor() { + if (characteristic != static_cast(-1)) operators.set_characteristic(characteristic); + } + Column_zp_settings(const Column_zp_settings& toCopy) + : operators(toCopy.operators.get_characteristic()), cellConstructor() {} + + Field_operators operators; + Cell_constructor cellConstructor; //will be replaced by more specific allocators depending on the column type. + }; + + // struct Column_z2_with_rows_settings { + // Column_z2_with_rows_settings() : cellConstructor(), rows(nullptr) {} + // Column_z2_with_rows_settings([[maybe_unused]] characteristic_type characteristic) + // : cellConstructor(), rows(nullptr) {} + + // Cell_constructor cellConstructor; + // row_container_type* rows; + // }; + + // struct Column_zp_with_rows_settings { + // Column_zp_with_rows_settings() : operators(), cellConstructor(), rows(nullptr) {} + // Column_zp_with_rows_settings(characteristic_type characteristic) + // : operators(characteristic), cellConstructor(), rows(nullptr) {} + + // Field_operators operators; + // Cell_constructor cellConstructor; + // row_container_type* rows; + // }; + + //To prepare a more flexible use of the column types later (custom allocators depending on the column type etc.) + using Column_settings = typename std::conditional< + PersistenceMatrixOptions::is_z2, + Column_z2_settings, + Column_zp_settings + >::type; + + // using Column_settings = typename std::conditional< + // PersistenceMatrixOptions::is_z2, + // typename std::conditional::type, + // typename std::conditional::type + // >::type; + + using column_container_type = + typename std::conditional, + std::vector + >::type; + + static const bool hasFixedBarcode = Option_list::is_of_boundary_type && !PersistenceMatrixOptions::has_vine_update; + /** + * @brief Type of the computed barcode. It is either a list of @ref Matrix::Bar or a vector of @ref Matrix::Bar, + * depending if bars need to be removed from the container as some point or not. + */ + using barcode_type = + typename std::conditional, + typename std::conditional, + std::vector + >::type + >::type; + using bar_dictionnary_type = + typename std::conditional, //RU + std::unordered_map //boundary + >::type, + typename std::conditional, + std::vector + >::type + >::type; + + //default type for boundaries to permit list initialization directly in function parameters + using boundary_type = typename std::conditional, + std::initializer_list > + >::type; + + //i.e. is simple @ref boundarymatrix "boundary matrix". Also, only needed because of the reduction algorithm. + //TODO: remove the necessity and recalculate when needed or keep it like that? + static const bool maxDimensionIsNeeded = + PersistenceMatrixOptions::has_column_pairings && PersistenceMatrixOptions::is_of_boundary_type && + !PersistenceMatrixOptions::has_vine_update && !PersistenceMatrixOptions::can_retrieve_representative_cycles; + + using Matrix_dimension_option = typename std::conditional< + PersistenceMatrixOptions::has_matrix_maximal_dimension_access || maxDimensionIsNeeded, + typename std::conditional, + Matrix_max_dimension_holder + >::type, + Dummy_matrix_dimension_holder + >::type; + + using Base_matrix_type = + typename std::conditional >, + Base_matrix > + >::type; + using Boundary_matrix_type = Boundary_matrix >; + using RU_matrix_type = RU_matrix >; + using Chain_matrix_type = Chain_matrix >; + + template + using Base_swap_option = + typename std::conditional, Base>, + Dummy_base_swap + >::type; + using Base_pairing_option = + typename std::conditional >, + Dummy_base_pairing + >::type; + + using RU_pairing_option = + typename std::conditional >, + Dummy_ru_pairing + >::type; + using RU_vine_swap_option = + typename std::conditional >, + Dummy_ru_vine_swap + >::type; + using RU_representative_cycles_option = + typename std::conditional >, + Dummy_ru_representative_cycles + >::type; + + using Chain_pairing_option = + typename std::conditional >, + Dummy_chain_pairing + >::type; + using Chain_vine_swap_option = typename std::conditional >, + Dummy_chain_vine_swap + >::type; + using Chain_representative_cycles_option = + typename std::conditional >, + Dummy_chain_representative_cycles + >::type; + + /** + * @brief Type of a representative cycle. Vector of @ref rowindex "row indices". + */ + using cycle_type = std::vector; //TODO: add coefficients + + //Return types to factorize the corresponding methods + + //The returned column is `const` if the matrix uses column compression + using returned_column_type = + typename std::conditional::type; + //The returned row is `const` if the matrix uses column compression + using returned_row_type = + typename std::conditional::type; + //If the matrix is a chain matrix, the insertion method returns the pivots of its unpaired columns used to reduce the + //inserted boundary. Otherwise, void. + using insertion_return_type = + typename std::conditional + >::type; + + /** + * @brief Default constructor. Initializes an empty matrix. + */ + Matrix(); + /** + * @brief Constructs a new matrix from the given ranges of @ref cell_rep_type. Each range corresponds to a column (the + * order of the ranges are preserved). The content of the ranges is assumed to be sorted by increasing IDs. If the + * columns are representing a boundary matrix, the IDs of the simplices are also assumed to be consecutifs, ordered by + * filtration value, starting with 0. + * + * See @ref mp_matrices "matrix descriptions" for futher details on how the given matrix is handled. + * + * @tparam Container_type Range type for @ref cell_rep_type ranges. Assumed to have a begin(), end() and size() + * method. + * @param columns For a @ref basematrix "base matrix", the columns are copied as is. If options related to homology + * are activated, @p columns is interpreted as a boundary matrix of a **simplicial** complex. In this case, + * `columns[i]` should store the boundary of simplex `i` as an ordered list of indices of its facets (again those + * indices correspond to their respective position in the matrix). Therefore the indices of the simplices are assumed + * to be consecutifs and starting with 0 (an empty boundary is interpreted as a vertex boundary and not as a non + * existing simplex). All dimensions up to the maximal dimension of interest have to be present. If only a higher + * dimension is of interest and not everything should be stored, then use the @ref insert_boundary method instead + * (after creating the matrix with the @ref Matrix(int numberOfColumns, characteristic_type characteristic) + * constructor preferably). If the persistence barcode has to be computed from this matrix, the simplices are also + * assumed to be ordered by appearance order in the filtration. Also, depending of the options, the matrix is + * eventually reduced on the fly or converted into a chain complex base, so the new matrix is not always identical to + * the old one. + * @param characteristic Characteristic of the coefficient field. Has to be specified if + * @ref PersistenceMatrixOptions::is_z2 is false. Default value is 11. Ignored if + * @ref PersistenceMatrixOptions::is_z2 is true. + */ + template + Matrix(const std::vector& columns, characteristic_type characteristic = 11); + /** + * @brief Constructs a new empty matrix and reserves space for the given number of columns. + * + * @param numberOfColumns Number of columns to reserve space for. + * @param characteristic Characteristic of the coefficient field. If not specified and + * @ref PersistenceMatrixOptions::is_z2 is false, the characteristic has to be set later with the use of + * @ref set_characteristic before calling for the first time a method needing it. Ignored if + * @ref PersistenceMatrixOptions::is_z2 is true. + */ + Matrix(int numberOfColumns, characteristic_type characteristic = static_cast(-1)); + /** + * @brief Constructs a new empty matrix with the given comparator functions. Only available when those comparators + * are necessary. + * + * That is, when **all** following options have following values: + * - @ref PersistenceMatrixOptions::is_of_boundary_type = false + * - @ref PersistenceMatrixOptions::has_vine_update = true + * - @ref PersistenceMatrixOptions::has_column_pairings = false + * + * Those comparators are necesseray to distinguish cases in a vine update. When the matrix is of + * @ref boundarymatrix "boundary type" or if the column pairing is activated (i.e., the barcode is stored), + * the comparators can be easily deduced without overhead. If neither are true, we assume that one has additional + * information outside of the matrix about the barcode to provide a better suited comparator adapted to the situation + * (as in the implementation of the Zigzag algorithm @cite zigzag for example.) + * + * @param birthComparator Method taking two @ref PosIdx indices as parameter and returns true if and only if the first + * face is associated to a bar with strictly smaller birth than the bar associated to the second one. + * @param deathComparator Method taking two @ref PosIdx indices as parameter and returns true if and only if the first + * face is associated to a bar with strictly smaller death than the bar associated to the second one. + */ + Matrix(const std::function& birthComparator, + const std::function& deathComparator); + /** + * @brief Constructs a new matrix from the given ranges with the given comparator functions. + * Only available when those comparators are necessary. + * + * That is, when **all** following options have following values: + * - @ref PersistenceMatrixOptions::is_of_boundary_type = false + * - @ref PersistenceMatrixOptions::has_vine_update = true + * - @ref PersistenceMatrixOptions::has_column_pairings = false + * + * See description of @ref Matrix(const std::vector& columns, characteristic_type characteristic) + * for more information about @p orderedBoundaries and + * @ref Matrix(const std::function&, const std::function&) + * for more information about the comparators. + * + * @tparam Boundary_type Range type for @ref cell_rep_type ranges. Assumed to have a begin(), end() and size() method. + * @param orderedBoundaries Vector of ordered boundaries in filtration order. Indexed continously starting at 0. + * @param birthComparator Method taking two @ref PosIdx indices as parameter and returns true if and only if the first + * face is associated to a bar with strictly smaller birth than the bar associated to the second one. + * @param deathComparator Method taking two @ref PosIdx indices as parameter and returns true if and only if the first + * face is associated to a bar with strictly smaller death than the bar associated to the second one. + * @param characteristic Characteristic of the coefficient field. Has to be specified if + * @ref PersistenceMatrixOptions::is_z2 is false. Default value is 11. + * Ignored if @ref PersistenceMatrixOptions::is_z2 is true. + */ + template + Matrix(const std::vector& orderedBoundaries, + const std::function& birthComparator, + const std::function& deathComparator, + characteristic_type characteristic = 11); + /** + * @brief Constructs a new empty matrix and reserves space for the given number of columns. + * Only available when those comparators are necessary. + * + * That is, when **all** following options have following values: + * - @ref PersistenceMatrixOptions::is_of_boundary_type = false + * - @ref PersistenceMatrixOptions::has_vine_update = true + * - @ref PersistenceMatrixOptions::has_column_pairings = false + * + * See description of + * @ref Matrix(const std::function&, const std::function&) + * for more information about the comparators. + * + * @param numberOfColumns Number of columns to reserve space for. + * @param birthComparator Method taking two @ref PosIdx indices as parameter and returns true if and only if the first + * face is associated to a bar with strictly smaller birth than the bar associated to the second one. + * @param deathComparator Method taking two @ref PosIdx indices as parameter and returns true if and only if the first + * face is associated to a bar with strictly smaller death than the bar associated to the second one. + * @param characteristic Characteristic of the coefficient field. If not specified and + * @ref PersistenceMatrixOptions::is_z2 is false, the characteristic has to be set later with the use of + * @ref set_characteristic before calling for the first time a method needing it. + * Ignored if @ref PersistenceMatrixOptions::is_z2 is true. + */ + Matrix(unsigned int numberOfColumns, + const std::function& birthComparator, + const std::function& deathComparator, + characteristic_type characteristic = static_cast(-1)); + /** + * @brief Copy constructor. + * + * @param matrixToCopy %Matrix to copy. + */ + Matrix(const Matrix& matrixToCopy); + /** + * @brief Move constructor. + * After the move, the given matrix will be empty. + * + * @param other %Matrix to move. + */ + Matrix(Matrix&& other) noexcept; + + ~Matrix(); + + //TODO: compatibily with multi fields: + // - set_characteristic(characteristic_type min, characteristic_type max) + // - readapt reduction? + /** + * @brief Sets the characteristic of the coefficient field if @ref PersistenceMatrixOptions::is_z2 is false, + * does nothing otherwise. + * Should be used if no characteristic could be specified at the creation of the empty matrix. + * Do not change the value of the characteristic once used. + * + * @warning The coefficient values stored in the matrix are stored after computing the corresponding modulo. + * Therefore, changing the characteristic after is very likely to invalidate all cell values. + * + * @param characteristic The characteristic to set. + */ + void set_characteristic(characteristic_type characteristic); + + // (TODO: if there is no row access and the column type corresponds to the internal column type of the matrix, + // moving the column instead of copying it should be possible. Is it worth implementing it?) + /** + * @brief Inserts a new ordered column at the end of the matrix by copying the given range of @ref cell_rep_type. + * The content of the range is assumed to be sorted by increasing ID value. + * + * Only available for @ref basematrix "base matrices". + * Otherwise use @ref insert_boundary which will deduce a new column from the boundary given. + * + * @tparam Container_type Range of @ref cell_rep_type. Assumed to have a begin(), end() and size() method. + * @param column Column to be inserted. + */ + template + void insert_column(const Container_type& column); + /** + * @brief Inserts a new ordered column at the given index by copying the given range of @ref cell_rep_type. + * There should not be any other column inserted at that index which was not explicitely removed before. + * The content of the range is assumed to be sorted by increasing ID value. + * + * Only available for @ref basematrix "base matrices" without column compression and without row access. + * + * @tparam Container_type Range of @ref cell_rep_type. Assumed to have a begin(), end() and size() method. + * @param column Column to be inserted. + * @param columnIndex @ref MatIdx index to which the column has to be inserted. + */ + template + void insert_column(const Container_type& column, index columnIndex); + //TODO: for simple boundary matrices, add an index pointing to the first column inserted after the last call of + //get_current_barcode to enable several calls to get_current_barcode + /** + * @brief Inserts at the end of the matrix a new ordered column corresponding to the given boundary. + * This means that it is assumed that this method is called on boundaries in the order of the filtration. + * It also assumes that the faces in the given boundary are identified by their relative position in the filtration, + * starting at 0. If it is not the case, use the other + * @ref insert_boundary(id_index faceIndex, const Boundary_type& boundary, dimension_type dim) "insert_boundary" + * instead by indicating the face ID used in the boundaries when the face is inserted. + * + * Different to the constructor, the boundaries do not have to come from a simplicial complex, but also from + * a more general cell complex. This includes cubical complexes or Morse complexes for example. + * + * The content of the new column will vary depending on the underlying @ref mp_matrices "type of the matrix": + * - If it is a @ref basematrix "basic matrix" type, the boundary is copied as it is, i.e., the method is equivalent + * to @ref insert_column. + * - If it is a @ref boundarymatrix "boundary type matrix" and only \f$ R \f$ is stored, the boundary is also just + * copied. The column will only be reduced later when the barcode is requested in order to apply some optimisations + * with the additional knowledge. Hence, the barcode will also not be updated, so call @ref get_current_barcode only + * when the matrix is complete. + * - If it is a @ref boundarymatrix "boundary type matrix" and both \f$ R \f$ and \f$ U \f$ are stored, the new + * boundary is stored in its reduced form and the barcode, if active, is also updated. + * - If it is a @ref chainmatrix "chain type matrix", the new column is of the form `IDIdx + linear combination of + * older column IDIdxs`, where the combination is deduced while reducing the given boundary. If the barcode is stored, + * it will also be updated. + * + * @tparam Boundary_type Range of @ref cell_rep_type. Assumed to have a begin(), end() and size() method. + * @param boundary Boundary generating the new column. The content should be ordered by ID. + * @param dim Dimension of the face whose boundary is given. If the complex is simplicial, + * this parameter can be omitted as it can be deduced from the size of the boundary. + * @return If it is a @ref chainmatrix "chain matrix", the method returns the @ref MatIdx indices of the unpaired + * chains used to reduce the boundary. Otherwise, nothing. + */ + template + insertion_return_type insert_boundary(const Boundary_type& boundary, dimension_type dim = -1); + /** + * @brief Only avalaible for @ref mp_matrices "non-basic matrices". + * It does the same as the other version, but allows the boundary faces to be identified without restrictions + * except that all IDs have to be strictly increasing in the order of filtration. Note that you should avoid then + * to use the other insertion method to avoid overwriting IDs. + * + * As a face has to be inserted before one of its cofaces in a valid filtration (recall that it is assumed that + * for @ref mp_matrices "non-basic matrices", the faces are inserted by order of filtration), it is sufficient to + * indicate the ID of the face being inserted. + * + * @tparam Boundary_type Range of @ref cell_rep_type. Assumed to have a begin(), end() and size() method. + * @param faceIndex @ref IDIdx index to use to indentify the new face. + * @param boundary Boundary generating the new column. The indices of the boundary have to correspond to the + * @p faceIndex values of precedent calls of the method for the corresponding faces and should be ordered in + * increasing order. + * @param dim Dimension of the face whose boundary is given. If the complex is simplicial, + * this parameter can be omitted as it can be deduced from the size of the boundary. + * @return If it is a @ref chainmatrix "chain matrix", the method returns the @ref MatIdx indices of the unpaired + * chains used to reduce the boundary. Otherwise, nothing. + */ + template + insertion_return_type insert_boundary(id_index faceIndex, const Boundary_type& boundary, dimension_type dim = -1); + + /** + * @brief Returns the column at the given @ref MatIdx index. + * For @ref boundarymatrix "RU matrices", is equivalent to + * @ref get_column(index columnIndex, bool inR) "get_column(columnIndex, true)". + * The type of the column depends on the choosen options, see @ref PersistenceMatrixOptions::column_type. + * + * @param columnIndex @ref MatIdx index of the column to return. + * @return Reference to the column. Is `const` if the matrix has column compression. + */ + returned_column_type& get_column(index columnIndex); + /** + * @brief Only available for @ref chainmatrix "chain matrices". Returns the column at the given @ref MatIdx index. + * The type of the column depends on the choosen options, see @ref PersistenceMatrixOptions::column_type. + * + * @param columnIndex @ref MatIdx index of the column to return. + * @return Const reference to the column. + */ + const Column_type& get_column(index columnIndex) const; + //TODO: there is no particular reason that this method is not available for identifier indexing, + // just has to be added to the interface... + /** + * @brief Only available for @ref boundarymatrix "RU matrices" without @ref Column_indexation_types::IDENTIFIER + * indexing. Returns the column at the given @ref MatIdx index in \f$ R \f$ if @p inR is true and in \f$ U \f$ if + * @p inR is false. The type of the column depends on the choosen options, + * see @ref PersistenceMatrixOptions::column_type. + * + * @param columnIndex @ref MatIdx index of the column to return. + * @param inR If true, returns the column in \f$ R \f$, if false, returns the column in \f$ U \f$. + * @return Const reference to the column. + */ + const Column_type& get_column(index columnIndex, bool inR); + + //TODO: update column indices when reordering rows (after lazy swap) such that always MatIdx are returned. + /** + * @brief Only available if @ref PersistenceMatrixOptions::has_row_access is true. Returns the row at the given + * @ref rowindex "row index". For @ref boundarymatrix "RU matrices", is equivalent to + * @ref get_row(id_index rowIndex, bool inR) "get_row(columnIndex, true)". The type of the row depends on the + * choosen options, see @ref PersistenceMatrixOptions::has_intrusive_rows. + * + * @param rowIndex @ref rowindex "Row index" of the row to return: @ref IDIdx for @ref chainmatrix "chain matrices" or + * updated @ref IDIdx for @ref boundarymatrix "boundary matrices" if swaps occured. + * @return Reference to the row. Is `const` if the matrix has column compression. + */ + returned_row_type& get_row(id_index rowIndex); + /** + * @brief Only available for @ref chainmatrix "chain matrices" and matrices with column compression. + * Returns the row at the given @ref rowindex "row index". + * The type of the row depends on the choosen options, see @ref PersistenceMatrixOptions::has_intrusive_rows. + * + * @param rowIndex @ref rowindex "Row index" of the row to return: @ref IDIdx for @ref chainmatrix "chain matrices" + * or updated @ref IDIdx for @ref boundarymatrix "boundary matrices" if swaps occured. + * @return Const reference to the row. + */ + const Row_type& get_row(id_index rowIndex) const; + //TODO: there is no particular reason that this method is not available for identifier indexing, + // just has to be added to the interface... + /** + * @brief Only available for @ref boundarymatrix "RU matrices" without @ref Column_indexation_types::IDENTIFIER + * indexing. Returns the row at the given @ref rowindex "row index" in \f$ R \f$ if @p inR is true and in \f$ U \f$ if + * @p inR is false. The type of the row depends on the choosen options, see + * @ref PersistenceMatrixOptions::has_intrusive_rows. + * + * @param rowIndex @ref rowindex "Row index" of the row to return: updated @ref IDIdx if swaps occured. + * @param inR If true, returns the row in \f$ R \f$, if false, returns the row in \f$ U \f$. + * @return Const reference to the row. + */ + const Row_type& get_row(id_index rowIndex, bool inR); + + /** + * @brief Only available for @ref basematrix "base matrices" without column compression and if + * @ref PersistenceMatrixOptions::has_map_column_container is true. Otherwise, see @ref remove_last. Erases the given + * column from the matrix. If @ref PersistenceMatrixOptions::has_row_access is also true, the deleted column cells are + * also automatically removed from their respective rows. + * + * @param columnIndex @ref MatIdx index of the column to remove. + */ + void remove_column(index columnIndex); + //TODO: rename method to be less confusing. + /** + * @brief The effect varies depending on the matrices and the options: + * - @ref basematrix "base matrix" and @ref boundarymatrix "boundary matrix": + * - @ref PersistenceMatrixOptions::has_map_column_container and @ref + * PersistenceMatrixOptions::has_column_and_row_swaps are true: cleans up maps used for the lazy row swaps. + * - @ref PersistenceMatrixOptions::has_row_access and @ref PersistenceMatrixOptions::has_removable_rows are true: + * assumes that the row is empty and removes it. + * - Otherwise, does nothing. + * - @ref boundarymatrix "boundary matrix" with \f$ U \f$ stored: only \f$ R \f$ is affected by the above. If properly + * used, \f$ U \f$ will never have empty rows. + * - @ref chainmatrix "chain matrix": only available if @ref PersistenceMatrixOptions::has_row_access and + * @ref PersistenceMatrixOptions::has_removable_rows are true. Assumes that the row is empty and removes it. + * + * @warning The removed rows are always assumed to be empty. If it is not the case, the deleted row cells are not + * removed from their columns. And in the case of intrusive rows, this will generate a segmentation fault when + * the column cells are destroyed later. The row access is just meant as a "read only" access to the rows and the + * @ref erase_empty_row method just as a way to specify that a row is empty and can therefore be removed from + * dictionnaries. This allows to avoid testing the emptiness of a row at each column cell removal, what can be quite + * frequent. + * + * @param rowIndex @ref rowindex "Row index" of the empty row to remove. + */ + void erase_empty_row(id_index rowIndex); + //TODO: for chain matrices, replace IDIdx input with MatIdx input to homogenise. + /** + * @brief Only available for @ref boundarymatrix "RU" and @ref chainmatrix "chain matrices" and if + * @ref PersistenceMatrixOptions::has_removable_columns and @ref PersistenceMatrixOptions::has_vine_update are true. + * For @ref chainmatrix "chain matrices", @ref PersistenceMatrixOptions::has_map_column_container and + * @ref PersistenceMatrixOptions::has_column_pairings also need to be true. Assumes that the face is maximal in the + * current complex and removes it such that the matrix remains consistent (i.e., RU is still an upper triangular + * decomposition of the boundary matrix and chain is still a compatible bases of the chain complex in the sense + * of @cite zigzag). The maximality of the face is not verified. Also updates the barcode if it was computed. + * + * For @ref chainmatrix "chain matrices", using the other version of the method could perform better depending on how + * the data is maintained on the side of the user. Then, @ref PersistenceMatrixOptions::has_column_pairings also do + * not need to be true. + * + * See also @ref remove_last and @ref remove_column. + * + * @param columnIndex If @ref boundarymatrix "boundary matrix", @ref MatIdx index of the face to remove, otherwise the + * @ref IDIdx index. + */ + void remove_maximal_face(index columnIndex); + //TODO: See if it would be better to use something more general than a vector for columnsToSwap, such that + // the user do not have to construct the vector from scratch. Like passing iterators instead. But it would be nice, + // to still be able to do (face, {})... + /** + * @brief Only available for @ref chainmatrix "chain matrices" and if + * @ref PersistenceMatrixOptions::has_removable_columns, @ref PersistenceMatrixOptions::has_vine_update and + * @ref PersistenceMatrixOptions::has_map_column_container are true. Assumes that the face is maximal in the current + * complex and removes it such that the matrix remains consistent (i.e., it is still a compatible bases of the chain + * complex in the sense of @cite zigzag). The maximality of the face is not verified. Also updates the barcode if it + * was computed. + * + * To maintain the compatibility, vine swaps are done to move the face up to the end of the filtration. Once at the + * end, the removal is trivial. But for @ref chainmatrix "chain matrices", swaps do not actually swap the position of + * the column every time, so the faces appearing after @p faceIndex in the filtration have to be searched first within + * the matrix. If the user has an easy access to the @ref IDIdx of the faces in the order of filtration, passing them + * by argument with @p columnsToSwap allows to skip a linear search process. Typically, if the user knows that the + * face he wants to remove is already the last face of the filtration, calling + * @ref remove_maximal_face(id_index faceIndex, const std::vector& columnsToSwap) + * "remove_maximal_face(faceID, {})" will be faster than @ref remove_last(). + * + * See also @ref remove_last. + * + * @param faceIndex @ref IDIdx index of the face to remove + * @param columnsToSwap Vector of @ref IDIdx indices of the faces coming after @p faceIndex in the filtration. + */ + void remove_maximal_face(id_index faceIndex, const std::vector& columnsToSwap); + /** + * @brief Removes the last inserted column/face from the matrix. + * If the matrix is @ref mp_matrices "non basic", @ref PersistenceMatrixOptions::has_removable_columns has to be true + * for the method to be available. Additionnaly, if the matrix is a @ref chainmatrix "chain matrix", either + * @ref PersistenceMatrixOptions::has_map_column_container has to be true or + * @ref PersistenceMatrixOptions::has_vine_update has to be false. And if the matrix is a + * @ref basematrix "base matrix" it should be without column compression. + * + * See also @ref remove_maximal_face and @ref remove_column. + * + * For @ref chainmatrix "chain matrices", if @ref PersistenceMatrixOptions::has_vine_update is true, the last face + * does not have to be at the end of the matrix and therefore has to be searched first. In this case, if the user + * already knows the @ref IDIdx of the last face, calling + * @ref remove_maximal_face(id_index faceIndex, const std::vector& columnsToSwap) + * "remove_maximal_face(faceID, {})" instead allows to skip the search. + */ + void remove_last(); + + /** + * @brief Returns the maximal dimension of a face stored in the matrix. Only available for + * @ref mp_matrices "non-basic matrices" and if @ref PersistenceMatrixOptions::has_matrix_maximal_dimension_access + * is true. + * + * @return The maximal dimension. + */ + dimension_type get_max_dimension() const; + /** + * @brief Returns the current number of columns in the matrix. + * + * @return The number of columns. + */ + index get_number_of_columns() const; + /** + * @brief Returns the dimension of the given face. Only available for @ref mp_matrices "non-basic matrices". + * + * @param columnIndex @ref MatIdx index of the column representing the face. + * @return Dimension of the face. + */ + dimension_type get_column_dimension(index columnIndex) const; + + /** + * @brief Adds column at @p sourceColumnIndex onto the column at @p targetColumnIndex in the matrix. Is available + * for every matrix type, but should be used with care with @ref mp_matrices "non-basic matrices", as they will be + * no verification to ensure that the addition makes sense for the meaning of the underlying object. For example, + * a right-to-left addition could corrupt the computation of the barcode or the representative cycles if done blindly. + * + * For @ref basematrix "basic matrices" with column compression, the representatives are summed together, which means + * that all column compressed together with the target column are affected by the change, not only the target. + * + * @tparam Index_type Any signed or unsigned integer type. + * @param sourceColumnIndex @ref MatIdx index of the column to add. + * @param targetColumnIndex @ref MatIdx index of the target column. + */ + template + std::enable_if_t > add_to(Index_type sourceColumnIndex, Index_type targetColumnIndex); + /** + * @brief Adds the given range of @ref Cell onto the column at @p targetColumnIndex in the matrix. Only available + * for @ref basematrix "basic matrices". + * + * For @ref basematrix "basic matrices" with column compression, the range is summed onto the representative, which + * means that all column compressed together with the target column are affected by the change, not only the target. + * + * @tparam Cell_range Range of @ref Cell. Needs a begin() and end() method. A column index does not need to be + * stored in the cells, even if @ref PersistenceMatrixOptions::has_row_access is true. + * @param sourceColumn Source @ref Cell range. + * @param targetColumnIndex @ref MatIdx index of the target column. + */ + template + std::enable_if_t > add_to(const Cell_range& sourceColumn, index targetColumnIndex); + + /** + * @brief Multiplies the target column with the coefficient and then adds the source column to it. + * That is: `targetColumn = (targetColumn * coefficient) + sourceColumn`. + * Is available for every matrix type, but should be used with care with @ref mp_matrices "non-basic matrices", + * as they will be no verification to ensure that the addition makes sense for the meaning of the underlying object. + * For example, a right-to-left addition could corrupt the computation of the barcode or the representative cycles + * if done blindly. + * + * For @ref basematrix "basic matrices" with column compression, the representatives are summed together, which means + * that all column compressed together with the target column are affected by the change, not only the target. + * + * @tparam Index_type Any signed or unsigned integer type. + * @param sourceColumnIndex @ref MatIdx index of the column to add. + * @param coefficient Value to multiply. + * @param targetColumnIndex @ref MatIdx index of the target column. + */ + template + std::enable_if_t > multiply_target_and_add_to(Index_type sourceColumnIndex, + int coefficient, + Index_type targetColumnIndex); + /** + * @brief Multiplies the target column with the coefficient and then adds the given range of @ref Cell to it. + * That is: `targetColumn = (targetColumn * coefficient) + sourceColumn`. Only available for + * @ref basematrix "basic matrices". + * + * For @ref basematrix "basic matrices" with column compression, the range is summed onto the representative, which + * means that all column compressed together with the target column are affected by the change, not only the target. + * + * @tparam Cell_range Range of @ref Cell. Needs a begin() and end() method. A column index does not need to be + * stored in the cells, even if @ref PersistenceMatrixOptions::has_row_access is true. + * @param sourceColumn Source @ref Cell range. + * @param coefficient Value to multiply. + * @param targetColumnIndex @ref MatIdx index of the target column. + */ + template + std::enable_if_t > multiply_target_and_add_to(const Cell_range& sourceColumn, + int coefficient, + index targetColumnIndex); + + /** + * @brief Multiplies the source column with the coefficient before adding it to the target column. + * That is: `targetColumn += (coefficient * sourceColumn)`. The source column will **not** be modified. + * Is available for every matrix type, but should be used with care with @ref mp_matrices "non-basic matrices", as + * they will be no verification to ensure that the addition makes sense for the meaning of the underlying object. + * For example, a right-to-left addition could corrupt the computation of the barcode or the representative cycles + * if done blindly. + * + * For @ref basematrix "basic matrices" with column compression, the representatives are summed together, which means + * that all column compressed together with the target column are affected by the change, not only the target. + * + * @tparam Index_type Any signed or unsigned integer type. + * @param coefficient Value to multiply. + * @param sourceColumnIndex @ref MatIdx index of the column to add. + * @param targetColumnIndex @ref MatIdx index of the target column. + */ + template + std::enable_if_t > multiply_source_and_add_to(int coefficient, + Index_type sourceColumnIndex, + Index_type targetColumnIndex); + /** + * @brief Multiplies the source column with the coefficient before adding it to the target column. + * That is: `targetColumn += (coefficient * sourceColumn)`. The source column will **not** be modified. + * Only available for @ref basematrix "basic matrices". + * + * For @ref basematrix "basic matrices" with column compression, the range is summed onto the representative, which + * means that all column compressed together with the target column are affected by the change, not only the target. + * + * @tparam Cell_range Range of @ref Cell. Needs a begin() and end() method. A column index does not need to be + * stored in the cells, even if @ref PersistenceMatrixOptions::has_row_access is true. + * @param coefficient Value to multiply. + * @param sourceColumn Source @ref Cell range. + * @param targetColumnIndex @ref MatIdx index of the target column. + */ + template + std::enable_if_t > multiply_source_and_add_to(int coefficient, + const Cell_range& sourceColumn, + index targetColumnIndex); + + /** + * @brief Zeroes the cell at the given coordinates. Not available for @ref chainmatrix "chain matrices" and for + * @ref basematrix "base matrices" with column compression. In general, should be used with care with + * @ref mp_matrices "non-basic matrices" to not destroy the validity of the persistence related properties of the + * matrix. + * + * For @ref boundarymatrix "RU matrices", equivalent to + * @ref zero_cell(index columnIndex, id_index rowIndex, bool inR) "zero_cell(columnIndex, rowIndex, true)". + * + * @param columnIndex @ref MatIdx index of the column of the cell. + * @param rowIndex @ref rowindex "Row index" of the row of the cell. + */ + void zero_cell(index columnIndex, id_index rowIndex); + /** + * @brief Only available for @ref boundarymatrix "RU matrices". Zeroes the cell at the given coordinates in \f$ R \f$ + * if @p inR is true or in \f$ U \f$ if @p inR is false. Should be used with care to not destroy the validity of the + * persistence related properties of the matrix. + * + * @param columnIndex @ref MatIdx index of the column of the cell. + * @param rowIndex @ref rowindex "Row index" of the row of the cell. + * @param inR Boolean indicating in which matrix to zero: if true in \f$ R \f$ and if false in \f$ U \f$. + */ + void zero_cell(index columnIndex, id_index rowIndex, bool inR); + /** + * @brief Zeroes the column at the given index. Not available for @ref chainmatrix "chain matrices" and for + * @ref basematrix "base matrices" with column compression. In general, should be used with care with + * @ref mp_matrices "non-basic matrices" to not destroy the validity of the persistence related properties of the + * matrix. + * + * For @ref boundarymatrix "RU matrices", equivalent to + * @ref zero_column(index columnIndex, bool inR) "zero_column(columnIndex, true)". + * + * @param columnIndex @ref MatIdx index of the column to zero. + */ + void zero_column(index columnIndex); + /** + * @brief Only available for @ref boundarymatrix "RU matrices". Zeroes the column at the given index in \f$ R \f$ if + * @p inR is true or in \f$ U \f$ if @p inR is false. Should be used with care to not destroy the validity of the + * persistence related properties of the matrix. + * + * @param columnIndex @ref MatIdx index of the column to zero. + * @param inR Boolean indicating in which matrix to zero: if true in \f$ R \f$ and if false in \f$ U \f$. + */ + void zero_column(index columnIndex, bool inR); + /** + * @brief Indicates if the cell at given coordinates has value zero. + * + * For @ref boundarymatrix "RU matrices", equivalent to + * @ref is_zero_cell(index columnIndex, id_index rowIndex, bool inR) const + * "is_zero_cell(columnIndex, rowIndex, true)". + * + * @param columnIndex @ref MatIdx index of the column of the cell. + * @param rowIndex @ref rowindex "Row index" of the row of the cell. + * @return true If the cell has value zero. + * @return false Otherwise. + */ + bool is_zero_cell(index columnIndex, id_index rowIndex); + /** + * @brief Only available for @ref boundarymatrix "RU matrices". Indicates if the cell at given coordinates has value + * zero in \f$ R \f$ if @p inR is true or in \f$ U \f$ if @p inR is false. + * + * @param columnIndex @ref MatIdx index of the column of the cell. + * @param rowIndex @ref rowindex "Row index" of the row of the cell. + * @param inR Boolean indicating in which matrix to look: if true in \f$ R \f$ and if false in \f$ U \f$. + * @return true If the cell has value zero. + * @return false Otherwise. + */ + bool is_zero_cell(index columnIndex, id_index rowIndex, bool inR) const; + /** + * @brief Indicates if the column at given index has value zero. + * + * For @ref boundarymatrix "RU matrices", equivalent to + * @ref is_zero_column(index columnIndex, bool inR) "is_zero_column(columnIndex, true)". + * + * Note that for @ref chainmatrix "chain matrices", this method should always return false, as a valid + * @ref chainmatrix "chain matrix" never has empty columns. + * + * @param columnIndex @ref MatIdx index of the column. + * @return true If the column has value zero. + * @return false Otherwise. + */ + bool is_zero_column(index columnIndex); + /** + * @brief Only available for @ref boundarymatrix "RU matrices". Indicates if the column at given index has value zero + * in \f$ R \f$ if @p inR is true or in \f$ U \f$ if @p inR is false. + * + * Note that if @p inR is false, this method should usually return false. + * + * @param columnIndex @ref MatIdx index of the column. + * @param inR Boolean indicating in which matrix to look: if true in \f$ R \f$ and if false in \f$ U \f$. + * @return true If the column has value zero. + * @return false Otherwise. + */ + bool is_zero_column(index columnIndex, bool inR); + + /** + * @brief Returns the @ref MatIdx index of the column which has the given @ref rowindex "row index" as pivot. Only + * available for @ref boundarymatrix "RU" and @ref chainmatrix "chain matrices". Assumes that the pivot exists. For + * @ref boundarymatrix "RU matrices", the column is returned from \f$ R \f$. + * + * Recall that the @ref rowindex "row indices" for @ref chainmatrix "chain matrices" correspond to the @ref IDIdx + * indices and that the @ref rowindex "row indices" for a @ref boundarymatrix "RU matrix" correspond to the updated + * @ref IDIdx indices which got potentially swapped by a vine swap. + * + * @param faceIndex @ref rowindex "Row index" of the pivot. + * @return @ref MatIdx index of the column with the given pivot. + */ + index get_column_with_pivot(id_index faceIndex) const; + /** + * @brief Returns the @ref rowindex "row index" of the pivot of the given column. Only available for + * @ref mp_matrices "non-basic matrices". + * + * @param columnIndex @ref MatIdx index of the column + * @return The @ref rowindex "row index" of the pivot. + */ + id_index get_pivot(index columnIndex); + + /** + * @brief Assign operator. + * + * @param other %Matrix to copy + * @return Reference to this object. + */ + Matrix& operator=(Matrix other); + /** + * @brief Swap operator for two matrices. + * + * @param matrix1 First matrix to swap. + * @param matrix2 Second matrix to swap. + */ + friend void swap(Matrix& matrix1, Matrix& matrix2) { + swap(matrix1.matrix_, matrix2.matrix_); + std::swap(matrix1.colSettings_, matrix2.colSettings_); + } + + void print(); // for debug + + //TODO: change the behaviour for boundary matrices. + /** + * @brief Returns the current barcode of the matrix. Available only if + * @ref PersistenceMatrixOptions::has_column_pairings is true. + * + * Recall that we assume that the boundaries were inserted in the order of filtration for the barcode to be valid. + * + * @warning For simple @ref boundarymatrix "boundary matrices" (only storing \f$ R \f$), we assume that + * @ref get_current_barcode is only called once the matrix is completed and won't be modified again. + * + * @return A reference to the barcode. The barcode is a vector of @ref Matrix::Bar. A bar stores three informations: + * the @ref PosIdx birth index, the @ref PosIdx death index and the dimension of the bar. + */ + const barcode_type& get_current_barcode(); + /** + * @brief Returns the current barcode of the matrix. Available only if + * @ref PersistenceMatrixOptions::has_column_pairings is true. + * + * Recall that we assume that the boundaries were inserted in the order of filtration for the barcode to be valid. + * + * @warning For simple @ref boundarymatrix "boundary matrices" (only storing \f$ R \f$), we assume that + * @ref get_current_barcode is only called once the matrix is completed and won't be modified again. + * + * @return A const reference to the barcode. The barcode is a vector of @ref Matrix::Bar. A bar stores three + * informations: the @ref PosIdx birth index, the @ref PosIdx death index and the dimension of the bar. + */ + const barcode_type& get_current_barcode() const; + + /** + * @brief Only available for @ref basematrix "base matrices" without column compression and simple + * @ref boundarymatrix "boundary matrices" (only storing \f$ R \f$) and if + * @ref PersistenceMatrixOptions::has_column_and_row_swaps is true. Swaps the two given columns. Note for + * @ref boundarymatrix "boundary matrices", that it really just swaps two columns and does not update anything else, + * nor performs additions to maintain some properties on the matrix. + * + * @param columnIndex1 First column @ref MatIdx index to swap. + * @param columnIndex2 Second column @ref MatIdx index to swap. + */ + void swap_columns(index columnIndex1, index columnIndex2); + /** + * @brief Only available for @ref basematrix "base matrices" without column compression and simple + * @ref boundarymatrix "boundary matrices" (only storing \f$ R \f$) and if + * @ref PersistenceMatrixOptions::has_column_and_row_swaps is true. Swaps the two given rows. Note for + * @ref boundarymatrix "boundary matrices", that it really just swaps two rows and does not update anything else, + * nor performs additions to maintain some properties on the matrix. + * + * @param rowIndex1 First @ref rowindex "row index" to swap. + * @param rowIndex2 Second @ref rowindex "row index" to swap. + */ + void swap_rows(index rowIndex1, index rowIndex2); + //TODO: find better name. And benchmark also to verify if it is really worth it to have this extra version in addition + //to vine_swap. + /** + * @brief Only available if @ref PersistenceMatrixOptions::has_vine_update is true and if it is either a bounary + * matrix or @ref PersistenceMatrixOptions::column_indexation_type is set to @ref Column_indexation_types::POSITION. + * Does the same than @ref vine_swap, but assumes that the swap is non trivial and therefore skips a part of the case + * study. + * + * @param index @ref PosIdx index of the first face to swap. The second one has to be at `index + 1`. Recall that for + * @ref boundarymatrix "boundary matrices", @ref PosIdx == @ref MatIdx. + * @return true If the barcode changed from the swap. + * @return false Otherwise. + */ + bool vine_swap_with_z_eq_1_case(pos_index index); + /** + * @brief Only available if @ref PersistenceMatrixOptions::has_vine_update is true and if it is either a + * @ref chainmatrix "chain matrix" or @ref PersistenceMatrixOptions::column_indexation_type is set to + * @ref Column_indexation_types::IDENTIFIER. Does the same than @ref vine_swap, but assumes that the swap is + * non-trivial and therefore skips a part of the case study. + * + * @param columnIndex1 @ref MatIdx index of the first face. + * @param columnIndex2 @ref MatIdx index of the second face. It is assumed that the @ref PosIdx of both only differs + * by one. + * @return Let \f$ pos1 \f$ be the @ref PosIdx index of @p columnIndex1 and \f$ pos2 \f$ be the @ref PosIdx index of + * @p columnIndex2. The method returns the @ref MatIdx of the column which has now, after the swap, the @ref PosIdx + * \f$ max(pos1, pos2) \f$. + */ + index vine_swap_with_z_eq_1_case(index columnIndex1, index columnIndex2); + /** + * @brief Only available if @ref PersistenceMatrixOptions::has_vine_update is true and if it is either a + * @ref boundarymatrix "boundary matrix" or @ref PersistenceMatrixOptions::column_indexation_type is set to + * @ref Column_indexation_types::POSITION. Does a vine swap between two faces which are consecutives in the + * filtration. Roughly, if \f$ F \f$ is the current filtration represented by the matrix, the method modifies the + * matrix such that the new state corresponds to a valid state for the filtration \f$ F' \f$ equal to \f$ F \f$ but + * with the two faces at position `index` and `index + 1` swapped. Of course, the two faces should not have a + * face/coface relation which each other ; \f$ F' \f$ has to be a valid filtration. + * See @cite vineyards for more information about vine and vineyards. + * + * @param index @ref PosIdx index of the first face to swap. The second one has to be at `index + 1`. Recall that for + * @ref boundarymatrix "boundary matrices", @ref PosIdx == @ref MatIdx. + * @return true If the barcode changed from the swap. + * @return false Otherwise. + */ + bool vine_swap(pos_index index); + /** + * @brief Only available if @ref PersistenceMatrixOptions::has_vine_update is true and if it is either a + * @ref chainmatrix "chain matrix" or @ref PersistenceMatrixOptions::column_indexation_type is set to + * @ref Column_indexation_types::IDENTIFIER. Does a vine swap between two faces which are consecutives in the + * filtration. Roughly, if \f$ F \f$ is the current filtration represented by the matrix, the method modifies the + * matrix such that the new state corresponds to a valid state for the filtration \f$ F' \f$ equal to \f$ F \f$ but + * with the two given faces at swapped positions. Of course, the two faces should not have a face/coface relation + * which each other ; \f$ F' \f$ has to be a valid filtration. + * See @cite vineyards for more information about vine and vineyards. + * + * @param columnIndex1 @ref MatIdx index of the first face. + * @param columnIndex2 @ref MatIdx index of the second face. It is assumed that the @ref PosIdx of both only differs + * by one. + * @return Let \f$ pos1 \f$ be the @ref PosIdx index of @p columnIndex1 and \f$ pos2 \f$ be the @ref PosIdx index of + * @p columnIndex2. The method returns the @ref MatIdx of the column which has now, after the swap, the @ref PosIdx + * \f$ max(pos1, pos2) \f$. + */ + index vine_swap(index columnIndex1, index columnIndex2); + + //TODO: Rethink the interface for representative cycles + /** + * @brief Only available if @ref PersistenceMatrixOptions::can_retrieve_representative_cycles is true. Precomputes the + * representative cycles of the current state of the filtration represented by the matrix. It does not need to be + * called before @ref get_representative_cycles is called for the first time, but needs to be called before calling + * @ref get_representative_cycles again if the matrix was modified in between. Otherwise the old cycles will be + * returned. + */ + void update_representative_cycles(); + /** + * @brief Only available if @ref PersistenceMatrixOptions::can_retrieve_representative_cycles is true. + * Returns all representative cycles of the current filtration. + * + * @return A const reference to the vector of representative cycles. + */ + const std::vector& get_representative_cycles(); + /** + * @brief Only available if @ref PersistenceMatrixOptions::can_retrieve_representative_cycles is true. + * Returns the cycle representing the given bar. + * + * @param bar A bar from the current barcode. + * @return A const reference to the cycle representing @p bar. + */ + const cycle_type& get_representative_cycle(const Bar& bar); + + private: + using Matrix_type = + typename std::conditional< + isNonBasic, + typename std::conditional< + PersistenceMatrixOptions::is_of_boundary_type, + typename std::conditional< + PersistenceMatrixOptions::has_vine_update || + PersistenceMatrixOptions::can_retrieve_representative_cycles, + typename std::conditional< + PersistenceMatrixOptions::column_indexation_type == Column_indexation_types::CONTAINER || + PersistenceMatrixOptions::column_indexation_type == Column_indexation_types::POSITION, + RU_matrix_type, Id_to_index_overlay > + >::type, + typename std::conditional< + PersistenceMatrixOptions::column_indexation_type == Column_indexation_types::CONTAINER || + PersistenceMatrixOptions::column_indexation_type == Column_indexation_types::POSITION, + Boundary_matrix_type, + Id_to_index_overlay > + >::type + >::type, + typename std::conditional< + PersistenceMatrixOptions::column_indexation_type == Column_indexation_types::CONTAINER, + Chain_matrix_type, + typename std::conditional< + PersistenceMatrixOptions::column_indexation_type == Column_indexation_types::POSITION, + Position_to_index_overlay >, + Id_to_index_overlay > + >::type + >::type + >::type, + Base_matrix_type + >::type; + + // Field_operators* operators_; + // Cell_constructor* cellPool_; + Column_settings* colSettings_; //pointer because the of swap operator on matrix_ which also stores the pointer + Matrix_type matrix_; + + static constexpr void _assert_options(); +}; + +template +inline Matrix::Matrix() + : colSettings_(new Column_settings()), matrix_(colSettings_) +{ + static_assert( + PersistenceMatrixOptions::is_of_boundary_type || !PersistenceMatrixOptions::has_vine_update || + PersistenceMatrixOptions::has_column_pairings, + "When no barcode is recorded with vine swaps, comparaison functions for the columns have to be provided."); + _assert_options(); +} + +template +template +inline Matrix::Matrix(const std::vector& columns, + characteristic_type characteristic) + : colSettings_(new Column_settings(characteristic)), + matrix_(columns, colSettings_) +{ + static_assert(PersistenceMatrixOptions::is_of_boundary_type || !PersistenceMatrixOptions::has_vine_update || + PersistenceMatrixOptions::has_column_pairings, + "When no barcode is recorded with vine swaps for chain matrices, comparaison functions for the columns " + "have to be provided."); + _assert_options(); +} + +template +inline Matrix::Matrix(int numberOfColumns, characteristic_type characteristic) + : colSettings_(new Column_settings(characteristic)), + matrix_(numberOfColumns, colSettings_) +{ + static_assert(PersistenceMatrixOptions::is_of_boundary_type || !PersistenceMatrixOptions::has_vine_update || + PersistenceMatrixOptions::has_column_pairings, + "When no barcode is recorded with vine swaps for chain matrices, comparaison functions for the columns " + "have to be provided."); + _assert_options(); +} + +template +inline Matrix::Matrix(const std::function& birthComparator, + const std::function& deathComparator) + : colSettings_(new Column_settings()), + matrix_(colSettings_, birthComparator, deathComparator) +{ + static_assert( + !PersistenceMatrixOptions::is_of_boundary_type && PersistenceMatrixOptions::has_vine_update && + !PersistenceMatrixOptions::has_column_pairings, + "Constructor only available for chain matrices when vine swaps are enabled, but barcodes are not recorded."); + _assert_options(); +} + +template +template +inline Matrix::Matrix(const std::vector& orderedBoundaries, + const std::function& birthComparator, + const std::function& deathComparator, + characteristic_type characteristic) + : colSettings_(new Column_settings(characteristic)), + matrix_(orderedBoundaries, colSettings_, birthComparator, deathComparator) +{ + static_assert( + !PersistenceMatrixOptions::is_of_boundary_type && PersistenceMatrixOptions::has_vine_update && + !PersistenceMatrixOptions::has_column_pairings, + "Constructor only available for chain matrices when vine swaps are enabled, but barcodes are not recorded."); + _assert_options(); +} + +template +inline Matrix::Matrix(unsigned int numberOfColumns, + const std::function& birthComparator, + const std::function& deathComparator, + characteristic_type characteristic) + : colSettings_(new Column_settings(characteristic)), + matrix_(numberOfColumns, colSettings_, birthComparator, deathComparator) +{ + static_assert( + !PersistenceMatrixOptions::is_of_boundary_type && PersistenceMatrixOptions::has_vine_update && + !PersistenceMatrixOptions::has_column_pairings, + "Constructor only available for chain matrices when vine swaps are enabled, but barcodes are not recorded."); + _assert_options(); +} + +template +inline Matrix::Matrix(const Matrix& matrixToCopy) + : colSettings_(new Column_settings(*matrixToCopy.colSettings_)), + matrix_(matrixToCopy.matrix_, colSettings_) +{ + _assert_options(); +} + +template +inline Matrix::Matrix(Matrix&& other) noexcept + : colSettings_(std::exchange(other.colSettings_, nullptr)), + matrix_(std::move(other.matrix_)) +{ + _assert_options(); +} + +template +inline Matrix::~Matrix() +{ + matrix_.reset(colSettings_); + delete colSettings_; +} + +template +inline void Matrix::set_characteristic(characteristic_type characteristic) +{ + if constexpr (!PersistenceMatrixOptions::is_z2) { + if (colSettings_->operators.get_characteristic() != static_cast(-1)) { + std::cerr << "Warning: Characteristic already initialised. Changing it could lead to incoherences in the matrice " + "as the modulo was already applied to values in existing columns."; + } + + colSettings_->operators.set_characteristic(characteristic); + } +} + +template +template +inline void Matrix::insert_column(const Container_type& column) +{ + if constexpr (!PersistenceMatrixOptions::is_z2){ + GUDHI_CHECK(colSettings_->operators.get_characteristic() != static_cast(-1), + std::logic_error("Matrix::insert_column - Columns cannot be initialized if the coefficient field " + "characteristic is not specified.")); + } + + static_assert( + !isNonBasic, + "'insert_column' not available for the chosen options. The input has to be in the form of a face boundary."); + matrix_.insert_column(column); +} + +template +template +inline void Matrix::insert_column(const Container_type& column, index columnIndex) +{ + if constexpr (!PersistenceMatrixOptions::is_z2){ + GUDHI_CHECK(colSettings_->operators.get_characteristic() != static_cast(-1), + std::logic_error("Matrix::insert_column - Columns cannot be initialized if the coefficient field " + "characteristic is not specified.")); + } + + static_assert(!isNonBasic && !PersistenceMatrixOptions::has_column_compression, + "'insert_column' with those parameters is not available for the chosen options."); + static_assert(!PersistenceMatrixOptions::has_row_access, + "Columns have to be inserted at the end of the matrix when row access is enabled."); + matrix_.insert_column(column, columnIndex); +} + +template +template +inline typename Matrix::insertion_return_type +Matrix::insert_boundary(const Boundary_type& boundary, dimension_type dim) +{ + if constexpr (!PersistenceMatrixOptions::is_z2){ + GUDHI_CHECK(colSettings_->operators.get_characteristic() != static_cast(-1), + std::logic_error("Matrix::insert_boundary - Columns cannot be initialized if the coefficient field " + "characteristic is not specified.")); + } + + if constexpr (isNonBasic && !PersistenceMatrixOptions::is_of_boundary_type && + PersistenceMatrixOptions::column_indexation_type == Column_indexation_types::CONTAINER) + return matrix_.insert_boundary(boundary, dim); + else + matrix_.insert_boundary(boundary, dim); +} + +template +template +inline typename Matrix::insertion_return_type +Matrix::insert_boundary(id_index faceIndex, + const Boundary_type& boundary, + dimension_type dim) +{ + if constexpr (!PersistenceMatrixOptions::is_z2){ + GUDHI_CHECK(colSettings_->operators.get_characteristic() != static_cast(-1), + std::logic_error("Matrix::insert_boundary - Columns cannot be initialized if the coefficient field " + "characteristic is not specified.")); + } + + static_assert(isNonBasic, "Only enabled for non-basic matrices."); + if constexpr (!PersistenceMatrixOptions::is_of_boundary_type && + PersistenceMatrixOptions::column_indexation_type == Column_indexation_types::CONTAINER) + return matrix_.insert_boundary(faceIndex, boundary, dim); + else + matrix_.insert_boundary(faceIndex, boundary, dim); +} + +template +inline typename Matrix::returned_column_type& Matrix::get_column( + index columnIndex) +{ + return matrix_.get_column(columnIndex); +} + +template +inline const typename Matrix::Column_type& Matrix::get_column( + index columnIndex) const +{ + return matrix_.get_column(columnIndex); +} + +template +inline const typename Matrix::Column_type& Matrix::get_column( + index columnIndex, bool inR) +{ + // TODO: I don't think there is a particular reason why the indexation is forced, should be removed. + static_assert( + isNonBasic && PersistenceMatrixOptions::is_of_boundary_type && + (PersistenceMatrixOptions::has_vine_update || PersistenceMatrixOptions::can_retrieve_representative_cycles) && + PersistenceMatrixOptions::column_indexation_type != Column_indexation_types::IDENTIFIER, + "Only enabled for position indexed RU matrices."); + + return matrix_.get_column(columnIndex, inR); +} + +template +inline typename Matrix::returned_row_type& Matrix::get_row( + id_index rowIndex) +{ + static_assert(PersistenceMatrixOptions::has_row_access, "'get_row' is not available for the chosen options."); + + return matrix_.get_row(rowIndex); +} + +template +inline const typename Matrix::Row_type& Matrix::get_row( + id_index rowIndex) const +{ + static_assert(PersistenceMatrixOptions::has_row_access, "'get_row' is not available for the chosen options."); + + return matrix_.get_row(rowIndex); +} + +template +inline const typename Matrix::Row_type& Matrix::get_row( + id_index rowIndex, bool inR) +{ + static_assert(PersistenceMatrixOptions::has_row_access, "'get_row' is not available for the chosen options."); + // TODO: I don't think there is a particular reason why the indexation is forced, should be removed. + static_assert( + isNonBasic && PersistenceMatrixOptions::is_of_boundary_type && + (PersistenceMatrixOptions::has_vine_update || PersistenceMatrixOptions::can_retrieve_representative_cycles) && + PersistenceMatrixOptions::column_indexation_type != Column_indexation_types::IDENTIFIER, + "Only enabled for position indexed RU matrices."); + + return matrix_.get_row(rowIndex, inR); +} + +template +inline void Matrix::remove_column(index columnIndex) +{ + static_assert(PersistenceMatrixOptions::has_map_column_container && !isNonBasic && + !PersistenceMatrixOptions::has_column_compression, + "'remove_column' is not available for the chosen options."); + + matrix_.remove_column(columnIndex); +} + +template +inline void Matrix::erase_empty_row(id_index rowIndex) +{ + static_assert( + !isNonBasic || PersistenceMatrixOptions::is_of_boundary_type || PersistenceMatrixOptions::has_removable_rows, + "'erase_empty_row' is not available for the chosen options."); + + matrix_.erase_empty_row(rowIndex); +} + +template +inline void Matrix::remove_maximal_face(index columnIndex) +{ + static_assert(PersistenceMatrixOptions::has_removable_columns, + "'remove_maximal_face(id_index)' is not available for the chosen options."); + static_assert(isNonBasic && PersistenceMatrixOptions::has_vine_update, + "'remove_maximal_face(id_index)' is not available for the chosen options."); + static_assert(PersistenceMatrixOptions::is_of_boundary_type || (PersistenceMatrixOptions::has_map_column_container && + PersistenceMatrixOptions::has_column_pairings), + "'remove_maximal_face(id_index)' is not available for the chosen options."); + + matrix_.remove_maximal_face(columnIndex); +} + +template +inline void Matrix::remove_maximal_face(id_index faceIndex, + const std::vector& columnsToSwap) +{ + static_assert(PersistenceMatrixOptions::has_removable_columns, + "'remove_maximal_face(id_index,const std::vector&)' is not available for the chosen options."); + static_assert(isNonBasic && !PersistenceMatrixOptions::is_of_boundary_type, + "'remove_maximal_face(id_index,const std::vector&)' is not available for the chosen options."); + static_assert(PersistenceMatrixOptions::has_map_column_container && PersistenceMatrixOptions::has_vine_update, + "'remove_maximal_face(id_index,const std::vector&)' is not available for the chosen options."); + + matrix_.remove_maximal_face(faceIndex, columnsToSwap); +} + +template +inline void Matrix::remove_last() +{ + static_assert(PersistenceMatrixOptions::has_removable_columns || !isNonBasic, + "'remove_last' is not available for the chosen options."); + static_assert(!PersistenceMatrixOptions::has_column_compression || isNonBasic, + "'remove_last' is not available for the chosen options."); + static_assert(!isNonBasic || PersistenceMatrixOptions::is_of_boundary_type || + PersistenceMatrixOptions::has_map_column_container || !PersistenceMatrixOptions::has_vine_update, + "'remove_last' is not available for the chosen options."); + + matrix_.remove_last(); +} + +template +inline typename Matrix::dimension_type Matrix::get_max_dimension() + const +{ + static_assert(isNonBasic, "'get_max_dimension' is not available for the chosen options."); + + return matrix_.get_max_dimension(); +} + +template +inline typename Matrix::index Matrix::get_number_of_columns() const +{ + return matrix_.get_number_of_columns(); +} + +template +inline typename Matrix::dimension_type Matrix::get_column_dimension( + index columnIndex) const +{ + static_assert(isNonBasic, "'get_column_dimension' is not available for the chosen options."); + + return matrix_.get_column_dimension(columnIndex); +} + +template +template +inline std::enable_if_t > Matrix::add_to( + Index_type sourceColumnIndex, Index_type targetColumnIndex) +{ + matrix_.add_to(sourceColumnIndex, targetColumnIndex); +} + +template +template +inline std::enable_if_t > Matrix::add_to( + const Cell_range& sourceColumn, index targetColumnIndex) +{ + static_assert(!isNonBasic, + "For boundary or chain matrices, only additions with columns inside the matrix is allowed to maintain " + "algebraic consistency."); + + matrix_.add_to(sourceColumn, targetColumnIndex); +} + +template +template +inline std::enable_if_t > Matrix::multiply_target_and_add_to( + Index_type sourceColumnIndex, int coefficient, Index_type targetColumnIndex) +{ + if constexpr (PersistenceMatrixOptions::is_z2) { + // coef will be converted to bool, because of element_type + matrix_.multiply_target_and_add_to(sourceColumnIndex, coefficient % 2, targetColumnIndex); + } else { + matrix_.multiply_target_and_add_to(sourceColumnIndex, colSettings_->operators.get_value(coefficient), targetColumnIndex); + } +} + +template +template +inline std::enable_if_t > Matrix::multiply_target_and_add_to( + const Cell_range& sourceColumn, int coefficient, index targetColumnIndex) +{ + static_assert(!isNonBasic, + "For boundary or chain matrices, only additions with columns inside the matrix is allowed to maintain " + "algebraic consistency."); + + if constexpr (PersistenceMatrixOptions::is_z2) { + // coef will be converted to bool, because of element_type + matrix_.multiply_target_and_add_to(sourceColumn, coefficient % 2, targetColumnIndex); + } else { + matrix_.multiply_target_and_add_to(sourceColumn, colSettings_->operators.get_value(coefficient), targetColumnIndex); + } +} + +template +template +inline std::enable_if_t > Matrix::multiply_source_and_add_to( + int coefficient, Index_type sourceColumnIndex, Index_type targetColumnIndex) +{ + if constexpr (PersistenceMatrixOptions::is_z2) { + // coef will be converted to bool, because of element_type + matrix_.multiply_source_and_add_to(coefficient % 2, sourceColumnIndex, targetColumnIndex); + } else { + matrix_.multiply_source_and_add_to(colSettings_->operators.get_value(coefficient), sourceColumnIndex, targetColumnIndex); + } +} + +template +template +inline std::enable_if_t > Matrix::multiply_source_and_add_to( + int coefficient, const Cell_range& sourceColumn, index targetColumnIndex) +{ + static_assert(!isNonBasic, + "For boundary or chain matrices, only additions with columns inside the matrix is allowed to maintain " + "algebraic consistency."); + + if constexpr (PersistenceMatrixOptions::is_z2) { + // coef will be converted to bool, because of element_type + matrix_.multiply_source_and_add_to(coefficient % 2, sourceColumn, targetColumnIndex); + } else { + matrix_.multiply_source_and_add_to(colSettings_->operators.get_value(coefficient), sourceColumn, targetColumnIndex); + } +} + +template +inline void Matrix::zero_cell(index columnIndex, id_index rowIndex) +{ + static_assert(PersistenceMatrixOptions::is_of_boundary_type && !PersistenceMatrixOptions::has_column_compression, + "'zero_cell' is not available for the chosen options."); + + return matrix_.zero_cell(columnIndex, rowIndex); +} + +template +inline void Matrix::zero_cell(index columnIndex, id_index rowIndex, bool inR) +{ + // TODO: I don't think there is a particular reason why the indexation is forced, should be removed. + static_assert( + isNonBasic && PersistenceMatrixOptions::is_of_boundary_type && + (PersistenceMatrixOptions::has_vine_update || PersistenceMatrixOptions::can_retrieve_representative_cycles) && + PersistenceMatrixOptions::column_indexation_type != Column_indexation_types::IDENTIFIER, + "Only enabled for RU matrices."); + + return matrix_.zero_cell(columnIndex, rowIndex, inR); +} + +template +inline void Matrix::zero_column(index columnIndex) +{ + static_assert(PersistenceMatrixOptions::is_of_boundary_type && !PersistenceMatrixOptions::has_column_compression, + "'zero_column' is not available for the chosen options."); + + return matrix_.zero_column(columnIndex); +} + +template +inline void Matrix::zero_column(index columnIndex, bool inR) +{ + // TODO: I don't think there is a particular reason why the indexation is forced, should be removed. + static_assert( + isNonBasic && PersistenceMatrixOptions::is_of_boundary_type && + (PersistenceMatrixOptions::has_vine_update || PersistenceMatrixOptions::can_retrieve_representative_cycles) && + PersistenceMatrixOptions::column_indexation_type != Column_indexation_types::IDENTIFIER, + "Only enabled for RU matrices."); + + return matrix_.zero_column(columnIndex, inR); +} + +template +inline bool Matrix::is_zero_cell(index columnIndex, id_index rowIndex) +{ + return matrix_.is_zero_cell(columnIndex, rowIndex); +} + +template +inline bool Matrix::is_zero_cell(index columnIndex, id_index rowIndex, bool inR) const +{ + // TODO: I don't think there is a particular reason why the indexation is forced, should be removed. + static_assert( + isNonBasic && PersistenceMatrixOptions::is_of_boundary_type && + (PersistenceMatrixOptions::has_vine_update || PersistenceMatrixOptions::can_retrieve_representative_cycles) && + PersistenceMatrixOptions::column_indexation_type != Column_indexation_types::IDENTIFIER, + "Only enabled for RU matrices."); + + return matrix_.is_zero_cell(columnIndex, rowIndex, inR); +} + +template +inline bool Matrix::is_zero_column(index columnIndex) +{ + return matrix_.is_zero_column(columnIndex); +} + +template +inline bool Matrix::is_zero_column(index columnIndex, bool inR) +{ + // TODO: I don't think there is a particular reason why the indexation is forced, should be removed. + static_assert( + isNonBasic && PersistenceMatrixOptions::is_of_boundary_type && + (PersistenceMatrixOptions::has_vine_update || PersistenceMatrixOptions::can_retrieve_representative_cycles) && + PersistenceMatrixOptions::column_indexation_type != Column_indexation_types::IDENTIFIER, + "Only enabled for RU matrices."); + + return matrix_.is_zero_column(columnIndex, inR); +} + +template +inline typename Matrix::index Matrix::get_column_with_pivot( + id_index faceIndex) const +{ + static_assert(isNonBasic && (!PersistenceMatrixOptions::is_of_boundary_type || + (PersistenceMatrixOptions::has_vine_update || + PersistenceMatrixOptions::can_retrieve_representative_cycles)), + "'get_column_with_pivot' is not available for the chosen options."); + + return matrix_.get_column_with_pivot(faceIndex); +} + +template +inline typename Matrix::id_index Matrix::get_pivot( + index columnIndex) +{ + static_assert(isNonBasic, "'get_pivot' is not available for the chosen options."); + + return matrix_.get_pivot(columnIndex); +} + +template +inline Matrix& Matrix::operator=(Matrix other) +{ + swap(matrix_, other.matrix_); + std::swap(colSettings_, other.colSettings_); + + return *this; +} + +template +inline void Matrix::print() +{ + return matrix_.print(); +} + +template +inline const typename Matrix::barcode_type& +Matrix::get_current_barcode() +{ + static_assert(PersistenceMatrixOptions::has_column_pairings, "This method was not enabled."); + + return matrix_.get_current_barcode(); +} + +template +inline const typename Matrix::barcode_type& +Matrix::get_current_barcode() const +{ + static_assert(PersistenceMatrixOptions::has_column_pairings, "This method was not enabled."); + static_assert( + !PersistenceMatrixOptions::is_of_boundary_type || PersistenceMatrixOptions::has_vine_update || + PersistenceMatrixOptions::can_retrieve_representative_cycles, + "'get_current_barcode' is not const for boundary matrices as the barcode is only computed when explicitely " + "asked."); + + return matrix_.get_current_barcode(); +} + +template +inline void Matrix::swap_columns(index columnIndex1, index columnIndex2) +{ + static_assert( + (!isNonBasic && !PersistenceMatrixOptions::has_column_compression) || + (isNonBasic && PersistenceMatrixOptions::is_of_boundary_type && !PersistenceMatrixOptions::has_vine_update && + !PersistenceMatrixOptions::can_retrieve_representative_cycles), + "This method was not enabled."); + return matrix_.swap_columns(columnIndex1, columnIndex2); +} + +template +inline void Matrix::swap_rows(index rowIndex1, index rowIndex2) +{ + static_assert( + (!isNonBasic && !PersistenceMatrixOptions::has_column_compression) || + (isNonBasic && PersistenceMatrixOptions::is_of_boundary_type && !PersistenceMatrixOptions::has_vine_update && + !PersistenceMatrixOptions::can_retrieve_representative_cycles), + "This method was not enabled."); + return matrix_.swap_rows(rowIndex1, rowIndex2); +} + +template +inline bool Matrix::vine_swap_with_z_eq_1_case(pos_index index) +{ + static_assert(PersistenceMatrixOptions::has_vine_update, "This method was not enabled."); + static_assert(PersistenceMatrixOptions::column_indexation_type == Column_indexation_types::POSITION || + (PersistenceMatrixOptions::is_of_boundary_type && + PersistenceMatrixOptions::column_indexation_type == Column_indexation_types::CONTAINER), + "This method was not enabled."); + return matrix_.vine_swap_with_z_eq_1_case(index); +} + +template +inline typename Matrix::index Matrix::vine_swap_with_z_eq_1_case( + index columnIndex1, index columnIndex2) +{ + static_assert(PersistenceMatrixOptions::has_vine_update, "This method was not enabled."); + static_assert(PersistenceMatrixOptions::column_indexation_type == Column_indexation_types::IDENTIFIER || + (!PersistenceMatrixOptions::is_of_boundary_type && + PersistenceMatrixOptions::column_indexation_type == Column_indexation_types::CONTAINER), + "This method was not enabled."); + + return matrix_.vine_swap_with_z_eq_1_case(columnIndex1, columnIndex2); +} + +template +inline bool Matrix::vine_swap(pos_index index) +{ + static_assert(PersistenceMatrixOptions::has_vine_update, "This method was not enabled."); + static_assert(PersistenceMatrixOptions::column_indexation_type == Column_indexation_types::POSITION || + (PersistenceMatrixOptions::is_of_boundary_type && + PersistenceMatrixOptions::column_indexation_type == Column_indexation_types::CONTAINER), + "This method was not enabled."); + return matrix_.vine_swap(index); +} + +template +inline typename Matrix::index Matrix::vine_swap( + index columnIndex1, index columnIndex2) +{ + static_assert(PersistenceMatrixOptions::has_vine_update, "This method was not enabled."); + static_assert(PersistenceMatrixOptions::column_indexation_type == Column_indexation_types::IDENTIFIER || + (!PersistenceMatrixOptions::is_of_boundary_type && + PersistenceMatrixOptions::column_indexation_type == Column_indexation_types::CONTAINER), + "This method was not enabled."); + return matrix_.vine_swap(columnIndex1, columnIndex2); +} + +template +inline void Matrix::update_representative_cycles() +{ + static_assert(PersistenceMatrixOptions::can_retrieve_representative_cycles, "This method was not enabled."); + matrix_.update_representative_cycles(); +} + +template +inline const std::vector::cycle_type>& +Matrix::get_representative_cycles() +{ + static_assert(PersistenceMatrixOptions::can_retrieve_representative_cycles, "This method was not enabled."); + return matrix_.get_representative_cycles(); +} + +template +inline const typename Matrix::cycle_type& +Matrix::get_representative_cycle(const Bar& bar) +{ + static_assert(PersistenceMatrixOptions::can_retrieve_representative_cycles, "This method was not enabled."); + return matrix_.get_representative_cycle(bar); +} + +template +inline constexpr void Matrix::_assert_options() +{ + static_assert( + PersistenceMatrixOptions::column_type != Column_types::HEAP || !PersistenceMatrixOptions::has_row_access, + "Row access is not possible for heap columns."); + static_assert(!PersistenceMatrixOptions::has_vine_update || PersistenceMatrixOptions::is_z2, + "Vine update currently works only for Z_2 coefficients."); + // static_assert(!PersistenceMatrixOptions::can_retrieve_representative_cycles || PersistenceMatrixOptions::is_z2, + // "Representaive cycles can currently only be computed with Z_2 coefficients."); + static_assert( + PersistenceMatrixOptions::column_type != Column_types::HEAP || !PersistenceMatrixOptions::has_column_compression, + "Column compression not compatible with heap columns."); + + // // This should be warnings instead, as PersistenceMatrixOptions::has_column_compression is just ignored in those + // // cases and don't produces errors as long as the corresponding methods are not called. + // static_assert(!PersistenceMatrixOptions::has_column_compression || !PersistenceMatrixOptions::has_column_pairings, + // "Column compression not available to compute persistence homology (it would bring no advantages; " + // "use it for co-homology instead)."); + // static_assert(!PersistenceMatrixOptions::has_column_compression || !PersistenceMatrixOptions::has_vine_update, + // "Column compression not available for vineyards."); + // static_assert(!PersistenceMatrixOptions::has_column_compression || + // !PersistenceMatrixOptions::can_retrieve_representative_cycles, + // "Column compression not available to retrieve representative cycles."); + // // Would column removal while column compression be useful? If yes, should erase() remove a single column or the + // // class of columns identical to the input? + // // For a single column, I have an implementation for union-find (not the current one) which allows deleting a + // // single element in constant time, but find becomes log n in worst case. + // // For a column class, we either just ignore the removed class (constant time), but this results in memory + // // residues, or, we have an erase method which is at least linear in the size of the class. + // static_assert( + // !PersistenceMatrixOptions::has_column_compression || !PersistenceMatrixOptions::has_map_column_container, + // "When column compression is used, the removal of columns is not implemented yet."); +} + +} // namespace persistence_matrix +} // namespace Gudhi + +#endif // MASTER_MATRIX_H diff --git a/src/Persistence_matrix/include/gudhi/persistence_matrix_options.h b/src/Persistence_matrix/include/gudhi/persistence_matrix_options.h new file mode 100644 index 0000000000..07ef2c31b7 --- /dev/null +++ b/src/Persistence_matrix/include/gudhi/persistence_matrix_options.h @@ -0,0 +1,169 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022-24 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +/** + * @file persistence_matrix_options.h + * @author Hannah Schreiber + * @brief Contains the options for the matrix template. + */ + +#ifndef PM_OPTIONS_INCLUDED +#define PM_OPTIONS_INCLUDED + +#include "Fields/Zp_field_operators.h" + +namespace Gudhi { +namespace persistence_matrix { + +/** + * @ingroup persistence_matrix + * + * @brief List of column types. + */ +enum Column_types { + LIST, /**< @ref List_column "": Underlying container is a std::list<@ref Cell*>. */ + SET, /**< @ref Set_column "": Underlying container is a std::set<@ref Cell*>. */ + HEAP, /**< @ref Heap_column "": Underlying container is a std::vector<@ref Cell*> ordered as a heap. + Is not compatible with row access and column compression. */ + VECTOR, /**< @ref Vector_column "": Underlying container is a std::vector<@ref Cell*> + with a lazy removal method. */ + NAIVE_VECTOR, /**< @ref Naive_vector_column "": Underlying container is a std::vector<@ref Cell*>. */ + UNORDERED_SET, /**< @ref Unordered_set_column "": Underlying container is a std::unordered_set<@ref Cell*>. */ + INTRUSIVE_LIST, /**< @ref Intrusive_list_column "": Underlying container is a boost::intrusive::list<@ref Cell>. */ + INTRUSIVE_SET /**< @ref Intrusive_set_column "": Underlying container is a boost::intrusive::set<@ref Cell>. */ +}; + +/** + * @ingroup persistence_matrix + * + * @brief List if indexation schemes. See @ref mp_indexation "description of indexation schemes" for more details + * about the meaning of the indexation types. + */ +enum Column_indexation_types { + CONTAINER, /**< Default use of @ref MatIdx indices. */ + POSITION, /**< All input and output @ref MatIdx indices are replaced with @ref PosIdx indices. */ + IDENTIFIER /**< All input and output @ref MatIdx indices are replaced with @ref IDIdx indices. */ +}; + +/** + * @struct Default_options persistence_matrix_options.h gudhi/persistence_matrix_options.h + * @ingroup persistence_matrix + * + * @brief Default option structure for @ref Matrix class. + * See the @ref PersistenceMatrixOptions concept for a more detailed description of the fields. + * Produces a @ref basematrix "base matrix" with no enabled option. + * + * To create other matrix types, the easiest is to simply inherit from this structure and overwrite only the options + * one is interested in. + * + * @tparam col_type Column type for the matrix. Default value: @ref Column_types::INTRUSIVE_SET + * @tparam is_z2_only Flag indicating if only \f$Z_2\f$ coefficient will be used with the matrix. Set to true if it + * is the case, false otherwise. Default value: true. + * @tparam FieldOperators Field operators used by the matrix, see FieldOperators concept. + * Only necessary if @p is_z2_only is false. + * Default value: @ref Gudhi::persistence_fields::Zp_field_operators<>. + */ +template > +struct Default_options +{ + using Field_coeff_operators = FieldOperators; + using dimension_type = int; + using index_type = unsigned int; + + static const bool is_z2 = is_z2_only; + static const Column_types column_type = col_type; + + static const Column_indexation_types column_indexation_type = Column_indexation_types::CONTAINER; + + static const bool has_column_compression = false; + static const bool has_column_and_row_swaps = false; + + static const bool has_map_column_container = false; + static const bool has_removable_columns = false; + + static const bool has_row_access = false; + static const bool has_intrusive_rows = true; + static const bool has_removable_rows = false; + + static const bool is_of_boundary_type = true; + + static const bool has_matrix_maximal_dimension_access = false; + static const bool has_column_pairings = false; + static const bool has_vine_update = false; + static const bool can_retrieve_representative_cycles = false; +}; + +//TODO: The following structures are the one used by the other modules or debug tests. +// They will probably be removed once the module was properly integrated. + +/** + * @brief Options used for the Zigzag persistence module. + * + * @tparam column_type Column type for the matrix. + */ +template +struct Zigzag_options : Default_options +{ + static const bool has_row_access = true; + static const bool has_column_pairings = false; + static const bool has_vine_update = true; + static const bool is_of_boundary_type = false; + static const bool has_map_column_container = true; + static const bool has_removable_columns = true; + static const bool has_removable_rows = true; +}; + +/** + * @brief Options needed to use the representative cycles. + * + * @tparam col_type Column type for the matrix. + */ +template +struct Representative_cycles_options : Default_options +{ + static const bool has_column_pairings = true; + static const bool can_retrieve_representative_cycles = true; +}; + +/** + * @brief Options used by the Multipersistence module. + * + * @tparam column_type Column type for the matrix. + */ +template +struct Multi_persistence_options : Default_options +{ + static const bool has_column_pairings = true; + static const bool has_vine_update = true; +}; + +/** + * @brief Options used by the cohomology module. + * + * @tparam column_type Column type for the matrix. + * @tparam is_z2_only True if Z2. + * @tparam FieldOperators Field operator. + */ +template > +struct Cohomology_persistence_options : Default_options +{ + static const bool has_row_access = true; + static const bool has_column_compression = true; + static const bool has_removable_rows = true; +}; + +} // namespace persistence_matrix +} // namespace Gudhi + +#endif // PM_OPTIONS_INCLUDED diff --git a/src/Persistence_matrix/test/CMakeLists.txt b/src/Persistence_matrix/test/CMakeLists.txt new file mode 100644 index 0000000000..adc2cbc4dd --- /dev/null +++ b/src/Persistence_matrix/test/CMakeLists.txt @@ -0,0 +1,384 @@ +project(Persistence_matrix_tests) + +include(GUDHI_boost_test) + +### Column Tests + +# Base columns + +add_executable(Persistence_matrix_column_tests_base_z2_no_row Persistence_matrix_column_tests_base.cpp) +target_compile_options(Persistence_matrix_column_tests_base_z2_no_row PUBLIC -DPM_TEST_Z2 -DPM_TEST_NO_ROW) +gudhi_add_boost_test(Persistence_matrix_column_tests_base_z2_no_row) + +add_executable(Persistence_matrix_column_tests_base_z2_with_row Persistence_matrix_column_tests_base.cpp) +target_compile_options(Persistence_matrix_column_tests_base_z2_with_row PUBLIC -DPM_TEST_Z2) +gudhi_add_boost_test(Persistence_matrix_column_tests_base_z2_with_row) + +add_executable(Persistence_matrix_column_tests_base_z2_with_rem_row Persistence_matrix_column_tests_base.cpp) +target_compile_options(Persistence_matrix_column_tests_base_z2_with_rem_row PUBLIC -DPM_TEST_Z2 -DPM_TEST_REM_ROW) +gudhi_add_boost_test(Persistence_matrix_column_tests_base_z2_with_rem_row) + +add_executable(Persistence_matrix_column_tests_base_z5_no_row Persistence_matrix_column_tests_base.cpp) +target_compile_options(Persistence_matrix_column_tests_base_z5_no_row PUBLIC -DPM_TEST_Z5 -DPM_TEST_NO_ROW) +gudhi_add_boost_test(Persistence_matrix_column_tests_base_z5_no_row) + +add_executable(Persistence_matrix_column_tests_base_z5_with_row Persistence_matrix_column_tests_base.cpp) +target_compile_options(Persistence_matrix_column_tests_base_z5_with_row PUBLIC -DPM_TEST_Z5) +gudhi_add_boost_test(Persistence_matrix_column_tests_base_z5_with_row) + +add_executable(Persistence_matrix_column_tests_base_z5_with_rem_row Persistence_matrix_column_tests_base.cpp) +target_compile_options(Persistence_matrix_column_tests_base_z5_with_rem_row PUBLIC -DPM_TEST_Z5 -DPM_TEST_REM_ROW) +gudhi_add_boost_test(Persistence_matrix_column_tests_base_z5_with_rem_row) + +# Boundary columns + +add_executable(Persistence_matrix_column_tests_boundary_z2_no_row Persistence_matrix_column_tests_boundary.cpp) +target_compile_options(Persistence_matrix_column_tests_boundary_z2_no_row PUBLIC -DPM_TEST_Z2 -DPM_TEST_NO_ROW) +gudhi_add_boost_test(Persistence_matrix_column_tests_boundary_z2_no_row) + +add_executable(Persistence_matrix_column_tests_boundary_z2_with_row Persistence_matrix_column_tests_boundary.cpp) +target_compile_options(Persistence_matrix_column_tests_boundary_z2_with_row PUBLIC -DPM_TEST_Z2) +gudhi_add_boost_test(Persistence_matrix_column_tests_boundary_z2_with_row) + +add_executable(Persistence_matrix_column_tests_boundary_z2_with_rem_row Persistence_matrix_column_tests_boundary.cpp) +target_compile_options(Persistence_matrix_column_tests_boundary_z2_with_rem_row PUBLIC -DPM_TEST_Z2 -DPM_TEST_REM_ROW) +gudhi_add_boost_test(Persistence_matrix_column_tests_boundary_z2_with_rem_row) + +add_executable(Persistence_matrix_column_tests_boundary_z5_no_row Persistence_matrix_column_tests_boundary.cpp) +target_compile_options(Persistence_matrix_column_tests_boundary_z5_no_row PUBLIC -DPM_TEST_Z5 -DPM_TEST_NO_ROW) +gudhi_add_boost_test(Persistence_matrix_column_tests_boundary_z5_no_row) + +add_executable(Persistence_matrix_column_tests_boundary_z5_with_row Persistence_matrix_column_tests_boundary.cpp) +target_compile_options(Persistence_matrix_column_tests_boundary_z5_with_row PUBLIC -DPM_TEST_Z5) +gudhi_add_boost_test(Persistence_matrix_column_tests_boundary_z5_with_row) + +add_executable(Persistence_matrix_column_tests_boundary_z5_with_rem_row Persistence_matrix_column_tests_boundary.cpp) +target_compile_options(Persistence_matrix_column_tests_boundary_z5_with_rem_row PUBLIC -DPM_TEST_Z5 -DPM_TEST_REM_ROW) +gudhi_add_boost_test(Persistence_matrix_column_tests_boundary_z5_with_rem_row) + +# Chain columns + +add_executable(Persistence_matrix_column_tests_chain_z2_no_row Persistence_matrix_column_tests_chain.cpp) +target_compile_options(Persistence_matrix_column_tests_chain_z2_no_row PUBLIC -DPM_TEST_Z2 -DPM_TEST_NO_ROW) +gudhi_add_boost_test(Persistence_matrix_column_tests_chain_z2_no_row) + +add_executable(Persistence_matrix_column_tests_chain_z2_with_row Persistence_matrix_column_tests_chain.cpp) +target_compile_options(Persistence_matrix_column_tests_chain_z2_with_row PUBLIC -DPM_TEST_Z2) +gudhi_add_boost_test(Persistence_matrix_column_tests_chain_z2_with_row) + +add_executable(Persistence_matrix_column_tests_chain_z2_with_rem_row Persistence_matrix_column_tests_chain.cpp) +target_compile_options(Persistence_matrix_column_tests_chain_z2_with_rem_row PUBLIC -DPM_TEST_Z2 -DPM_TEST_REM_ROW) +gudhi_add_boost_test(Persistence_matrix_column_tests_chain_z2_with_rem_row) + +add_executable(Persistence_matrix_column_tests_chain_z5_no_row Persistence_matrix_column_tests_chain.cpp) +target_compile_options(Persistence_matrix_column_tests_chain_z5_no_row PUBLIC -DPM_TEST_Z5 -DPM_TEST_NO_ROW) +gudhi_add_boost_test(Persistence_matrix_column_tests_chain_z5_no_row) + +add_executable(Persistence_matrix_column_tests_chain_z5_with_row Persistence_matrix_column_tests_chain.cpp) +target_compile_options(Persistence_matrix_column_tests_chain_z5_with_row PUBLIC -DPM_TEST_Z5) +gudhi_add_boost_test(Persistence_matrix_column_tests_chain_z5_with_row) + +add_executable(Persistence_matrix_column_tests_chain_z5_with_rem_row Persistence_matrix_column_tests_chain.cpp) +target_compile_options(Persistence_matrix_column_tests_chain_z5_with_rem_row PUBLIC -DPM_TEST_Z5 -DPM_TEST_REM_ROW) +gudhi_add_boost_test(Persistence_matrix_column_tests_chain_z5_with_rem_row) + +### Matrix Tests + +set(COL_TYPE -DPM_TEST_INTR_LIST) +set(TEST_ALL -DPM_TEST_ALL=0) +set(COMP_ALL FALSE) + +# Base matrices + +add_executable(Persistence_matrix_matrix_tests_z2_base Persistence_matrix_matrix_tests_z2_base.cpp) +target_compile_options(Persistence_matrix_matrix_tests_z2_base PUBLIC ${COL_TYPE} ${TEST_ALL}) +gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_base) + +add_executable(Persistence_matrix_matrix_tests_zp_base Persistence_matrix_matrix_tests_zp_base.cpp) +target_compile_options(Persistence_matrix_matrix_tests_zp_base PUBLIC ${COL_TYPE} ${TEST_ALL}) +gudhi_add_boost_test(Persistence_matrix_matrix_tests_zp_base) + +# Base matrices with column compression + +add_executable(Persistence_matrix_matrix_tests_z2_compression Persistence_matrix_matrix_tests_z2_compression.cpp) +target_compile_options(Persistence_matrix_matrix_tests_z2_compression PUBLIC ${COL_TYPE}) +gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_compression) + +add_executable(Persistence_matrix_matrix_tests_zp_compression Persistence_matrix_matrix_tests_zp_compression.cpp) +target_compile_options(Persistence_matrix_matrix_tests_zp_compression PUBLIC ${COL_TYPE}) +gudhi_add_boost_test(Persistence_matrix_matrix_tests_zp_compression) + +# Boundary matrices + +add_executable(Persistence_matrix_matrix_tests_z2_boundary_pos_idx Persistence_matrix_matrix_tests_z2_boundary.cpp) +target_compile_options(Persistence_matrix_matrix_tests_z2_boundary_pos_idx PUBLIC ${COL_TYPE} ${TEST_ALL}) +gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_boundary_pos_idx) + +add_executable(Persistence_matrix_matrix_tests_zp_boundary_pos_idx Persistence_matrix_matrix_tests_zp_boundary.cpp) +target_compile_options(Persistence_matrix_matrix_tests_zp_boundary_pos_idx PUBLIC ${COL_TYPE} ${TEST_ALL}) +gudhi_add_boost_test(Persistence_matrix_matrix_tests_zp_boundary_pos_idx) + +if(COMP_ALL) + add_executable(Persistence_matrix_matrix_tests_z2_boundary_id_idx Persistence_matrix_matrix_tests_z2_boundary.cpp) + target_compile_options(Persistence_matrix_matrix_tests_z2_boundary_id_idx PUBLIC ${COL_TYPE} -DPM_TEST_ID_IDX ${TEST_ALL}) + gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_boundary_id_idx) + + add_executable(Persistence_matrix_matrix_tests_zp_boundary_id_idx Persistence_matrix_matrix_tests_zp_boundary.cpp) + target_compile_options(Persistence_matrix_matrix_tests_zp_boundary_id_idx PUBLIC ${COL_TYPE} -DPM_TEST_ID_IDX ${TEST_ALL}) + gudhi_add_boost_test(Persistence_matrix_matrix_tests_zp_boundary_id_idx) +endif() + +# RU matrices + +add_executable(Persistence_matrix_matrix_tests_z2_ru_vine_pos_idx_no_barcode_no_max_dim Persistence_matrix_matrix_tests_z2_ru_vine.cpp) +target_compile_options(Persistence_matrix_matrix_tests_z2_ru_vine_pos_idx_no_barcode_no_max_dim PUBLIC ${COL_TYPE} ${TEST_ALL}) +gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_ru_vine_pos_idx_no_barcode_no_max_dim) + +add_executable(Persistence_matrix_matrix_tests_z2_ru_vine_pos_idx_no_barcode_max_dim Persistence_matrix_matrix_tests_z2_ru_vine.cpp) +target_compile_options(Persistence_matrix_matrix_tests_z2_ru_vine_pos_idx_no_barcode_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_MAX_DIM ${TEST_ALL}) +gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_ru_vine_pos_idx_no_barcode_max_dim) + +add_executable(Persistence_matrix_matrix_tests_z2_ru_vine_pos_idx_barcode_no_max_dim Persistence_matrix_matrix_tests_z2_ru_vine.cpp) +target_compile_options(Persistence_matrix_matrix_tests_z2_ru_vine_pos_idx_barcode_no_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_BARCODE ${TEST_ALL}) +gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_ru_vine_pos_idx_barcode_no_max_dim) + +add_executable(Persistence_matrix_matrix_tests_z2_ru_vine_pos_idx_barcode_max_dim Persistence_matrix_matrix_tests_z2_ru_vine.cpp) +target_compile_options(Persistence_matrix_matrix_tests_z2_ru_vine_pos_idx_barcode_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_BARCODE -DPM_TEST_MAX_DIM ${TEST_ALL}) +gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_ru_vine_pos_idx_barcode_max_dim) + +add_executable(Persistence_matrix_matrix_tests_z2_ru_rep_pos_idx_no_barcode_no_max_dim Persistence_matrix_matrix_tests_z2_ru_rep.cpp) +target_compile_options(Persistence_matrix_matrix_tests_z2_ru_rep_pos_idx_no_barcode_no_max_dim PUBLIC ${COL_TYPE}) +gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_ru_rep_pos_idx_no_barcode_no_max_dim) + +add_executable(Persistence_matrix_matrix_tests_z2_ru_rep_pos_idx_no_barcode_max_dim Persistence_matrix_matrix_tests_z2_ru_rep.cpp) +target_compile_options(Persistence_matrix_matrix_tests_z2_ru_rep_pos_idx_no_barcode_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_MAX_DIM) +gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_ru_rep_pos_idx_no_barcode_max_dim) + +add_executable(Persistence_matrix_matrix_tests_z2_ru_rep_pos_idx_barcode_no_max_dim Persistence_matrix_matrix_tests_z2_ru_rep.cpp) +target_compile_options(Persistence_matrix_matrix_tests_z2_ru_rep_pos_idx_barcode_no_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_BARCODE) +gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_ru_rep_pos_idx_barcode_no_max_dim) + +add_executable(Persistence_matrix_matrix_tests_z2_ru_rep_pos_idx_barcode_max_dim Persistence_matrix_matrix_tests_z2_ru_rep.cpp) +target_compile_options(Persistence_matrix_matrix_tests_z2_ru_rep_pos_idx_barcode_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_BARCODE -DPM_TEST_MAX_DIM) +gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_ru_rep_pos_idx_barcode_max_dim) + +add_executable(Persistence_matrix_matrix_tests_zp_ru_rep_pos_idx_no_barcode_no_max_dim Persistence_matrix_matrix_tests_zp_ru_rep.cpp) +target_compile_options(Persistence_matrix_matrix_tests_zp_ru_rep_pos_idx_no_barcode_no_max_dim PUBLIC ${COL_TYPE}) +gudhi_add_boost_test(Persistence_matrix_matrix_tests_zp_ru_rep_pos_idx_no_barcode_no_max_dim) + +add_executable(Persistence_matrix_matrix_tests_zp_ru_rep_pos_idx_no_barcode_max_dim Persistence_matrix_matrix_tests_zp_ru_rep.cpp) +target_compile_options(Persistence_matrix_matrix_tests_zp_ru_rep_pos_idx_no_barcode_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_MAX_DIM) +gudhi_add_boost_test(Persistence_matrix_matrix_tests_zp_ru_rep_pos_idx_no_barcode_max_dim) + +add_executable(Persistence_matrix_matrix_tests_zp_ru_rep_pos_idx_barcode_no_max_dim Persistence_matrix_matrix_tests_zp_ru_rep.cpp) +target_compile_options(Persistence_matrix_matrix_tests_zp_ru_rep_pos_idx_barcode_no_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_BARCODE) +gudhi_add_boost_test(Persistence_matrix_matrix_tests_zp_ru_rep_pos_idx_barcode_no_max_dim) + +add_executable(Persistence_matrix_matrix_tests_zp_ru_rep_pos_idx_barcode_max_dim Persistence_matrix_matrix_tests_zp_ru_rep.cpp) +target_compile_options(Persistence_matrix_matrix_tests_zp_ru_rep_pos_idx_barcode_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_BARCODE -DPM_TEST_MAX_DIM) +gudhi_add_boost_test(Persistence_matrix_matrix_tests_zp_ru_rep_pos_idx_barcode_max_dim) + +if(COMP_ALL) + add_executable(Persistence_matrix_matrix_tests_z2_ru_vine_id_idx_no_barcode_no_max_dim Persistence_matrix_matrix_tests_z2_ru_vine.cpp) + target_compile_options(Persistence_matrix_matrix_tests_z2_ru_vine_id_idx_no_barcode_no_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_ID_IDX ${TEST_ALL}) + gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_ru_vine_id_idx_no_barcode_no_max_dim) + + add_executable(Persistence_matrix_matrix_tests_z2_ru_vine_id_idx_no_barcode_max_dim Persistence_matrix_matrix_tests_z2_ru_vine.cpp) + target_compile_options(Persistence_matrix_matrix_tests_z2_ru_vine_id_idx_no_barcode_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_ID_IDX -DPM_TEST_MAX_DIM ${TEST_ALL}) + gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_ru_vine_id_idx_no_barcode_max_dim) + + add_executable(Persistence_matrix_matrix_tests_z2_ru_vine_id_idx_barcode_no_max_dim Persistence_matrix_matrix_tests_z2_ru_vine.cpp) + target_compile_options(Persistence_matrix_matrix_tests_z2_ru_vine_id_idx_barcode_no_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_ID_IDX -DPM_TEST_BARCODE ${TEST_ALL}) + gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_ru_vine_id_idx_barcode_no_max_dim) + + add_executable(Persistence_matrix_matrix_tests_z2_ru_vine_id_idx_barcode_max_dim Persistence_matrix_matrix_tests_z2_ru_vine.cpp) + target_compile_options(Persistence_matrix_matrix_tests_z2_ru_vine_id_idx_barcode_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_ID_IDX -DPM_TEST_BARCODE -DPM_TEST_MAX_DIM ${TEST_ALL}) + gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_ru_vine_id_idx_barcode_max_dim) + + add_executable(Persistence_matrix_matrix_tests_z2_ru_rep_id_idx_no_barcode_no_max_dim Persistence_matrix_matrix_tests_z2_ru_rep.cpp) + target_compile_options(Persistence_matrix_matrix_tests_z2_ru_rep_id_idx_no_barcode_no_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_ID_IDX) + gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_ru_rep_id_idx_no_barcode_no_max_dim) + + add_executable(Persistence_matrix_matrix_tests_z2_ru_rep_id_idx_no_barcode_max_dim Persistence_matrix_matrix_tests_z2_ru_rep.cpp) + target_compile_options(Persistence_matrix_matrix_tests_z2_ru_rep_id_idx_no_barcode_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_ID_IDX -DPM_TEST_MAX_DIM) + gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_ru_rep_id_idx_no_barcode_max_dim) + + add_executable(Persistence_matrix_matrix_tests_z2_ru_rep_id_idx_barcode_no_max_dim Persistence_matrix_matrix_tests_z2_ru_rep.cpp) + target_compile_options(Persistence_matrix_matrix_tests_z2_ru_rep_id_idx_barcode_no_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_ID_IDX -DPM_TEST_BARCODE) + gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_ru_rep_id_idx_barcode_no_max_dim) + + add_executable(Persistence_matrix_matrix_tests_z2_ru_rep_id_idx_barcode_max_dim Persistence_matrix_matrix_tests_z2_ru_rep.cpp) + target_compile_options(Persistence_matrix_matrix_tests_z2_ru_rep_id_idx_barcode_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_ID_IDX -DPM_TEST_BARCODE -DPM_TEST_MAX_DIM) + gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_ru_rep_id_idx_barcode_max_dim) + + add_executable(Persistence_matrix_matrix_tests_zp_ru_rep_id_idx_no_barcode_no_max_dim Persistence_matrix_matrix_tests_zp_ru_rep.cpp) + target_compile_options(Persistence_matrix_matrix_tests_zp_ru_rep_id_idx_no_barcode_no_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_ID_IDX) + gudhi_add_boost_test(Persistence_matrix_matrix_tests_zp_ru_rep_id_idx_no_barcode_no_max_dim) + + add_executable(Persistence_matrix_matrix_tests_zp_ru_rep_id_idx_no_barcode_max_dim Persistence_matrix_matrix_tests_zp_ru_rep.cpp) + target_compile_options(Persistence_matrix_matrix_tests_zp_ru_rep_id_idx_no_barcode_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_ID_IDX -DPM_TEST_MAX_DIM) + gudhi_add_boost_test(Persistence_matrix_matrix_tests_zp_ru_rep_id_idx_no_barcode_max_dim) + + add_executable(Persistence_matrix_matrix_tests_zp_ru_rep_id_idx_barcode_no_max_dim Persistence_matrix_matrix_tests_zp_ru_rep.cpp) + target_compile_options(Persistence_matrix_matrix_tests_zp_ru_rep_id_idx_barcode_no_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_ID_IDX -DPM_TEST_BARCODE) + gudhi_add_boost_test(Persistence_matrix_matrix_tests_zp_ru_rep_id_idx_barcode_no_max_dim) + + add_executable(Persistence_matrix_matrix_tests_zp_ru_rep_id_idx_barcode_max_dim Persistence_matrix_matrix_tests_zp_ru_rep.cpp) + target_compile_options(Persistence_matrix_matrix_tests_zp_ru_rep_id_idx_barcode_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_ID_IDX -DPM_TEST_BARCODE -DPM_TEST_MAX_DIM) + gudhi_add_boost_test(Persistence_matrix_matrix_tests_zp_ru_rep_id_idx_barcode_max_dim) +endif() + +# Chain matrices + +if(COMP_ALL) + add_executable(Persistence_matrix_matrix_tests_z2_chain_vine_pos_idx_no_rem_col_no_max_dim Persistence_matrix_matrix_tests_z2_chain_vine.cpp) + target_compile_options(Persistence_matrix_matrix_tests_z2_chain_vine_pos_idx_no_rem_col_no_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_BARCODE) + gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_chain_vine_pos_idx_no_rem_col_no_max_dim) + + add_executable(Persistence_matrix_matrix_tests_z2_chain_vine_pos_idx_no_rem_col_max_dim Persistence_matrix_matrix_tests_z2_chain_vine.cpp) + target_compile_options(Persistence_matrix_matrix_tests_z2_chain_vine_pos_idx_no_rem_col_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_MAX_DIM -DPM_TEST_BARCODE ${TEST_ALL}) + gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_chain_vine_pos_idx_no_rem_col_max_dim) + + add_executable(Persistence_matrix_matrix_tests_z2_chain_vine_pos_idx_rem_col_no_max_dim Persistence_matrix_matrix_tests_z2_chain_vine.cpp) + target_compile_options(Persistence_matrix_matrix_tests_z2_chain_vine_pos_idx_rem_col_no_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_REM_COL -DPM_TEST_BARCODE ${TEST_ALL}) + gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_chain_vine_pos_idx_rem_col_no_max_dim) + + add_executable(Persistence_matrix_matrix_tests_z2_chain_vine_pos_idx_rem_col_max_dim Persistence_matrix_matrix_tests_z2_chain_vine.cpp) + target_compile_options(Persistence_matrix_matrix_tests_z2_chain_vine_pos_idx_rem_col_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_REM_COL -DPM_TEST_MAX_DIM -DPM_TEST_BARCODE ${TEST_ALL}) + gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_chain_vine_pos_idx_rem_col_max_dim) + + add_executable(Persistence_matrix_matrix_tests_z2_chain_vine_pos_idx_no_rem_col_no_max_dim_no_barcode Persistence_matrix_matrix_tests_z2_chain_vine.cpp) + target_compile_options(Persistence_matrix_matrix_tests_z2_chain_vine_pos_idx_no_rem_col_no_max_dim_no_barcode PUBLIC ${COL_TYPE}) + gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_chain_vine_pos_idx_no_rem_col_no_max_dim_no_barcode) + + add_executable(Persistence_matrix_matrix_tests_z2_chain_vine_pos_idx_no_rem_col_max_dim_no_barcode Persistence_matrix_matrix_tests_z2_chain_vine.cpp) + target_compile_options(Persistence_matrix_matrix_tests_z2_chain_vine_pos_idx_no_rem_col_max_dim_no_barcode PUBLIC ${COL_TYPE} -DPM_TEST_MAX_DIM) + gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_chain_vine_pos_idx_no_rem_col_max_dim_no_barcode) + + add_executable(Persistence_matrix_matrix_tests_z2_chain_vine_pos_idx_rem_col_no_max_dim_no_barcode Persistence_matrix_matrix_tests_z2_chain_vine.cpp) + target_compile_options(Persistence_matrix_matrix_tests_z2_chain_vine_pos_idx_rem_col_no_max_dim_no_barcode PUBLIC ${COL_TYPE} -DPM_TEST_REM_COL) + gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_chain_vine_pos_idx_rem_col_no_max_dim_no_barcode) + + add_executable(Persistence_matrix_matrix_tests_z2_chain_vine_pos_idx_rem_col_max_dim_no_barcode Persistence_matrix_matrix_tests_z2_chain_vine.cpp) + target_compile_options(Persistence_matrix_matrix_tests_z2_chain_vine_pos_idx_rem_col_max_dim_no_barcode PUBLIC ${COL_TYPE} -DPM_TEST_REM_COL -DPM_TEST_MAX_DIM) + gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_chain_vine_pos_idx_rem_col_max_dim_no_barcode) + + add_executable(Persistence_matrix_matrix_tests_z2_chain_rep_pos_idx_col_no_max_dim Persistence_matrix_matrix_tests_z2_chain_rep.cpp) + target_compile_options(Persistence_matrix_matrix_tests_z2_chain_rep_pos_idx_col_no_max_dim PUBLIC ${COL_TYPE} ${TEST_ALL}) + gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_chain_rep_pos_idx_col_no_max_dim) + + add_executable(Persistence_matrix_matrix_tests_z2_chain_rep_pos_idx_col_max_dim Persistence_matrix_matrix_tests_z2_chain_rep.cpp) + target_compile_options(Persistence_matrix_matrix_tests_z2_chain_rep_pos_idx_col_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_MAX_DIM ${TEST_ALL}) + gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_chain_rep_pos_idx_col_max_dim) + + add_executable(Persistence_matrix_matrix_tests_zp_chain_rep_pos_idx_col_no_max_dim Persistence_matrix_matrix_tests_zp_chain_rep.cpp) + target_compile_options(Persistence_matrix_matrix_tests_zp_chain_rep_pos_idx_col_no_max_dim PUBLIC ${COL_TYPE} ${TEST_ALL}) + gudhi_add_boost_test(Persistence_matrix_matrix_tests_zp_chain_rep_pos_idx_col_no_max_dim) + + add_executable(Persistence_matrix_matrix_tests_zp_chain_rep_pos_idx_col_max_dim Persistence_matrix_matrix_tests_zp_chain_rep.cpp) + target_compile_options(Persistence_matrix_matrix_tests_zp_chain_rep_pos_idx_col_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_MAX_DIM ${TEST_ALL}) + gudhi_add_boost_test(Persistence_matrix_matrix_tests_zp_chain_rep_pos_idx_col_max_dim) + + add_executable(Persistence_matrix_matrix_tests_z2_chain_barcode_pos_idx_col_no_max_dim Persistence_matrix_matrix_tests_z2_chain_barcode.cpp) + target_compile_options(Persistence_matrix_matrix_tests_z2_chain_barcode_pos_idx_col_no_max_dim PUBLIC ${COL_TYPE}) + gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_chain_barcode_pos_idx_col_no_max_dim) + + add_executable(Persistence_matrix_matrix_tests_z2_chain_barcode_pos_idx_col_max_dim Persistence_matrix_matrix_tests_z2_chain_barcode.cpp) + target_compile_options(Persistence_matrix_matrix_tests_z2_chain_barcode_pos_idx_col_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_MAX_DIM) + gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_chain_barcode_pos_idx_col_max_dim) + + add_executable(Persistence_matrix_matrix_tests_zp_chain_barcode_pos_idx_col_no_max_dim Persistence_matrix_matrix_tests_zp_chain_barcode.cpp) + target_compile_options(Persistence_matrix_matrix_tests_zp_chain_barcode_pos_idx_col_no_max_dim PUBLIC ${COL_TYPE}) + gudhi_add_boost_test(Persistence_matrix_matrix_tests_zp_chain_barcode_pos_idx_col_no_max_dim) + + add_executable(Persistence_matrix_matrix_tests_zp_chain_barcode_pos_idx_col_max_dim Persistence_matrix_matrix_tests_zp_chain_barcode.cpp) + target_compile_options(Persistence_matrix_matrix_tests_zp_chain_barcode_pos_idx_col_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_MAX_DIM) + gudhi_add_boost_test(Persistence_matrix_matrix_tests_zp_chain_barcode_pos_idx_col_max_dim) +endif() + +add_executable(Persistence_matrix_matrix_tests_z2_chain_vine_id_idx_no_rem_col_no_max_dim Persistence_matrix_matrix_tests_z2_chain_vine.cpp) +target_compile_options(Persistence_matrix_matrix_tests_z2_chain_vine_id_idx_no_rem_col_no_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_ID_IDX -DPM_TEST_BARCODE ${TEST_ALL}) +gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_chain_vine_id_idx_no_rem_col_no_max_dim) + +add_executable(Persistence_matrix_matrix_tests_z2_chain_vine_id_idx_no_rem_col_max_dim Persistence_matrix_matrix_tests_z2_chain_vine.cpp) +target_compile_options(Persistence_matrix_matrix_tests_z2_chain_vine_id_idx_no_rem_col_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_ID_IDX -DPM_TEST_MAX_DIM -DPM_TEST_BARCODE ${TEST_ALL}) +gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_chain_vine_id_idx_no_rem_col_max_dim) + +add_executable(Persistence_matrix_matrix_tests_z2_chain_vine_id_idx_rem_col_no_max_dim Persistence_matrix_matrix_tests_z2_chain_vine.cpp) +target_compile_options(Persistence_matrix_matrix_tests_z2_chain_vine_id_idx_rem_col_no_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_ID_IDX -DPM_TEST_REM_COL -DPM_TEST_BARCODE ${TEST_ALL}) +gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_chain_vine_id_idx_rem_col_no_max_dim) + +add_executable(Persistence_matrix_matrix_tests_z2_chain_vine_id_idx_rem_col_max_dim Persistence_matrix_matrix_tests_z2_chain_vine.cpp) +target_compile_options(Persistence_matrix_matrix_tests_z2_chain_vine_id_idx_rem_col_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_ID_IDX -DPM_TEST_REM_COL -DPM_TEST_MAX_DIM -DPM_TEST_BARCODE ${TEST_ALL}) +gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_chain_vine_id_idx_rem_col_max_dim) + +add_executable(Persistence_matrix_matrix_tests_z2_chain_vine_id_idx_no_rem_col_no_max_dim_no_barcode Persistence_matrix_matrix_tests_z2_chain_vine.cpp) +target_compile_options(Persistence_matrix_matrix_tests_z2_chain_vine_id_idx_no_rem_col_no_max_dim_no_barcode PUBLIC ${COL_TYPE} -DPM_TEST_ID_IDX) +gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_chain_vine_id_idx_no_rem_col_no_max_dim_no_barcode) + +add_executable(Persistence_matrix_matrix_tests_z2_chain_vine_id_idx_no_rem_col_max_dim_no_barcode Persistence_matrix_matrix_tests_z2_chain_vine.cpp) +target_compile_options(Persistence_matrix_matrix_tests_z2_chain_vine_id_idx_no_rem_col_max_dim_no_barcode PUBLIC ${COL_TYPE} -DPM_TEST_ID_IDX -DPM_TEST_MAX_DIM) +gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_chain_vine_id_idx_no_rem_col_max_dim_no_barcode) + +add_executable(Persistence_matrix_matrix_tests_z2_chain_vine_id_idx_rem_col_no_max_dim_no_barcode Persistence_matrix_matrix_tests_z2_chain_vine.cpp) +target_compile_options(Persistence_matrix_matrix_tests_z2_chain_vine_id_idx_rem_col_no_max_dim_no_barcode PUBLIC ${COL_TYPE} -DPM_TEST_ID_IDX -DPM_TEST_REM_COL) +gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_chain_vine_id_idx_rem_col_no_max_dim_no_barcode) + +add_executable(Persistence_matrix_matrix_tests_z2_chain_vine_id_idx_rem_col_max_dim_no_barcode Persistence_matrix_matrix_tests_z2_chain_vine.cpp) +target_compile_options(Persistence_matrix_matrix_tests_z2_chain_vine_id_idx_rem_col_max_dim_no_barcode PUBLIC ${COL_TYPE} -DPM_TEST_ID_IDX -DPM_TEST_REM_COL -DPM_TEST_MAX_DIM) +gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_chain_vine_id_idx_rem_col_max_dim_no_barcode) + +add_executable(Persistence_matrix_matrix_tests_z2_chain_rep_id_idx_col_no_max_dim Persistence_matrix_matrix_tests_z2_chain_rep.cpp) +target_compile_options(Persistence_matrix_matrix_tests_z2_chain_rep_id_idx_col_no_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_ID_IDX ${TEST_ALL}) +gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_chain_rep_id_idx_col_no_max_dim) + +add_executable(Persistence_matrix_matrix_tests_z2_chain_rep_id_idx_col_max_dim Persistence_matrix_matrix_tests_z2_chain_rep.cpp) +target_compile_options(Persistence_matrix_matrix_tests_z2_chain_rep_id_idx_col_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_ID_IDX -DPM_TEST_MAX_DIM ${TEST_ALL}) +gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_chain_rep_id_idx_col_max_dim) + +add_executable(Persistence_matrix_matrix_tests_zp_chain_rep_id_idx_col_no_max_dim Persistence_matrix_matrix_tests_zp_chain_rep.cpp) +target_compile_options(Persistence_matrix_matrix_tests_zp_chain_rep_id_idx_col_no_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_ID_IDX ${TEST_ALL}) +gudhi_add_boost_test(Persistence_matrix_matrix_tests_zp_chain_rep_id_idx_col_no_max_dim) + +add_executable(Persistence_matrix_matrix_tests_zp_chain_rep_id_idx_col_max_dim Persistence_matrix_matrix_tests_zp_chain_rep.cpp) +target_compile_options(Persistence_matrix_matrix_tests_zp_chain_rep_id_idx_col_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_ID_IDX -DPM_TEST_MAX_DIM ${TEST_ALL}) +gudhi_add_boost_test(Persistence_matrix_matrix_tests_zp_chain_rep_id_idx_col_max_dim) + +add_executable(Persistence_matrix_matrix_tests_z2_chain_barcode_id_idx_col_no_max_dim Persistence_matrix_matrix_tests_z2_chain_barcode.cpp) +target_compile_options(Persistence_matrix_matrix_tests_z2_chain_barcode_id_idx_col_no_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_ID_IDX) +gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_chain_barcode_id_idx_col_no_max_dim) + +add_executable(Persistence_matrix_matrix_tests_z2_chain_barcode_id_idx_col_max_dim Persistence_matrix_matrix_tests_z2_chain_barcode.cpp) +target_compile_options(Persistence_matrix_matrix_tests_z2_chain_barcode_id_idx_col_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_ID_IDX -DPM_TEST_MAX_DIM) +gudhi_add_boost_test(Persistence_matrix_matrix_tests_z2_chain_barcode_id_idx_col_max_dim) + +add_executable(Persistence_matrix_matrix_tests_zp_chain_barcode_id_idx_col_no_max_dim Persistence_matrix_matrix_tests_zp_chain_barcode.cpp) +target_compile_options(Persistence_matrix_matrix_tests_zp_chain_barcode_id_idx_col_no_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_ID_IDX) +gudhi_add_boost_test(Persistence_matrix_matrix_tests_zp_chain_barcode_id_idx_col_no_max_dim) + +add_executable(Persistence_matrix_matrix_tests_zp_chain_barcode_id_idx_col_max_dim Persistence_matrix_matrix_tests_zp_chain_barcode.cpp) +target_compile_options(Persistence_matrix_matrix_tests_zp_chain_barcode_id_idx_col_max_dim PUBLIC ${COL_TYPE} -DPM_TEST_ID_IDX -DPM_TEST_MAX_DIM) +gudhi_add_boost_test(Persistence_matrix_matrix_tests_zp_chain_barcode_id_idx_col_max_dim) + +### Field Tests + +if(GMP_FOUND AND GMPXX_FOUND) + add_executable(Persistence_matrix_field_tests Persistence_matrix_field_tests.cpp) + target_compile_options(Persistence_matrix_field_tests PUBLIC -DPM_GMP_FOUND) + target_link_libraries(Persistence_matrix_field_tests ${GMPXX_LIBRARIES} ${GMP_LIBRARIES}) + gudhi_add_boost_test(Persistence_matrix_field_tests) + + add_executable(Persistence_matrix_field_operators_tests Persistence_matrix_field_operators_tests.cpp) + target_compile_options(Persistence_matrix_field_operators_tests PUBLIC -DPM_GMP_FOUND) + target_link_libraries(Persistence_matrix_field_operators_tests ${GMPXX_LIBRARIES} ${GMP_LIBRARIES}) + gudhi_add_boost_test(Persistence_matrix_field_operators_tests) +else() + add_executable(Persistence_matrix_field_tests Persistence_matrix_field_tests.cpp) + gudhi_add_boost_test(Persistence_matrix_field_tests) + + add_executable(Persistence_matrix_field_operators_tests Persistence_matrix_field_operators_tests.cpp) + gudhi_add_boost_test(Persistence_matrix_field_operators_tests) +endif() + + + diff --git a/src/Persistence_matrix/test/Persistence_matrix_column_tests_base.cpp b/src/Persistence_matrix/test/Persistence_matrix_column_tests_base.cpp new file mode 100644 index 0000000000..aaf0a454e5 --- /dev/null +++ b/src/Persistence_matrix/test/Persistence_matrix_column_tests_base.cpp @@ -0,0 +1,159 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2023 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE "persistence_matrix" +#include + +#include "pm_column_tests.h" +#include "pm_column_tests_boost_type_lists.h" + +using option_name_list = mp_list_q; + +#ifdef PM_TEST_Z2 +#ifdef PM_TEST_NO_ROW + +using z2_no_row_access_columns = columns_list >; + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_column_constructors, Column, z2_no_row_access_columns) { + column_test_common_constructors(); + column_test_base_boundary_constructors(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_z2_column_content_access, Column, z2_no_row_access_columns) { + typename Column::Column_settings settings; + std::vector matrix = build_column_matrix(settings); + column_test_common_z2_content_access(matrix); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_z2_column_operators, Column, z2_no_row_access_columns) { + typename Column::Column_settings settings; + std::vector matrix = build_column_matrix(settings); + column_test_common_z2_operators(matrix); + + matrix = build_column_matrix(settings); + column_test_base_z2_operators(matrix); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_z2_column_other, Column, z2_no_row_access_columns) { + column_test_base_boundary_z2_methods(); +} + +#else + +#ifdef PM_TEST_REM_ROW +using z2_only_row_access_columns = columns_list >; +#else +using z2_only_row_access_columns = columns_list >; +#endif + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_z2_column_with_row_access_content_access, Column, z2_only_row_access_columns) { + typename Column::Master::row_container_type rows; // do not destroy before matrix + typename Column::Column_settings settings; + std::vector matrix = build_column_matrix(rows, settings); + column_test_common_z2_content_access(matrix); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_z2_column_with_row_access_operators, Column, z2_only_row_access_columns) { + typename Column::Master::row_container_type rows; // do not destroy before matrix + typename Column::Column_settings settings; + std::vector matrix = build_column_matrix(rows, settings); + column_test_common_z2_operators(matrix); + + matrix.clear(); + rows.clear(); + matrix = build_column_matrix(rows, settings); + column_test_base_z2_operators(matrix); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_column_row_access_constructors, Column, z2_only_row_access_columns) { + typename Column::Master::row_container_type rows; // do not destroy before matrix + typename Column::Column_settings settings; + std::vector matrix = build_column_matrix(rows, settings); + + column_test_row_access_constructors(matrix, rows); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_z2_column_other, Column, z2_only_row_access_columns) { + column_test_base_boundary_z2_methods(); +} + +#endif +#endif + +#ifdef PM_TEST_Z5 +#ifdef PM_TEST_NO_ROW + +using z5_no_row_access_columns = columns_list >; + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_column_constructors, Column, z5_no_row_access_columns) { + column_test_common_constructors(); + column_test_base_boundary_constructors(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_z5_column_content_access, Column, z5_no_row_access_columns) { + typename Column::Column_settings settings(5); + std::vector matrix = build_column_matrix(settings); + column_test_common_z5_content_access(matrix); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_z5_column_operators, Column, z5_no_row_access_columns) { + typename Column::Column_settings settings(5); + std::vector matrix = build_column_matrix(settings); + column_test_common_z5_operators(matrix); + + matrix = build_column_matrix(settings); + column_test_base_z5_operators(matrix); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_z5_column_other, Column, z5_no_row_access_columns) { + column_test_base_boundary_z5_methods(); +} + +#else + +#ifdef PM_TEST_REM_ROW +using z5_only_row_access_columns = columns_list >; +#else +using z5_only_row_access_columns = columns_list >; +#endif + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_z5_column_with_row_access_content_access, Column, z5_only_row_access_columns) { + typename Column::Master::row_container_type rows; // do not destroy before matrix + typename Column::Column_settings settings(5); + std::vector matrix = build_column_matrix(rows, settings); + column_test_common_z5_content_access(matrix); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_z5_column_with_row_access_operators, Column, z5_only_row_access_columns) { + typename Column::Master::row_container_type rows; // do not destroy before matrix + typename Column::Column_settings settings(5); + std::vector matrix = build_column_matrix(rows, settings); + column_test_common_z5_operators(matrix); + + matrix.clear(); + rows.clear(); + matrix = build_column_matrix(rows, settings); + column_test_base_z5_operators(matrix); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_column_row_access_constructors, Column, z5_only_row_access_columns) { + typename Column::Master::row_container_type rows; // do not destroy before matrix + typename Column::Column_settings settings(5); + std::vector matrix = build_column_matrix(rows, settings); + + column_test_row_access_constructors(matrix, rows); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_z5_column_other, Column, z5_only_row_access_columns) { + column_test_base_boundary_z5_methods(); +} + +#endif +#endif diff --git a/src/Persistence_matrix/test/Persistence_matrix_column_tests_boundary.cpp b/src/Persistence_matrix/test/Persistence_matrix_column_tests_boundary.cpp new file mode 100644 index 0000000000..1224881f4e --- /dev/null +++ b/src/Persistence_matrix/test/Persistence_matrix_column_tests_boundary.cpp @@ -0,0 +1,193 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2023 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE "persistence_matrix" +#include + +#include "pm_column_tests.h" +#include "pm_column_tests_boost_type_lists.h" + +using option_name_list = mp_list_q; + +#ifdef PM_TEST_Z2 +#ifdef PM_TEST_NO_ROW + +using z2_no_row_access_columns = columns_list >; + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_column_constructors, Column, z2_no_row_access_columns) { + column_test_common_constructors(); + column_test_base_boundary_constructors(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_z2_column_content_access, Column, z2_no_row_access_columns) { + typename Column::Column_settings settings; + std::vector matrix = build_column_matrix(settings); + column_test_common_z2_content_access(matrix); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_z2_column_operators, Column, z2_no_row_access_columns) { + typename Column::Column_settings settings; + std::vector matrix = build_column_matrix(settings); + column_test_common_z2_operators(matrix); + + matrix = build_column_matrix(settings); + column_test_boundary_z2_operators(matrix); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_z2_column_other, Column, z2_no_row_access_columns) { + typename Column::Column_settings settings; + column_test_base_boundary_z2_methods(); + + std::vector matrix = build_base_boundary_column_matrix(settings); + column_test_boundary_methods(matrix); + + matrix = build_column_matrix(settings); + column_test_boundary_chain_methods(matrix); +} + +#else + +#ifdef PM_TEST_REM_ROW +using z2_only_row_access_columns = columns_list >; +#else +using z2_only_row_access_columns = columns_list >; +#endif + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_z2_column_with_row_access_content_access, Column, z2_only_row_access_columns) { + typename Column::Master::row_container_type rows; // do not destroy before matrix + typename Column::Column_settings settings; + std::vector matrix = build_column_matrix(rows, settings); + column_test_common_z2_content_access(matrix); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_z2_column_with_row_access_operators, Column, z2_only_row_access_columns) { + typename Column::Master::row_container_type rows; // do not destroy before matrix + typename Column::Column_settings settings; + std::vector matrix = build_column_matrix(rows, settings); + column_test_common_z2_operators(matrix); + + matrix.clear(); + rows.clear(); + matrix = build_column_matrix(rows, settings); + column_test_boundary_z2_operators(matrix); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_column_row_access_constructors, Column, z2_only_row_access_columns) { + typename Column::Master::row_container_type rows; // do not destroy before matrix + typename Column::Column_settings settings; + std::vector matrix = build_column_matrix(rows, settings); + + column_test_row_access_constructors(matrix, rows); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_z2_column_with_row_access_other, Column, z2_only_row_access_columns) { + column_test_base_boundary_z2_methods(); + + typename Column::Master::row_container_type rows; // do not destroy before matrix + typename Column::Column_settings settings; + std::vector matrix = build_base_boundary_column_matrix(rows, settings); + column_test_boundary_methods(matrix); + + matrix.clear(); + rows.clear(); + matrix = build_column_matrix(rows, settings); + column_test_boundary_chain_methods(matrix); +} + +#endif +#endif + +#ifdef PM_TEST_Z5 +#ifdef PM_TEST_NO_ROW + +using z5_no_row_access_columns = columns_list >; + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_column_constructors, Column, z5_no_row_access_columns) { + column_test_common_constructors(); + column_test_base_boundary_constructors(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_z5_column_content_access, Column, z5_no_row_access_columns) { + typename Column::Column_settings settings(5); + std::vector matrix = build_column_matrix(settings); + column_test_common_z5_content_access(matrix); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_z5_column_operators, Column, z5_no_row_access_columns) { + typename Column::Column_settings settings(5); + std::vector matrix = build_column_matrix(settings); + column_test_common_z5_operators(matrix); + + matrix = build_column_matrix(settings); + column_test_boundary_z5_operators(matrix); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_z5_column_other, Column, z5_no_row_access_columns) { + typename Column::Column_settings settings(5); + column_test_base_boundary_z5_methods(); + + std::vector matrix = build_base_boundary_column_matrix(settings); + column_test_boundary_methods(matrix); + + matrix = build_column_matrix(settings); + column_test_boundary_chain_methods(matrix); +} + +#else + +#ifdef PM_TEST_REM_ROW +using z5_only_row_access_columns = columns_list >; +#else +using z5_only_row_access_columns = columns_list >; +#endif + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_z5_column_with_row_access_content_access, Column, z5_only_row_access_columns) { + typename Column::Master::row_container_type rows; // do not destroy before matrix + typename Column::Column_settings settings(5); + std::vector matrix = build_column_matrix(rows, settings); + column_test_common_z5_content_access(matrix); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_z5_column_with_row_access_operators, Column, z5_only_row_access_columns) { + typename Column::Master::row_container_type rows; // do not destroy before matrix + typename Column::Column_settings settings(5); + std::vector matrix = build_column_matrix(rows, settings); + column_test_common_z5_operators(matrix); + + matrix.clear(); + rows.clear(); + matrix = build_column_matrix(rows, settings); + column_test_boundary_z5_operators(matrix); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_column_row_access_constructors, Column, z5_only_row_access_columns) { + typename Column::Master::row_container_type rows; // do not destroy before matrix + typename Column::Column_settings settings(5); + std::vector matrix = build_column_matrix(rows, settings); + + column_test_row_access_constructors(matrix, rows); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_z5_column_with_row_access_other, Column, z5_only_row_access_columns) { + typename Column::Column_settings settings(5); + column_test_base_boundary_z5_methods(); + + typename Column::Master::row_container_type rows; // do not destroy before matrix + std::vector matrix = build_base_boundary_column_matrix(rows, settings); + column_test_boundary_methods(matrix); + + matrix.clear(); + rows.clear(); + matrix = build_column_matrix(rows, settings); + column_test_boundary_chain_methods(matrix); +} + +#endif +#endif diff --git a/src/Persistence_matrix/test/Persistence_matrix_column_tests_chain.cpp b/src/Persistence_matrix/test/Persistence_matrix_column_tests_chain.cpp new file mode 100644 index 0000000000..4af567f940 --- /dev/null +++ b/src/Persistence_matrix/test/Persistence_matrix_column_tests_chain.cpp @@ -0,0 +1,159 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2023 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE "persistence_matrix" +#include + +#include "pm_column_tests.h" +#include "pm_column_tests_boost_type_lists.h" + +using option_name_list = mp_list_q; + +#ifdef PM_TEST_Z2 +#ifdef PM_TEST_NO_ROW + +using z2_no_row_access_columns = columns_list >; + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_column_constructors, Column, z2_no_row_access_columns) { + column_test_common_constructors(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_z2_column_content_access, Column, z2_no_row_access_columns) { + typename Column::Column_settings settings; + std::vector matrix = build_column_matrix(settings); + column_test_common_z2_content_access(matrix); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_z2_column_operators, Column, z2_no_row_access_columns) { + typename Column::Column_settings settings; + std::vector matrix = build_column_matrix(settings); + column_test_common_z2_operators(matrix); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_column_other, Column, z2_no_row_access_columns) { + typename Column::Column_settings settings; + std::vector matrix = build_column_matrix(settings); + + column_test_chain_methods(); + column_test_boundary_chain_methods(matrix); +} + +#else + +#ifdef PM_TEST_REM_ROW +using z2_only_row_access_columns = columns_list >; +#else +using z2_only_row_access_columns = columns_list >; +#endif + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_z2_column_with_row_access_content_access, Column, z2_only_row_access_columns) { + typename Column::Master::row_container_type rows; // do not destroy before matrix + typename Column::Column_settings settings; + std::vector matrix = build_column_matrix(rows, settings); + column_test_common_z2_content_access(matrix); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_z2_column_with_row_access_operators, Column, z2_only_row_access_columns) { + typename Column::Master::row_container_type rows; // do not destroy before matrix + typename Column::Column_settings settings; + std::vector matrix = build_column_matrix(rows, settings); + column_test_common_z2_operators(matrix); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_column_row_access_constructors, Column, z2_only_row_access_columns) { + typename Column::Master::row_container_type rows; // do not destroy before matrix + typename Column::Column_settings settings; + std::vector matrix = build_column_matrix(rows, settings); + + column_test_row_access_constructors(matrix, rows); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_column_with_row_access_other, Column, z2_only_row_access_columns) { + typename Column::Master::row_container_type rows; // do not destroy before matrix + typename Column::Column_settings settings; + std::vector matrix = build_column_matrix(rows, settings); + + column_test_chain_methods(); + column_test_boundary_chain_methods(matrix); +} + +#endif +#endif + +#ifdef PM_TEST_Z5 +#ifdef PM_TEST_NO_ROW + +using z5_no_row_access_columns = columns_list >; + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_column_constructors, Column, z5_no_row_access_columns) { + column_test_common_constructors(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_z5_column_content_access, Column, z5_no_row_access_columns) { + typename Column::Column_settings settings(5); + std::vector matrix = build_column_matrix(settings); + column_test_common_z5_content_access(matrix); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_z5_column_operators, Column, z5_no_row_access_columns) { + typename Column::Column_settings settings(5); + std::vector matrix = build_column_matrix(settings); + column_test_common_z5_operators(matrix); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_column_other, Column, z5_no_row_access_columns) { + typename Column::Column_settings settings(5); + std::vector matrix = build_column_matrix(settings); + + column_test_chain_methods(); + column_test_boundary_chain_methods(matrix); +} + +#else + +#ifdef PM_TEST_REM_ROW +using z5_only_row_access_columns = columns_list >; +#else +using z5_only_row_access_columns = columns_list >; +#endif + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_z5_column_with_row_access_content_access, Column, z5_only_row_access_columns) { + typename Column::Master::row_container_type rows; // do not destroy before matrix + typename Column::Column_settings settings(5); + std::vector matrix = build_column_matrix(rows, settings); + column_test_common_z5_content_access(matrix); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_z5_column_with_row_access_operators, Column, z5_only_row_access_columns) { + typename Column::Master::row_container_type rows; // do not destroy before matrix + typename Column::Column_settings settings(5); + std::vector matrix = build_column_matrix(rows, settings); + column_test_common_z5_operators(matrix); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_column_row_access_constructors, Column, z5_only_row_access_columns) { + typename Column::Master::row_container_type rows; // do not destroy before matrix + typename Column::Column_settings settings(5); + std::vector matrix = build_column_matrix(rows, settings); + + column_test_row_access_constructors(matrix, rows); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_column_with_row_access_other, Column, z5_only_row_access_columns) { + typename Column::Master::row_container_type rows; // do not destroy before matrix + typename Column::Column_settings settings(5); + std::vector matrix = build_column_matrix(rows, settings); + + column_test_chain_methods(); + column_test_boundary_chain_methods(matrix); +} + +#endif +#endif diff --git a/src/Persistence_matrix/test/Persistence_matrix_field_operators_tests.cpp b/src/Persistence_matrix/test/Persistence_matrix_field_operators_tests.cpp new file mode 100644 index 0000000000..e070e14a3f --- /dev/null +++ b/src/Persistence_matrix/test/Persistence_matrix_field_operators_tests.cpp @@ -0,0 +1,575 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE "persistence_matrix" +#include + +#include "gudhi/Fields/Z2_field_operators.h" +#include "gudhi/Fields/Zp_field_operators.h" +#include "gudhi/Fields/Multi_field_small_operators.h" +#ifdef PM_GMP_FOUND +#include "gudhi/Fields/Multi_field_operators.h" + +using Gudhi::persistence_fields::Multi_field_operators; +#endif +using Gudhi::persistence_fields::Multi_field_operators_with_small_characteristics; +using Gudhi::persistence_fields::Z2_field_operators; +using Gudhi::persistence_fields::Zp_field_operators; + +template +void test_z2_standart_field_operators(Z2& op) { + using T = typename Z2::element_type; + + unsigned int z21 = 7u; + unsigned int z22 = 2u; + T z23 = op.get_value(7u); + T z24 = op.get_value(2u); + + // e * m + a + BOOST_CHECK_EQUAL(op.multiply_and_add(z21, z22, 3u), 1); + BOOST_CHECK_EQUAL(op.multiply_and_add(z23, z24, T(3)), 1); + + //(e + a) * m + BOOST_CHECK_EQUAL(op.add_and_multiply(z21, z22, 3u), 1); + BOOST_CHECK_EQUAL(op.add_and_multiply(z23, z24, T(3)), 1); + + //+ + BOOST_CHECK_EQUAL(op.add(z21, z22), 1); + BOOST_CHECK_EQUAL(op.add(z23, z24), 1); + BOOST_CHECK_EQUAL(op.add(z23, T(3)), 0); + BOOST_CHECK_EQUAL(op.add(z24, T(3)), 1); + BOOST_CHECK_EQUAL(op.add(op.get_value(6u), z23), 1); + BOOST_CHECK_EQUAL(op.add(op.get_value(6u), z24), 0); + z21 = op.add(z21, 3u); + BOOST_CHECK_EQUAL(z21, 0); + z21 = op.add(z21, z22); + BOOST_CHECK_EQUAL(z21, 0); + z23 = op.get_value(z21); + + //- + BOOST_CHECK_EQUAL(op.substract(z21, z22), 0); + BOOST_CHECK_EQUAL(op.substract(z23, z24), 0); + BOOST_CHECK_EQUAL(op.substract(z23, T(3)), 1); + BOOST_CHECK_EQUAL(op.substract(z24, T(3)), 1); + BOOST_CHECK_EQUAL(op.substract(op.get_value(6u), z23), 0); + BOOST_CHECK_EQUAL(op.substract(op.get_value(6u), z24), 0); + z21 = op.substract(z21, 3u); + BOOST_CHECK_EQUAL(z21, 1); + z21 = op.substract(z21, z22); + BOOST_CHECK_EQUAL(z21, 1); + z23 = op.get_value(z21); + + //* + BOOST_CHECK_EQUAL(op.multiply(z21, z22), 0); + BOOST_CHECK_EQUAL(op.multiply(z23, z24), 0); + BOOST_CHECK_EQUAL(op.multiply(z23, T(3)), 1); + BOOST_CHECK_EQUAL(op.multiply(z24, T(3)), 0); + BOOST_CHECK_EQUAL(op.multiply(op.get_value(6u), z23), 0); + BOOST_CHECK_EQUAL(op.multiply(op.get_value(6u), z24), 0); + z21 = op.multiply(z21, 3u); + BOOST_CHECK_EQUAL(z21, 1); + z21 = op.multiply(z21, z22); + BOOST_CHECK_EQUAL(z21, 0); + z23 = op.get_value(z21); + + //== + BOOST_CHECK(op.are_equal(z21, 0u)); + BOOST_CHECK(op.are_equal(z21, z22)); + BOOST_CHECK(op.are_equal(op.get_value(z21), z23)); + BOOST_CHECK(op.are_equal(op.get_value(z21), z24)); +} + +template +void test_z5_standart_field_operators(Z5& op) { + unsigned int z51 = 7; + unsigned int z52 = 3; + auto z53 = op.get_value(7); + auto z54 = op.get_value(3); + + // e * m + a + BOOST_CHECK_EQUAL(op.multiply_and_add(z51, z52, 3), 4); + BOOST_CHECK_EQUAL(op.multiply_and_add(z53, z54, 3), 4); + + //(e + a) * m + BOOST_CHECK_EQUAL(op.add_and_multiply(z51, z52, 3), 0); + BOOST_CHECK_EQUAL(op.add_and_multiply(z53, z54, 3), 0); + + //+ + BOOST_CHECK_EQUAL(op.add(z51, z52), 0); + BOOST_CHECK_EQUAL(op.add(z53, z54), 0); + BOOST_CHECK_EQUAL(op.add(z53, 3), 0); + BOOST_CHECK_EQUAL(op.add(z54, 3), 1); + BOOST_CHECK_EQUAL(op.add(7, z53), 4); + BOOST_CHECK_EQUAL(op.add(7, z54), 0); + z51 = op.add(z51, 3); + BOOST_CHECK_EQUAL(z51, 0); + z51 = op.add(z51, z52); + BOOST_CHECK_EQUAL(z51, 3); + z53 = op.get_value(z51); + + //- + BOOST_CHECK_EQUAL(op.substract(z51, z52), 0); + BOOST_CHECK_EQUAL(op.substract(z53, z54), 0); + BOOST_CHECK_EQUAL(op.substract(z53, 3), 0); + BOOST_CHECK_EQUAL(op.substract(z54, 3), 0); + BOOST_CHECK_EQUAL(op.substract(7, z53), 4); + BOOST_CHECK_EQUAL(op.substract(7, z54), 4); + z51 = op.substract(z51, 3); + BOOST_CHECK_EQUAL(z51, 0); + z51 = op.substract(z51, z52); + BOOST_CHECK_EQUAL(z51, 2); + z53 = op.get_value(z51); + + //* + BOOST_CHECK_EQUAL(op.multiply(z51, z52), 1); + BOOST_CHECK_EQUAL(op.multiply(z53, z54), 1); + BOOST_CHECK_EQUAL(op.multiply(z53, 3), 1); + BOOST_CHECK_EQUAL(op.multiply(z54, 3), 4); + BOOST_CHECK_EQUAL(op.multiply(7, z53), 4); + BOOST_CHECK_EQUAL(op.multiply(7, z54), 1); + z51 = op.multiply(z51, 3); + BOOST_CHECK_EQUAL(z51, 1); + z51 = op.multiply(z51, z52); + BOOST_CHECK_EQUAL(z51, 3); + z53 = op.get_value(z51); + + //== + BOOST_CHECK(op.are_equal(z51, 3)); + BOOST_CHECK(op.are_equal(z51, z52)); + BOOST_CHECK(op.are_equal(z51, z53)); + BOOST_CHECK(op.are_equal(z51, z54)); +} + +template +void test_z2_standart_field_inplace_operators(Z2& op) { + using T = typename Z2::element_type; + + T z23 = op.get_value(7u); + T z24 = op.get_value(2u); + T z25 = 3; + + // e * m + a + op.multiply_and_add_inplace_front(z23, z24, T(3)); + BOOST_CHECK_EQUAL(z23, 1); + z23 = op.get_value(7u); + op.multiply_and_add_inplace_back(z23, z24, z25); + BOOST_CHECK_EQUAL(z25, 1); + z25 = 3; + + //(e + a) * m + op.add_and_multiply_inplace_front(z23, z24, T(3)); + BOOST_CHECK_EQUAL(z23, 1); + z23 = op.get_value(7u); + op.add_and_multiply_inplace_back(z23, z24, z25); + BOOST_CHECK_EQUAL(z25, 1); + z25 = 3; + + //+ + op.add_inplace(z23, z24); + BOOST_CHECK_EQUAL(z23, 1); + z23 = op.get_value(7u); + op.add_inplace(z23, T(3)); + BOOST_CHECK_EQUAL(z23, 0); + z23 = op.get_value(7u); + op.add_inplace(z24, T(3)); + BOOST_CHECK_EQUAL(z24, 1); + z24 = op.get_value(2u); + op.add_inplace(z23, op.get_value(6u)); + BOOST_CHECK_EQUAL(z23, 1); + z23 = op.get_value(7u); + op.add_inplace(z24, op.get_value(6u)); + BOOST_CHECK_EQUAL(z24, 0); + z24 = op.get_value(2u); + z23 = 0; + + //- + op.substract_inplace_front(z23, z24); + BOOST_CHECK_EQUAL(z23, 0); + z23 = 0; + op.substract_inplace_front(z23, T(3)); + BOOST_CHECK_EQUAL(z23, 1); + z23 = 0; + op.substract_inplace_front(z24, T(3)); + BOOST_CHECK_EQUAL(z24, 1); + z24 = op.get_value(2u); + op.substract_inplace_back(op.get_value(6u), z23); + BOOST_CHECK_EQUAL(z23, 0); + z23 = 0; + op.substract_inplace_back(op.get_value(6u), z24); + BOOST_CHECK_EQUAL(z24, 0); + z24 = op.get_value(2u); + z23 = 1; + + //* + op.multiply_inplace(z23, z24); + BOOST_CHECK_EQUAL(z23, 0); + z23 = 1; + op.multiply_inplace(z23, T(3)); + BOOST_CHECK_EQUAL(z23, 1); + z23 = 1; + op.multiply_inplace(z24, T(3)); + BOOST_CHECK_EQUAL(z24, 0); + z24 = op.get_value(2u); + op.multiply_inplace(z23, op.get_value(6u)); + BOOST_CHECK_EQUAL(z23, 0); + op.multiply_inplace(z24, op.get_value(6u)); + BOOST_CHECK_EQUAL(z24, 0); +} + +template +void test_z5_standart_field_inplace_operators(Z5& op) { + using T = typename Z5::element_type; + + T z53 = op.get_value(7); + T z54 = op.get_value(3); + T z55 = 3; + + // e * m + a + op.multiply_and_add_inplace_front(z53, z54, 3); + BOOST_CHECK_EQUAL(z53, 4); + z53 = op.get_value(7); + op.multiply_and_add_inplace_back(z53, z54, z55); + BOOST_CHECK_EQUAL(z55, 4); + z55 = 3; + + //(e + a) * m + op.add_and_multiply_inplace_front(z53, z54, 3); + BOOST_CHECK_EQUAL(z53, 0); + z53 = op.get_value(7); + op.add_and_multiply_inplace_back(z53, z54, z55); + BOOST_CHECK_EQUAL(z55, 0); + z55 = 3; + + //+ + op.add_inplace(z53, z54); + BOOST_CHECK_EQUAL(z53, 0); + z53 = op.get_value(7); + op.add_inplace(z53, 3); + BOOST_CHECK_EQUAL(z53, 0); + z53 = op.get_value(7); + op.add_inplace(z54, 3); + BOOST_CHECK_EQUAL(z54, 1); + z54 = op.get_value(3); + op.add_inplace(z53, 7); + BOOST_CHECK_EQUAL(z53, 4); + z53 = op.get_value(7); + op.add_inplace(z54, 7); + BOOST_CHECK_EQUAL(z54, 0); + z54 = op.get_value(3); + z53 = 3; + + //- + op.substract_inplace_front(z53, z54); + BOOST_CHECK_EQUAL(z53, 0); + z53 = 3; + op.substract_inplace_front(z53, 3); + BOOST_CHECK_EQUAL(z53, 0); + z53 = 3; + op.substract_inplace_front(z54, 3); + BOOST_CHECK_EQUAL(z54, 0); + z54 = op.get_value(3); + op.substract_inplace_back(7, z53); + BOOST_CHECK_EQUAL(z53, 4); + z53 = 3; + op.substract_inplace_back(7, z54); + BOOST_CHECK_EQUAL(z54, 4); + z54 = op.get_value(3); + z53 = 2; + + //* + op.multiply_inplace(z53, z54); + BOOST_CHECK_EQUAL(z53, 1); + z53 = 2; + op.multiply_inplace(z53, 3); + BOOST_CHECK_EQUAL(z53, 1); + z53 = 2; + op.multiply_inplace(z54, 3); + BOOST_CHECK_EQUAL(z54, 4); + z54 = op.get_value(3); + op.multiply_inplace(z53, 7); + BOOST_CHECK_EQUAL(z53, 4); + z53 = 2; + op.multiply_inplace(z54, 7); + BOOST_CHECK_EQUAL(z54, 1); +} + +template +void test_z2_standart_field_properties(Z2& op) { + unsigned int z21 = 7; + unsigned int z22 = 2; + + BOOST_CHECK_EQUAL(op.get_inverse(z21), 1); + BOOST_CHECK_EQUAL(op.get_inverse(z22), 0); + BOOST_CHECK(op.get_partial_inverse(z21, 35) == std::make_pair(typename Z2::element_type(1), 35u)); + BOOST_CHECK(op.get_partial_inverse(z22, 35) == std::make_pair(typename Z2::element_type(0), 35u)); + + BOOST_CHECK_EQUAL(op.get_additive_identity(), 0); + BOOST_CHECK_EQUAL(op.get_multiplicative_identity(), 1); + BOOST_CHECK_EQUAL(op.get_partial_multiplicative_identity(35), 1); + + BOOST_CHECK_EQUAL(op.get_characteristic(), 2); + + BOOST_CHECK_EQUAL(op.get_value(z21), 1); + BOOST_CHECK_EQUAL(op.get_value(z22), 0); +} + +template +void test_z5_standart_field_properties(Z5& op) { + unsigned int z51 = 7; + unsigned int z52 = 3; + + BOOST_CHECK_EQUAL(op.get_inverse(z51), 3); + BOOST_CHECK_EQUAL(op.get_inverse(z52), 2); + BOOST_CHECK(op.get_partial_inverse(z51, 35) == std::make_pair(3u, 35u)); + BOOST_CHECK(op.get_partial_inverse(z52, 35) == std::make_pair(2u, 35u)); + + BOOST_CHECK_EQUAL(op.get_additive_identity(), 0); + + BOOST_CHECK_EQUAL(op.get_multiplicative_identity(), 1); + BOOST_CHECK_EQUAL(op.get_partial_multiplicative_identity(35), 1); + + BOOST_CHECK_EQUAL(op.get_characteristic(), 5); + + BOOST_CHECK_EQUAL(op.get_value(z51), 2); + BOOST_CHECK_EQUAL(op.get_value(z52), 3); +} + +template +void test_z7_standart_field_properties(Z7& op) { + unsigned int z71 = 8; + unsigned int z72 = 3; + + BOOST_CHECK_EQUAL(op.get_inverse(z71), 1); + BOOST_CHECK_EQUAL(op.get_inverse(z72), 5); + BOOST_CHECK(op.get_partial_inverse(z71, 35) == std::make_pair(1u, 35u)); + BOOST_CHECK(op.get_partial_inverse(z72, 35) == std::make_pair(5u, 35u)); + + BOOST_CHECK_EQUAL(op.get_additive_identity(), 0); + + BOOST_CHECK_EQUAL(op.get_multiplicative_identity(), 1); + BOOST_CHECK_EQUAL(op.get_partial_multiplicative_identity(35), 1); + + BOOST_CHECK_EQUAL(op.get_characteristic(), 7); + + BOOST_CHECK_EQUAL(op.get_value(z71), 1); + BOOST_CHECK_EQUAL(op.get_value(z72), 3); +} + +BOOST_AUTO_TEST_CASE(Field_operators_operation) { + Z2_field_operators z2op; + test_z2_standart_field_operators(z2op); + test_z2_standart_field_inplace_operators(z2op); + + Zp_field_operators zpop; + zpop.set_characteristic(2); + test_z2_standart_field_operators(zpop); + test_z2_standart_field_inplace_operators(zpop); + zpop.set_characteristic(5); + test_z5_standart_field_operators(zpop); + test_z5_standart_field_inplace_operators(zpop); +} + +BOOST_AUTO_TEST_CASE(Field_operators_properties) { + Z2_field_operators z2op; + test_z2_standart_field_properties(z2op); + + Zp_field_operators zpop; + zpop.set_characteristic(2); + test_z2_standart_field_properties(zpop); + zpop.set_characteristic(5); + test_z5_standart_field_properties(zpop); + zpop.set_characteristic(7); + test_z7_standart_field_properties(zpop); +} + +template +void test_multi_field_operators(MF& op) { + using T = typename MF::element_type; + + T m1(5005); + T m2(5007); + + // e * m + a + BOOST_CHECK_EQUAL(op.multiply_and_add(m1, m2, 3), 3); + + //(e + a) * m + BOOST_CHECK_EQUAL(op.add_and_multiply(m1, m2, 3), 6); + + //+ + BOOST_CHECK_EQUAL(op.add(m1, m2), T(2)); + BOOST_CHECK_EQUAL(op.add(m1, 3), T(3)); + BOOST_CHECK_EQUAL(op.add(m2, 3), T(5)); + BOOST_CHECK_EQUAL(op.add(6, m1), T(6)); + BOOST_CHECK_EQUAL(op.add(6, m2), T(8)); + m1 = op.add(m1, 3); + BOOST_CHECK_EQUAL(m1, T(3)); + m1 = op.add(m1, m2); + BOOST_CHECK_EQUAL(m1, T(5)); + + //- + BOOST_CHECK_EQUAL(op.substract(m1, m2), T(3)); + BOOST_CHECK_EQUAL(op.substract(m1, 3), T(2)); + BOOST_CHECK_EQUAL(op.substract(m2, 3), T(5004)); + BOOST_CHECK_EQUAL(op.substract(6, m1), T(1)); + BOOST_CHECK_EQUAL(op.substract(6, m2), T(4)); + m2 = op.substract(m2, 3); + BOOST_CHECK_EQUAL(m2, T(5004)); + m2 = op.substract(m2, m1); + BOOST_CHECK_EQUAL(m2, T(4999)); + + //* + BOOST_CHECK_EQUAL(op.multiply(m1, m2), T(4975)); + BOOST_CHECK_EQUAL(op.multiply(m1, 3), T(15)); + BOOST_CHECK_EQUAL(op.multiply(m2, 3), T(4987)); + BOOST_CHECK_EQUAL(op.multiply(6, m1), T(30)); + BOOST_CHECK_EQUAL(op.multiply(6, m2), T(4969)); + m1 = op.multiply(m1, 3); + BOOST_CHECK_EQUAL(m1, T(15)); + m1 = op.multiply(m1, m2); + BOOST_CHECK_EQUAL(m1, T(4915)); + + //== + BOOST_CHECK(!op.are_equal(m1, m2)); + m2 = op.substract(m2, 84); + BOOST_CHECK(op.are_equal(m1, T(4915))); + BOOST_CHECK(op.are_equal(m1, m2)); +} + +template +void test_multi_field_inplace_operators(MF& op) { + using T = typename MF::element_type; + + T m1(5005); + T m2(5007); + T m3(3); + + // e * m + a + op.multiply_and_add_inplace_front(m1, m2, 3); + BOOST_CHECK_EQUAL(m1, 3); + m1 = 5005; + op.multiply_and_add_inplace_back(m1, m2, m3); + BOOST_CHECK_EQUAL(m3, 3); + m3 = 3; + + //(e + a) * m + op.add_and_multiply_inplace_front(m1, m2, 3); + BOOST_CHECK_EQUAL(m1, 6); + m1 = 5005; + op.add_and_multiply_inplace_back(m1, m2, m3); + BOOST_CHECK_EQUAL(m3, 6); + m3 = 3; + + //+ + op.add_inplace(m1, m2); + BOOST_CHECK_EQUAL(m1, T(2)); + m1 = 5005; + op.add_inplace(m1, 3); + BOOST_CHECK_EQUAL(m1, T(3)); + m1 = 5005; + op.add_inplace(m2, 3); + BOOST_CHECK_EQUAL(m2, T(5)); + m2 = 5007; + op.add_inplace(m1, 6); + BOOST_CHECK_EQUAL(m1, T(6)); + m1 = 5005; + op.add_inplace(m2, 6); + BOOST_CHECK_EQUAL(m2, T(8)); + m2 = 5007; + m1 = 5; + + //- + op.substract_inplace_front(m1, m2); + BOOST_CHECK_EQUAL(m1, T(3)); + m1 = 5; + op.substract_inplace_front(m1, 3); + BOOST_CHECK_EQUAL(m1, T(2)); + m1 = 5; + op.substract_inplace_front(m2, 3); + BOOST_CHECK_EQUAL(m2, T(5004)); + m2 = 5007; + op.substract_inplace_back(6, m1); + BOOST_CHECK_EQUAL(m1, T(1)); + m1 = 5; + op.substract_inplace_back(6, m2); + BOOST_CHECK_EQUAL(m2, T(4)); + m2 = 4999; + + //* + op.multiply_inplace(m1, m2); + BOOST_CHECK_EQUAL(m1, T(4975)); + m1 = 5; + op.multiply_inplace(m1, 3); + BOOST_CHECK_EQUAL(m1, T(15)); + m1 = 5; + op.multiply_inplace(m2, 3); + BOOST_CHECK_EQUAL(m2, T(4987)); + m2 = 4999; + op.multiply_inplace(m1, 6); + BOOST_CHECK_EQUAL(m1, T(30)); + m1 = 5; + op.multiply_inplace(m2, 6); + BOOST_CHECK_EQUAL(m2, T(4969)); +} + +template +void test_multi_field_properties(MF& op) { + using T = typename MF::element_type; + + T m1(1); + T m2(7); + + BOOST_CHECK_EQUAL(op.get_inverse(m1), T(1)); + BOOST_CHECK_EQUAL(op.get_inverse(m2), T(2758)); + BOOST_CHECK(op.get_partial_inverse(m1, 35) == std::make_pair(T(1716), T(35))); + BOOST_CHECK(op.get_partial_inverse(m2, 35) == std::make_pair(T(3003), T(5))); + + BOOST_CHECK_EQUAL(op.get_additive_identity(), T(0)); + BOOST_CHECK_EQUAL(op.get_multiplicative_identity(), T(1)); + BOOST_CHECK_EQUAL(op.get_partial_multiplicative_identity(7), T(715)); + + BOOST_CHECK_EQUAL(op.get_characteristic(), 5005); + + BOOST_CHECK_EQUAL(op.get_value(m1), 1); + BOOST_CHECK_EQUAL(op.get_value(m2), 7); +} + +BOOST_AUTO_TEST_CASE(Multi_Field_operators_operation) { +#ifdef PM_GMP_FOUND + Multi_field_operators mfop; + mfop.set_characteristic(5, 13); + test_multi_field_operators(mfop); + test_multi_field_inplace_operators(mfop); +#endif + Multi_field_operators_with_small_characteristics smfop; + smfop.set_characteristic(5, 13); + test_multi_field_operators(smfop); + test_multi_field_inplace_operators(smfop); +} + +BOOST_AUTO_TEST_CASE(Multi_Field_operators_properties) { + Multi_field_operators_with_small_characteristics smfop; + smfop.set_characteristic(5, 13); + test_multi_field_properties(smfop); +#ifdef PM_GMP_FOUND + Multi_field_operators mfop; + mfop.set_characteristic(5, 13); + test_multi_field_properties(mfop); + + mfop.set_characteristic(3, 30); + smfop.set_characteristic(3, 30); + + BOOST_CHECK_EQUAL(mfop.get_characteristic(), smfop.get_characteristic()); // == 3234846615 + BOOST_CHECK_EQUAL(mfop.get_partial_inverse(2, 35).first, smfop.get_partial_inverse(2, 35).first); // == 2033332158 + BOOST_CHECK_EQUAL(mfop.get_partial_inverse(2, 35).second, smfop.get_partial_inverse(2, 35).second); // == 2033332158 + BOOST_CHECK_EQUAL(mfop.get_partial_multiplicative_identity(35), smfop.get_partial_multiplicative_identity(35)); +#endif +} + diff --git a/src/Persistence_matrix/test/Persistence_matrix_field_tests.cpp b/src/Persistence_matrix/test/Persistence_matrix_field_tests.cpp new file mode 100644 index 0000000000..9ee4008bfe --- /dev/null +++ b/src/Persistence_matrix/test/Persistence_matrix_field_tests.cpp @@ -0,0 +1,532 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2022 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE "persistence_matrix" +#include + +#include "gudhi/Fields/Z2_field.h" +#include "gudhi/Fields/Zp_field.h" +#include "gudhi/Fields/Zp_field_shared.h" +#include "gudhi/Fields/Multi_field_small.h" +#include "gudhi/Fields/Multi_field_small_shared.h" +#ifdef PM_GMP_FOUND +#include "gudhi/Fields/Multi_field.h" +#include "gudhi/Fields/Multi_field_shared.h" + +using Gudhi::persistence_fields::Multi_field_element; +using Gudhi::persistence_fields::Shared_multi_field_element; +#endif +using Gudhi::persistence_fields::Multi_field_element_with_small_characteristics; +using Gudhi::persistence_fields::Shared_multi_field_element_with_small_characteristics; +using Gudhi::persistence_fields::Shared_Zp_field_element; +using Gudhi::persistence_fields::Z2_field_element; +using Gudhi::persistence_fields::Zp_field_element; + +template +void test_z2_standart_field_constructors() { + // default constructor + Z2 z2_d; + BOOST_CHECK_EQUAL(z2_d, 0); + + // value constructor + Z2 z2_v(5); + BOOST_CHECK_EQUAL(z2_v, 1); + z2_d = 5; + BOOST_CHECK_EQUAL(z2_d, 1); + + // copy constructor + Z2 z2_c1(z2_d); + BOOST_CHECK_EQUAL(z2_c1, 1); + Z2 z2_c2 = z2_c1; + BOOST_CHECK_EQUAL(z2_c2, 1); + + // move constructor + Z2 z2_m1(5); + Z2 z2_m2(std::move(z2_m1)); + BOOST_CHECK_EQUAL(z2_m2, 1); + BOOST_CHECK_EQUAL(z2_m1, 0); + + // swap + Z2 z2_s1(5); + Z2 z2_s2(8); + swap(z2_s1, z2_s2); + BOOST_CHECK_EQUAL(z2_s2, 1); + BOOST_CHECK_EQUAL(z2_s1, 0); +} + +template +void test_z5_standart_field_constructors() { + // default constructor + Z5 z5_d; + BOOST_CHECK_EQUAL(z5_d, 0); + + // value constructor + Z5 z5_v(7); + BOOST_CHECK_EQUAL(z5_v, 2); + z5_d = 7; + BOOST_CHECK_EQUAL(z5_d, 2); + + // copy constructor + Z5 z5_c1(7); + Z5 z5_c2 = z5_c1; + BOOST_CHECK_EQUAL(z5_c2, 2); + Z5 z5_c3(z5_c2); + BOOST_CHECK_EQUAL(z5_c3, 2); + + // move constructor + Z5 z5_m1(7); + Z5 z5_m2(std::move(z5_m1)); + BOOST_CHECK_EQUAL(z5_m2, 2); + BOOST_CHECK_EQUAL(z5_m1, 0); + + // swap + Z5 z5_s1(4); + Z5 z5_s2(8); + swap(z5_s1, z5_s2); + BOOST_CHECK_EQUAL(z5_s2, 4); + BOOST_CHECK_EQUAL(z5_s1, 3); +} + +template +void test_z13_standart_field_constructors() { + // default constructor + Z13 z13_d; + BOOST_CHECK_EQUAL(z13_d, 0); + + // value constructor + Z13 z13_v(5); + BOOST_CHECK_EQUAL(z13_v, 5); + z13_d = 5; + BOOST_CHECK_EQUAL(z13_d, 5); + + // copy constructor + Z13 z13_c1(5); + Z13 z13_c2 = z13_c1; + BOOST_CHECK_EQUAL(z13_c2, 5); + Z13 z13_c3(z13_c2); + BOOST_CHECK_EQUAL(z13_c3, 5); + + // move constructor + Z13 z13_m1(5); + Z13 z13_m2(std::move(z13_m1)); + BOOST_CHECK_EQUAL(z13_m2, 5); + BOOST_CHECK_EQUAL(z13_m1, 0); + + // swap + Z13 z13_s1(4); + Z13 z13_s2(22); + swap(z13_s1, z13_s2); + BOOST_CHECK_EQUAL(z13_s2, 4); + BOOST_CHECK_EQUAL(z13_s1, 9); +} + +template +void test_z2_standart_field_operators() { + Z2 z21(7); + Z2 z22(2); + + //+ + BOOST_CHECK_EQUAL(z21 + z22, 1); + BOOST_CHECK_EQUAL(z21 + 3, 0); + BOOST_CHECK_EQUAL(z22 + 3, 1); + BOOST_CHECK_EQUAL(6 + z21, 1); + BOOST_CHECK_EQUAL(6 + z22, 0); + z21 += 3; + BOOST_CHECK_EQUAL(z21, 0); + z21 += z22; + BOOST_CHECK_EQUAL(z21, 0); + + //- + BOOST_CHECK_EQUAL(z21 - z22, 0); + BOOST_CHECK_EQUAL(z21 - 3, 1); + BOOST_CHECK_EQUAL(z22 - 3, 1); + BOOST_CHECK_EQUAL(6 - z21, 0); + BOOST_CHECK_EQUAL(6 - z22, 0); + z21 -= 3; + BOOST_CHECK_EQUAL(z21, 1); + z21 -= z22; + BOOST_CHECK_EQUAL(z21, 1); + + //* + BOOST_CHECK_EQUAL(z21 * z22, 0); + BOOST_CHECK_EQUAL(z21 * 3, 1); + BOOST_CHECK_EQUAL(z22 * 3, 0); + BOOST_CHECK_EQUAL(6 * z21, 0); + BOOST_CHECK_EQUAL(6 * z22, 0); + z21 *= 3; + BOOST_CHECK_EQUAL(z21, 1); + z21 *= z22; + BOOST_CHECK_EQUAL(z21, 0); + + //== + BOOST_CHECK(z21 == z22); + BOOST_CHECK(z21 == 0); + BOOST_CHECK(0 == z21); + BOOST_CHECK(z22 == 0); + BOOST_CHECK(0 == z22); + BOOST_CHECK(z21 != 1); + BOOST_CHECK(3 != z21); + BOOST_CHECK(z22 != 1); + BOOST_CHECK(3 != z22); +} + +template +void test_z5_standart_field_operators() { + Z5 z51(7); + Z5 z52(3); + + //+ + BOOST_CHECK_EQUAL(z51 + z52, 0); + BOOST_CHECK_EQUAL(z51 + 3, 0); + BOOST_CHECK_EQUAL(z52 + 3, 1); + BOOST_CHECK_EQUAL(7 + z51, 4); + BOOST_CHECK_EQUAL(7 + z52, 0); + z51 += 3; + BOOST_CHECK_EQUAL(z51, 0); + z51 += z52; + BOOST_CHECK_EQUAL(z51, 3); + + //- + BOOST_CHECK_EQUAL(z51 - z52, 0); + BOOST_CHECK_EQUAL(z51 - 3, 0); + BOOST_CHECK_EQUAL(z52 - 3, 0); + BOOST_CHECK_EQUAL(7 - z51, 4); + BOOST_CHECK_EQUAL(7 - z52, 4); + z51 -= 3; + BOOST_CHECK_EQUAL(z51, 0); + z51 -= z52; + BOOST_CHECK_EQUAL(z51, 2); + + //* + BOOST_CHECK_EQUAL(z51 * z52, 1); + BOOST_CHECK_EQUAL(z51 * 3, 1); + BOOST_CHECK_EQUAL(z52 * 3, 4); + BOOST_CHECK_EQUAL(7 * z51, 4); + BOOST_CHECK_EQUAL(7 * z52, 1); + z51 *= 3; + BOOST_CHECK_EQUAL(z51, 1); + z51 *= z52; + BOOST_CHECK_EQUAL(z51, 3); + + //== + BOOST_CHECK(z51 == z52); + BOOST_CHECK(z51 == 3); + BOOST_CHECK(3 == z51); + BOOST_CHECK(z52 == 3); + BOOST_CHECK(3 == z52); + BOOST_CHECK(z51 != 1); + BOOST_CHECK(7 != z51); + BOOST_CHECK(z52 != 7); + BOOST_CHECK(1 != z52); +} + +template +void test_z2_standart_field_properties() { + Z2 z21(7); + Z2 z22(2); + + BOOST_CHECK_EQUAL(z21.get_inverse(), 1); + BOOST_CHECK_EQUAL(z22.get_inverse(), 0); + BOOST_CHECK(z21.get_partial_inverse(35) == std::make_pair(Z2(1), 35u)); + BOOST_CHECK(z22.get_partial_inverse(35) == std::make_pair(Z2(0), 35u)); + + BOOST_CHECK_EQUAL(z21.get_additive_identity(), 0); + BOOST_CHECK_EQUAL(z22.get_additive_identity(), 0); + + BOOST_CHECK_EQUAL(z21.get_multiplicative_identity(), 1); + BOOST_CHECK_EQUAL(z22.get_multiplicative_identity(), 1); + BOOST_CHECK_EQUAL(z21.get_partial_multiplicative_identity(7), 1); + BOOST_CHECK_EQUAL(z22.get_partial_multiplicative_identity(7), 1); + + BOOST_CHECK_EQUAL(z21.get_characteristic(), 2); + BOOST_CHECK_EQUAL(z22.get_characteristic(), 2); + + BOOST_CHECK_EQUAL(z21.get_value(), 1); + BOOST_CHECK_EQUAL(z22.get_value(), 0); +} + +template +void test_z5_standart_field_properties() { + Z5 z51(7); + Z5 z52(3); + + BOOST_CHECK_EQUAL(z51.get_inverse(), 3); + BOOST_CHECK_EQUAL(z52.get_inverse(), 2); + BOOST_CHECK(z51.get_partial_inverse(35) == std::make_pair(Z5(3), 35u)); + BOOST_CHECK(z52.get_partial_inverse(35) == std::make_pair(Z5(2), 35u)); + + BOOST_CHECK_EQUAL(z51.get_additive_identity(), 0); + BOOST_CHECK_EQUAL(z52.get_additive_identity(), 0); + + BOOST_CHECK_EQUAL(z51.get_multiplicative_identity(), 1); + BOOST_CHECK_EQUAL(z52.get_multiplicative_identity(), 1); + BOOST_CHECK_EQUAL(z51.get_partial_multiplicative_identity(7), 1); + BOOST_CHECK_EQUAL(z52.get_partial_multiplicative_identity(7), 1); + + BOOST_CHECK_EQUAL(z51.get_characteristic(), 5); + BOOST_CHECK_EQUAL(z52.get_characteristic(), 5); + + BOOST_CHECK_EQUAL(z51.get_value(), 2); + BOOST_CHECK_EQUAL(z52.get_value(), 3); +} + +template +void test_z7_standart_field_properties() { + Z7 z71(8); + Z7 z72(3); + + BOOST_CHECK_EQUAL(z71.get_inverse(), 1); + BOOST_CHECK_EQUAL(z72.get_inverse(), 5); + BOOST_CHECK(z71.get_partial_inverse(35) == std::make_pair(Z7(1), 35u)); + BOOST_CHECK(z72.get_partial_inverse(35) == std::make_pair(Z7(5), 35u)); + + BOOST_CHECK_EQUAL(z71.get_additive_identity(), 0); + BOOST_CHECK_EQUAL(z72.get_additive_identity(), 0); + + BOOST_CHECK_EQUAL(z71.get_multiplicative_identity(), 1); + BOOST_CHECK_EQUAL(z72.get_multiplicative_identity(), 1); + BOOST_CHECK_EQUAL(z71.get_partial_multiplicative_identity(7), 1); + BOOST_CHECK_EQUAL(z72.get_partial_multiplicative_identity(7), 1); + + BOOST_CHECK_EQUAL(z71.get_characteristic(), 7); + BOOST_CHECK_EQUAL(z72.get_characteristic(), 7); + + BOOST_CHECK_EQUAL(z71.get_value(), 1); + BOOST_CHECK_EQUAL(z72.get_value(), 3); +} + +BOOST_AUTO_TEST_CASE(Field_constructors) { + test_z2_standart_field_constructors(); + test_z2_standart_field_constructors >(); + test_z5_standart_field_constructors >(); + test_z13_standart_field_constructors >(); +} + +BOOST_AUTO_TEST_CASE(Field_operators) { + test_z2_standart_field_operators(); + test_z2_standart_field_operators >(); + test_z5_standart_field_operators >(); +} + +BOOST_AUTO_TEST_CASE(Field_properties) { + test_z2_standart_field_properties(); + test_z2_standart_field_properties >(); + test_z5_standart_field_properties >(); + test_z7_standart_field_properties >(); +} + +BOOST_AUTO_TEST_CASE(Shared_Field_constructors) { + Shared_Zp_field_element<>::initialize(2); + test_z2_standart_field_constructors >(); + + Shared_Zp_field_element<>::initialize(5); + test_z5_standart_field_constructors >(); + + Shared_Zp_field_element<>::initialize(13); + test_z13_standart_field_constructors >(); +} + +BOOST_AUTO_TEST_CASE(Shared_Field_operators) { + Shared_Zp_field_element<>::initialize(2); + test_z2_standart_field_operators >(); + + Shared_Zp_field_element<>::initialize(5); + test_z5_standart_field_operators >(); +} + +BOOST_AUTO_TEST_CASE(Shared_Field_properties) { + Shared_Zp_field_element<>::initialize(2); + test_z2_standart_field_properties >(); + + Shared_Zp_field_element<>::initialize(5); + test_z5_standart_field_properties >(); + + Shared_Zp_field_element<>::initialize(7); + test_z7_standart_field_properties >(); +} + +template +void test_multi_field_constructors() { + using T = typename MF::element_type; + + // default constructor + MF m_d; + BOOST_CHECK_EQUAL(m_d, T(0)); + + // value constructor + MF m_v(5006); + BOOST_CHECK_EQUAL(m_v, T(1)); + + // copy constructor + MF m_c1(5006); + MF m_c2 = m_c1; + BOOST_CHECK_EQUAL(m_c2, T(1)); + MF m_c3(m_c2); + BOOST_CHECK_EQUAL(m_c3, T(1)); + + // move constructor + MF m_m1(5006); + MF m_m2(std::move(m_m1)); + BOOST_CHECK_EQUAL(m_m2, T(1)); + // BOOST_CHECK_EQUAL(m_m1, T(0)); //does not work on windows + + // swap + MF m_s1(5006); + MF m_s2(5005); + swap(m_s1, m_s2); + BOOST_CHECK_EQUAL(m_s2, T(1)); + BOOST_CHECK_EQUAL(m_s1, T(0)); +} + +template +void test_multi_field_operators() { + using T = typename MF::element_type; + + MF m1(5005); + MF m2(5007); + + //+ + BOOST_CHECK_EQUAL(m1 + m2, T(2)); + BOOST_CHECK_EQUAL(m1 + T(3), T(3)); + BOOST_CHECK_EQUAL(m2 + T(3), T(5)); + BOOST_CHECK_EQUAL(T(6) + m1, T(6)); + BOOST_CHECK_EQUAL(T(6) + m2, T(8)); + m1 += T(3); + BOOST_CHECK_EQUAL(m1, T(3)); + m1 += m2; + BOOST_CHECK_EQUAL(m1, T(5)); + + //- + BOOST_CHECK_EQUAL(m1 - m2, T(3)); + BOOST_CHECK_EQUAL(m1 - T(3), T(2)); + BOOST_CHECK_EQUAL(m2 - T(3), T(5004)); + BOOST_CHECK_EQUAL(T(6) - m1, T(1)); + BOOST_CHECK_EQUAL(T(6) - m2, T(4)); + m2 -= T(3); + BOOST_CHECK_EQUAL(m2, T(5004)); + m2 -= m1; + BOOST_CHECK_EQUAL(m2, T(4999)); + + //* + BOOST_CHECK_EQUAL(m1 * m2, T(4975)); + BOOST_CHECK_EQUAL(m1 * T(3), T(15)); + BOOST_CHECK_EQUAL(m2 * T(3), T(4987)); + BOOST_CHECK_EQUAL(T(6) * m1, T(30)); + BOOST_CHECK_EQUAL(T(6) * m2, T(4969)); + m1 *= T(3); + BOOST_CHECK_EQUAL(m1, T(15)); + m1 *= m2; + BOOST_CHECK_EQUAL(m1, T(4915)); + + //== + BOOST_CHECK(m1 != m2); + m2 -= T(84); + BOOST_CHECK(m1 == m2); + BOOST_CHECK(m1 == T(4915)); + BOOST_CHECK(T(4915) == m1); + BOOST_CHECK(m2 == T(4915)); + BOOST_CHECK(T(4915) == m2); + BOOST_CHECK(m1 != T(1)); + BOOST_CHECK(T(3) != m1); + BOOST_CHECK(m2 != T(1)); + BOOST_CHECK(T(3) != m2); +} + +template +void test_multi_field_properties() { + using T = typename MF::element_type; + + MF m1(1); + MF m2(7); + + BOOST_CHECK_EQUAL(m1.get_inverse(), T(1)); + BOOST_CHECK_EQUAL(m2.get_inverse(), T(2758)); + BOOST_CHECK(m1.get_partial_inverse(35) == std::make_pair(MF(1716), T(35))); + BOOST_CHECK(m2.get_partial_inverse(35) == std::make_pair(MF(3003), T(5))); + + BOOST_CHECK_EQUAL(m1.get_additive_identity(), T(0)); + BOOST_CHECK_EQUAL(m2.get_additive_identity(), T(0)); + BOOST_CHECK_EQUAL(m1.get_multiplicative_identity(), T(1)); + BOOST_CHECK_EQUAL(m2.get_multiplicative_identity(), T(1)); + BOOST_CHECK_EQUAL(m1.get_partial_multiplicative_identity(7), T(715)); + BOOST_CHECK_EQUAL(m2.get_partial_multiplicative_identity(7), T(715)); + + BOOST_CHECK_EQUAL(m1.get_characteristic(), 5005); + BOOST_CHECK_EQUAL(m2.get_characteristic(), 5005); + + BOOST_CHECK_EQUAL(m1.get_value(), 1); + BOOST_CHECK_EQUAL(m2.get_value(), 7); +} + +BOOST_AUTO_TEST_CASE(Multi_Field_constructors) { +#ifdef PM_GMP_FOUND + test_multi_field_constructors >(); +#endif + test_multi_field_constructors >(); +} + +BOOST_AUTO_TEST_CASE(Multi_Field_operators) { +#ifdef PM_GMP_FOUND + test_multi_field_operators >(); +#endif + test_multi_field_operators >(); +} + +BOOST_AUTO_TEST_CASE(Multi_Field_properties) { + test_multi_field_properties >(); +#ifdef PM_GMP_FOUND + test_multi_field_properties >(); + + Multi_field_element<3, 30> mb1(2); + Multi_field_element_with_small_characteristics<3, 30> mb2(2); + + BOOST_CHECK_EQUAL(mb1.get_characteristic(), mb2.get_characteristic()); // == 3234846615 + BOOST_CHECK_EQUAL(mb1.get_partial_inverse(35).first.get_value(), + mb2.get_partial_inverse(35).first.get_value()); // == 2033332158 +#endif +} + +BOOST_AUTO_TEST_CASE(Shared_Multi_Field_constructors) { +#ifdef PM_GMP_FOUND + Shared_multi_field_element::initialize(5, 13); + test_multi_field_constructors(); +#endif + Shared_multi_field_element_with_small_characteristics<>::initialize(5, 13); + test_multi_field_constructors >(); +} + +BOOST_AUTO_TEST_CASE(Shared_Multi_Field_operators) { +#ifdef PM_GMP_FOUND + Shared_multi_field_element::initialize(5, 13); + test_multi_field_operators(); +#endif + Shared_multi_field_element_with_small_characteristics<>::initialize(5, 13); + test_multi_field_operators >(); +} + +BOOST_AUTO_TEST_CASE(Shared_Multi_Field_properties) { + Shared_multi_field_element_with_small_characteristics<>::initialize(5, 13); + test_multi_field_properties >(); + +#ifdef PM_GMP_FOUND + Shared_multi_field_element::initialize(5, 13); + test_multi_field_properties(); + + Shared_multi_field_element::initialize(3, 30); + Shared_multi_field_element_with_small_characteristics<>::initialize(3, 30); + Shared_multi_field_element mb1(2); + Shared_multi_field_element_with_small_characteristics<> mb2(2); + + BOOST_CHECK_EQUAL(mb1.get_characteristic(), mb2.get_characteristic()); // == 3234846615 + BOOST_CHECK_EQUAL(mb1.get_partial_inverse(35).first.get_value(), + mb2.get_partial_inverse(35).first.get_value()); // == 2033332158 +#endif +} diff --git a/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_z2_base.cpp b/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_z2_base.cpp new file mode 100644 index 0000000000..8d179d204e --- /dev/null +++ b/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_z2_base.cpp @@ -0,0 +1,49 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2023 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE "persistence_matrix" +#include + +#include "pm_matrix_tests.h" +#include "pm_matrix_tests_boost_type_lists.h" + +using full_matrices = matrices_list; +using row_access_matrices = matrices_list; +using removable_rows_matrices = matrices_list; +using removable_columns_matrices = matrices_list; +using swap_matrices = matrices_list; + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_matrix_z2_constructors, Matrix, full_matrices) { test_constructors(); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_matrix_z2_insertion, Matrix, full_matrices) { test_general_insertion(); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_matrix_z2_access, Matrix, full_matrices) { test_base_access(); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_matrix_z2_zeroing, Matrix, full_matrices) { test_zeroing(); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_matrix_z2_row_access, Matrix, row_access_matrices) { + test_base_z2_row_access(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_matrix_z2_row_removal, Matrix, removable_rows_matrices) { + test_row_removal(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_matrix_z2_column_removal, Matrix, removable_columns_matrices) { + test_column_removal(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_matrix_z2_operation, Matrix, full_matrices) { + test_base_operation(); + test_base_cell_range_operation(); + test_const_operation(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_matrix_z2_swaps, Matrix, swap_matrices) { test_base_swaps(); } diff --git a/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_z2_boundary.cpp b/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_z2_boundary.cpp new file mode 100644 index 0000000000..823b5da237 --- /dev/null +++ b/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_z2_boundary.cpp @@ -0,0 +1,69 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2023 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE "persistence_matrix" +#include + +#include "pm_matrix_tests.h" +#include "pm_matrix_tests_boost_type_lists.h" + +#ifdef PM_TEST_ID_IDX +using full_matrices = matrices_list >; +using row_access_matrices = matrices_list >; +using removable_rows_matrices = matrices_list >; +using removable_columns_matrices = matrices_list >; +using max_dim_matrices = matrices_list >; +using barcode_matrices = matrices_list >; +using swap_matrices = matrices_list >; +#else +using full_matrices = matrices_list >; +using row_access_matrices = matrices_list >; +using removable_rows_matrices = matrices_list >; +using removable_columns_matrices = matrices_list >; +using max_dim_matrices = matrices_list >; +using barcode_matrices = matrices_list >; +using swap_matrices = matrices_list >; +#endif + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_matrix_z2_constructors, Matrix, full_matrices) { test_constructors(); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_matrix_z2_insertion, Matrix, full_matrices) { + test_boundary_insertion(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_matrix_z2_access, Matrix, full_matrices) { test_boundary_access(); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_matrix_z2_zeroing, Matrix, full_matrices) { test_zeroing(); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_matrix_z2_row_access, Matrix, row_access_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns); + test_non_base_row_access(m); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_matrix_z2_row_removal, Matrix, removable_rows_matrices) { + test_row_removal(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_matrix_z2_column_removal, Matrix, removable_columns_matrices) { + test_boundary_maximal_simplex_removal(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_matrix_z2_max_dimension, Matrix, max_dim_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns); + test_maximal_dimension(m); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_matrix_z2_operation, Matrix, full_matrices) { test_base_operation(); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_matrix_z2_barcode, Matrix, barcode_matrices) { test_barcode(); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_matrix_z2_swaps, Matrix, swap_matrices) { test_base_swaps(); } diff --git a/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_z2_chain_barcode.cpp b/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_z2_chain_barcode.cpp new file mode 100644 index 0000000000..70d353cf71 --- /dev/null +++ b/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_z2_chain_barcode.cpp @@ -0,0 +1,90 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2023 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE "persistence_matrix" +#include + +#include "pm_matrix_tests.h" +#include "pm_matrix_tests_boost_type_lists.h" + +#ifdef PM_TEST_ID_IDX +#ifdef PM_TEST_MAX_DIM +using opts = boost::mp11::mp_list; +#else +using opts = boost::mp11::mp_list; +#endif +#else +#ifdef PM_TEST_MAX_DIM +using opts = boost::mp11::mp_list; +#else +using opts = boost::mp11::mp_list; +#endif +#endif + +using full_matrices = matrices_list >; +using row_access_matrices = matrices_list >; +using removable_rows_matrices = matrices_list >; +using removable_columns_matrices = matrices_list >; + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_barcode_constructors, Matrix, full_matrices) { + test_constructors(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_barcode_insertion, Matrix, full_matrices) { + Matrix m1; + + auto orderedBoundaries = build_simple_boundary_matrix(); + orderedBoundaries.pop_back(); + orderedBoundaries.pop_back(); + + Matrix m2(orderedBoundaries); + + test_chain_boundary_insertion(m1, m2); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_barcode_access, Matrix, full_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns); + test_chain_access(m); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_barcode_row_access, Matrix, row_access_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns); + test_non_base_row_access(m); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_barcode_row_removal, Matrix, removable_rows_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns); + test_chain_row_removal(m); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_barcode_column_removal, Matrix, removable_columns_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns); + test_chain_maximal_simplex_removal(m); +} + +#ifdef PM_TEST_MAX_DIM +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_barcode_max_dimension, Matrix, full_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns); + test_maximal_dimension(m); +} +#endif + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_barcode_operation, Matrix, full_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns); + test_chain_operation(m); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_barcode_barcode, Matrix, full_matrices) { test_barcode(); } diff --git a/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_z2_chain_rep.cpp b/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_z2_chain_rep.cpp new file mode 100644 index 0000000000..99701e0731 --- /dev/null +++ b/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_z2_chain_rep.cpp @@ -0,0 +1,95 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2023 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE "persistence_matrix" +#include + +#include "pm_matrix_tests.h" +#include "pm_matrix_tests_boost_type_lists.h" + +#ifdef PM_TEST_ID_IDX +#ifdef PM_TEST_MAX_DIM +using opts = boost::mp11::mp_list; +#else +using opts = boost::mp11::mp_list; +#endif +#else +#ifdef PM_TEST_MAX_DIM +using opts = boost::mp11::mp_list; +#else +using opts = boost::mp11::mp_list; +#endif +#endif + +using full_matrices = matrices_list >; +using row_access_matrices = matrices_list >; +using removable_rows_matrices = matrices_list >; +using removable_columns_matrices = matrices_list >; +using barcode_matrices = matrices_list >; + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_rep_constructors, Matrix, full_matrices) { test_constructors(); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_rep_insertion, Matrix, full_matrices) { + Matrix m1; + + auto orderedBoundaries = build_simple_boundary_matrix(); + orderedBoundaries.pop_back(); + orderedBoundaries.pop_back(); + + Matrix m2(orderedBoundaries); + + test_chain_boundary_insertion(m1, m2); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_rep_access, Matrix, full_matrices) { + auto orderedBoundaries = build_simple_boundary_matrix(); + Matrix m(orderedBoundaries); + test_chain_access(m); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_rep_row_access, Matrix, row_access_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns); + test_non_base_row_access(m); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_rep_row_removal, Matrix, removable_rows_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns); + test_chain_row_removal(m); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_rep_column_removal, Matrix, removable_columns_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns); + test_chain_maximal_simplex_removal(m); +} + +#ifdef PM_TEST_MAX_DIM +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_rep_max_dimension, Matrix, full_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns); + test_maximal_dimension(m); +} +#endif + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_rep_operation, Matrix, full_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns); + test_chain_operation(m); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_rep_barcode, Matrix, barcode_matrices) { test_barcode(); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_rep_representative_cycles, Matrix, full_matrices) { + auto columns = build_longer_boundary_matrix(); + Matrix m(columns); + test_representative_cycles(m); +} diff --git a/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_z2_chain_vine.cpp b/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_z2_chain_vine.cpp new file mode 100644 index 0000000000..5fb13f1aed --- /dev/null +++ b/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_z2_chain_vine.cpp @@ -0,0 +1,216 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2023 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE "persistence_matrix" +#include + +#include "pm_matrix_tests.h" +#include "pm_matrix_tests_boost_type_lists.h" + +#ifdef PM_TEST_ID_IDX +#ifdef PM_TEST_REM_COL +#ifdef PM_TEST_MAX_DIM +using opts = boost::mp11::mp_list; +#else +using opts = boost::mp11::mp_list; +#endif +#else +#ifdef PM_TEST_MAX_DIM +using opts = boost::mp11::mp_list; +#else +using opts = boost::mp11::mp_list; +#endif +#endif +#else +#ifdef PM_TEST_REM_COL +#ifdef PM_TEST_MAX_DIM +using opts = boost::mp11::mp_list; +#else +using opts = boost::mp11::mp_list; +#endif +#else +#ifdef PM_TEST_MAX_DIM +using opts = boost::mp11::mp_list; +#else +using opts = boost::mp11::mp_list; +#endif +#endif +#endif + +#ifdef PM_TEST_BARCODE +using full_matrices = matrices_list >; +using row_access_matrices = matrices_list >; +using removable_rows_matrices = matrices_list >; +using rep_matrices = matrices_list >; + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_vine_constructors, Matrix, full_matrices) { test_constructors(); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_vine_insertion, Matrix, full_matrices) { + Matrix m1; + + auto orderedBoundaries = build_simple_boundary_matrix(); + orderedBoundaries.pop_back(); + orderedBoundaries.pop_back(); + + Matrix m2(orderedBoundaries); + + test_chain_boundary_insertion(m1, m2); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_vine_access, Matrix, full_matrices) { + auto orderedBoundaries = build_simple_boundary_matrix(); + Matrix m(orderedBoundaries); + test_chain_access(m); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_vine_row_access, Matrix, row_access_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns); + test_non_base_row_access(m); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_vine_row_removal, Matrix, removable_rows_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns); + test_chain_row_removal(m); +} + +#ifdef PM_TEST_REM_COL +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_vine_column_removal, Matrix, full_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns); + test_chain_maximal_simplex_removal(m); +} +#endif + +#ifdef PM_TEST_MAX_DIM +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_vine_max_dimension, Matrix, full_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns); + test_maximal_dimension(m); +} +#endif + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_vine_operation, Matrix, full_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns); + test_chain_operation(m); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_vine_barcode, Matrix, full_matrices) { test_barcode(); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_vine, Matrix, full_matrices) { + auto columns = build_longer_boundary_matrix(); + Matrix m(columns); +#ifdef PM_TEST_ID_IDX + test_vine_swap_with_id_index(m); +#else + test_vine_swap_with_position_index(m); +#endif +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_vine_representative_cycles, Matrix, rep_matrices) { + auto columns = build_longer_boundary_matrix(); + Matrix m(columns); + test_representative_cycles(m); +} + +#else + +// only works because of the example used +bool birth_comparator(unsigned int columnIndex1, unsigned int columnIndex2) { + if (columnIndex1 == 0) return false; + if (columnIndex1 == 3) return true; + if (columnIndex1 == 1) return true; + return false; +} + +using full_matrices = matrices_list >; +using row_access_matrices = matrices_list >; +using removable_rows_matrices = matrices_list >; +using rep_matrices = matrices_list >; + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_vine_constructors, Matrix, full_matrices) { + test_chain_constructors(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_vine_insertion, Matrix, full_matrices) { + Matrix m1(birth_comparator, Gudhi::persistence_matrix::_no_G_death_comparator); + + auto orderedBoundaries = build_simple_boundary_matrix(); + orderedBoundaries.pop_back(); + orderedBoundaries.pop_back(); + + Matrix m2(orderedBoundaries, birth_comparator, Gudhi::persistence_matrix::_no_G_death_comparator); + + test_chain_boundary_insertion(m1, m2); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_vine_access, Matrix, full_matrices) { + auto orderedBoundaries = build_simple_boundary_matrix(); + Matrix m(orderedBoundaries, birth_comparator, Gudhi::persistence_matrix::_no_G_death_comparator); + test_chain_access(m); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_vine_row_access, Matrix, row_access_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns, birth_comparator, Gudhi::persistence_matrix::_no_G_death_comparator); + test_non_base_row_access(m); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_vine_row_removal, Matrix, removable_rows_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns, birth_comparator, Gudhi::persistence_matrix::_no_G_death_comparator); + test_chain_row_removal(m); +} + +#ifdef PM_TEST_REM_COL +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_vine_column_removal, Matrix, full_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns, birth_comparator, Gudhi::persistence_matrix::_no_G_death_comparator); + test_chain_maximal_simplex_removal(m); +} +#endif + +#ifdef PM_TEST_MAX_DIM +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_vine_max_dimension, Matrix, full_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns, birth_comparator, Gudhi::persistence_matrix::_no_G_death_comparator); + test_maximal_dimension(m); +} +#endif + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_vine_operation, Matrix, full_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns, birth_comparator, Gudhi::persistence_matrix::_no_G_death_comparator); + test_chain_operation(m); +} + +#ifdef PM_TEST_ID_IDX +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_vine_without_barcode, Matrix, full_matrices) { + auto columns = build_longer_boundary_matrix(); + Matrix m(columns, birth_comparator, Gudhi::persistence_matrix::_no_G_death_comparator); + test_vine_swap_with_id_index(m); +} +#else +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_vine_without_barcode, Matrix, full_matrices) { + auto columns = build_longer_boundary_matrix(); + Matrix m(columns, birth_comparator, Gudhi::persistence_matrix::_no_G_death_comparator); + test_vine_swap_with_position_index(m); +} +#endif + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_z2_vine_representative_cycles, Matrix, rep_matrices) { + auto columns = build_longer_boundary_matrix(); + Matrix m(columns, birth_comparator, Gudhi::persistence_matrix::_no_G_death_comparator); + test_representative_cycles(m); +} + +#endif diff --git a/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_z2_compression.cpp b/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_z2_compression.cpp new file mode 100644 index 0000000000..b5f1c31e64 --- /dev/null +++ b/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_z2_compression.cpp @@ -0,0 +1,45 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2023 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE "persistence_matrix" +#include + +#include "pm_matrix_tests.h" +#include "pm_matrix_tests_boost_type_lists.h" + +using full_matrices = matrices_list; +using row_access_matrices = matrices_list; +using removable_rows_matrices = matrices_list; + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_column_compression_matrix_z2_constructors, Matrix, full_matrices) { + test_constructors(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_column_compression_matrix_z2_insertion, Matrix, full_matrices) { + test_general_insertion(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_column_compression_matrix_z2_access, Matrix, full_matrices) { + test_base_access(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_column_compression_matrix_z2_row_access, Matrix, row_access_matrices) { + test_base_z2_row_access(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_column_compression_matrix_z2_row_removal, Matrix, removable_rows_matrices) { + test_row_removal(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_column_compression_matrix_z2_operation, Matrix, full_matrices) { + test_base_col_comp_operation(); + test_base_col_comp_cell_range_operation(); + test_base_col_comp_const_operation(); +} diff --git a/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_z2_ru_rep.cpp b/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_z2_ru_rep.cpp new file mode 100644 index 0000000000..dcf13e3bc9 --- /dev/null +++ b/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_z2_ru_rep.cpp @@ -0,0 +1,106 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2023 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE "persistence_matrix" +#include + +#include "pm_matrix_tests.h" +#include "pm_matrix_tests_boost_type_lists.h" + +#ifdef PM_TEST_ID_IDX +#ifdef PM_TEST_BARCODE +#ifdef PM_TEST_MAX_DIM +using opts = boost::mp11::mp_list; +#else +using opts = boost::mp11::mp_list; +#endif +#else +#ifdef PM_TEST_MAX_DIM +using opts = boost::mp11::mp_list; +#else +using opts = boost::mp11::mp_list; +#endif +#endif +#else +#ifdef PM_TEST_BARCODE +#ifdef PM_TEST_MAX_DIM +using opts = boost::mp11::mp_list; +#else +using opts = boost::mp11::mp_list; +#endif +#else +#ifdef PM_TEST_MAX_DIM +using opts = boost::mp11::mp_list; +#else +using opts = boost::mp11::mp_list; +#endif +#endif +#endif + +using full_matrices = matrices_list >; +using row_access_matrices = matrices_list >; +using removable_rows_matrices = matrices_list >; +using removable_columns_matrices = matrices_list >; + +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_z2_rep_constructors, Matrix, full_matrices) { test_constructors(); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_z2_rep_insertion, Matrix, full_matrices) { test_boundary_insertion(); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_z2_rep_zeroing, Matrix, full_matrices) { test_zeroing(); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_z2_rep_row_removal, Matrix, removable_rows_matrices) { + test_row_removal(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_z2_rep_column_removal, Matrix, removable_columns_matrices) { + test_ru_maximal_simplex_removal(); +} + +#ifdef PM_TEST_MAX_DIM +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_z2_rep_max_dimension, Matrix, full_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns); + test_maximal_dimension(m); +} +#endif + +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_z2_rep_operation, Matrix, full_matrices) { test_ru_operation(); } + +#ifdef PM_TEST_BARCODE +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_z2_rep_barcode, Matrix, full_matrices) { test_barcode(); } +#endif + +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_z2_rep_representative_cycles, Matrix, full_matrices) { + auto columns = build_longer_boundary_matrix(); + Matrix m(columns); + test_representative_cycles(m); +} + +#ifdef PM_TEST_ID_IDX +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_z2_rep_access, Matrix, full_matrices) { test_boundary_access(); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_z2_rep_row_access, Matrix, row_access_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns); + test_non_base_row_access(m); +} +#else +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_z2_rep_access, Matrix, full_matrices) { + test_boundary_access(); + test_ru_u_access(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_z2_rep_row_access, Matrix, row_access_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns); + test_non_base_row_access(m); + test_ru_u_row_access(); +} +#endif diff --git a/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_z2_ru_vine.cpp b/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_z2_ru_vine.cpp new file mode 100644 index 0000000000..a0924471e0 --- /dev/null +++ b/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_z2_ru_vine.cpp @@ -0,0 +1,119 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2023 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE "persistence_matrix" +#include + +#include "pm_matrix_tests.h" +#include "pm_matrix_tests_boost_type_lists.h" + +#ifdef PM_TEST_ID_IDX +#ifdef PM_TEST_BARCODE +#ifdef PM_TEST_MAX_DIM +using opts = boost::mp11::mp_list; +#else +using opts = boost::mp11::mp_list; +#endif +#else +#ifdef PM_TEST_MAX_DIM +using opts = boost::mp11::mp_list; +#else +using opts = boost::mp11::mp_list; +#endif +#endif +#else +#ifdef PM_TEST_BARCODE +#ifdef PM_TEST_MAX_DIM +using opts = boost::mp11::mp_list; +#else +using opts = boost::mp11::mp_list; +#endif +#else +#ifdef PM_TEST_MAX_DIM +using opts = boost::mp11::mp_list; +#else +using opts = boost::mp11::mp_list; +#endif +#endif +#endif + +using full_matrices = matrices_list >; +using row_access_matrices = matrices_list >; +using removable_rows_matrices = matrices_list >; +using removable_columns_matrices = matrices_list >; +using rep_matrices = matrices_list >; + +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_z2_vine_constructors, Matrix, full_matrices) { test_constructors(); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_z2_vine_insertion, Matrix, full_matrices) { test_boundary_insertion(); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_z2_vine_zeroing, Matrix, full_matrices) { test_zeroing(); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_z2_vine_row_removal, Matrix, removable_rows_matrices) { + test_row_removal(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_z2_vine_column_removal, Matrix, removable_columns_matrices) { + test_ru_maximal_simplex_removal(); +} + +#ifdef PM_TEST_MAX_DIM +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_z2_vine_max_dimension, Matrix, full_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns); + test_maximal_dimension(m); +} +#endif + +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_z2_vine_operation, Matrix, full_matrices) { test_ru_operation(); } + +#ifdef PM_TEST_BARCODE +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_z2_vine_barcode, Matrix, full_matrices) { test_barcode(); } +#endif + +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_z2_vine_representative_cycles, Matrix, rep_matrices) { + auto columns = build_longer_boundary_matrix(); + Matrix m(columns); + test_representative_cycles(m); +} + +#ifdef PM_TEST_ID_IDX +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_z2_vine_access, Matrix, full_matrices) { test_boundary_access(); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_z2_vine_row_access, Matrix, row_access_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns); + test_non_base_row_access(m); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_z2_vine, Matrix, full_matrices) { + auto columns = build_longer_boundary_matrix(); + Matrix m(columns); + test_vine_swap_with_id_index(m); +} +#else +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_z2_vine_access, Matrix, full_matrices) { + test_boundary_access(); + test_ru_u_access(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_z2_vine_row_access, Matrix, row_access_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns); + test_non_base_row_access(m); + test_ru_u_row_access(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_z2_vine, Matrix, full_matrices) { + auto columns = build_longer_boundary_matrix(); + Matrix m(columns); + test_vine_swap_with_position_index(m); +} +#endif diff --git a/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_zp_base.cpp b/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_zp_base.cpp new file mode 100644 index 0000000000..2a5a996d37 --- /dev/null +++ b/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_zp_base.cpp @@ -0,0 +1,49 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2023 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE "persistence_matrix" +#include + +#include "pm_matrix_tests.h" +#include "pm_matrix_tests_boost_type_lists.h" + +using full_matrices = matrices_list; +using row_access_matrices = matrices_list; +using removable_rows_matrices = matrices_list; +using removable_columns_matrices = matrices_list; +using swap_matrices = matrices_list; + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_matrix_zp_constructors, Matrix, full_matrices) { test_constructors(); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_matrix_zp_insertion, Matrix, full_matrices) { test_general_insertion(); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_matrix_zp_access, Matrix, full_matrices) { test_base_access(); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_matrix_zp_zeroing, Matrix, full_matrices) { test_zeroing(); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_matrix_zp_row_access, Matrix, row_access_matrices) { + test_base_z5_row_access(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_matrix_zp_row_removal, Matrix, removable_rows_matrices) { + test_row_removal(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_matrix_zp_column_removal, Matrix, removable_columns_matrices) { + test_column_removal(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_matrix_zp_operation, Matrix, full_matrices) { + test_base_operation(); + test_base_cell_range_operation(); + test_const_operation(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_matrix_zp_swaps, Matrix, swap_matrices) { test_base_swaps(); } diff --git a/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_zp_boundary.cpp b/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_zp_boundary.cpp new file mode 100644 index 0000000000..5111b76c6f --- /dev/null +++ b/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_zp_boundary.cpp @@ -0,0 +1,69 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2023 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE "persistence_matrix" +#include + +#include "pm_matrix_tests.h" +#include "pm_matrix_tests_boost_type_lists.h" + +#ifdef PM_TEST_ID_IDX +using full_matrices = matrices_list >; +using row_access_matrices = matrices_list >; +using removable_rows_matrices = matrices_list >; +using removable_columns_matrices = matrices_list >; +using max_dim_matrices = matrices_list >; +using barcode_matrices = matrices_list >; +using swap_matrices = matrices_list >; +#else +using full_matrices = matrices_list >; +using row_access_matrices = matrices_list >; +using removable_rows_matrices = matrices_list >; +using removable_columns_matrices = matrices_list >; +using max_dim_matrices = matrices_list >; +using barcode_matrices = matrices_list >; +using swap_matrices = matrices_list >; +#endif + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_matrix_zp_constructors, Matrix, full_matrices) { test_constructors(); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_matrix_zp_insertion, Matrix, full_matrices) { + test_boundary_insertion(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_matrix_zp_access, Matrix, full_matrices) { test_boundary_access(); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_matrix_zp_zeroing, Matrix, full_matrices) { test_zeroing(); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_matrix_zp_row_access, Matrix, row_access_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns, 5); + test_non_base_row_access(m); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_matrix_zp_row_removal, Matrix, removable_rows_matrices) { + test_row_removal(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_matrix_zp_column_removal, Matrix, removable_columns_matrices) { + test_boundary_maximal_simplex_removal(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_matrix_zp_max_dimension, Matrix, max_dim_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns, 5); + test_maximal_dimension(m); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_matrix_zp_operation, Matrix, full_matrices) { test_base_operation(); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_matrix_zp_barcode, Matrix, barcode_matrices) { test_barcode(); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(Boundary_matrix_zp_swaps, Matrix, swap_matrices) { test_base_swaps(); } diff --git a/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_zp_chain_barcode.cpp b/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_zp_chain_barcode.cpp new file mode 100644 index 0000000000..08692b44c2 --- /dev/null +++ b/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_zp_chain_barcode.cpp @@ -0,0 +1,91 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2023 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE "persistence_matrix" +#include + +#include "pm_matrix_tests.h" +#include "pm_matrix_tests_boost_type_lists.h" + +#ifdef PM_TEST_ID_IDX +#ifdef PM_TEST_MAX_DIM +using opts = boost::mp11::mp_list; +#else +using opts = boost::mp11::mp_list; +#endif +#else +#ifdef PM_TEST_MAX_DIM +using opts = boost::mp11::mp_list; +#else +using opts = boost::mp11::mp_list; +#endif +#endif + +using full_matrices = matrices_list >; +using row_access_matrices = matrices_list >; +using removable_rows_matrices = matrices_list >; +using removable_columns_matrices = matrices_list >; + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_zp_barcode_constructors, Matrix, full_matrices) { + test_constructors(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_zp_barcode_insertion, Matrix, full_matrices) { + Matrix m1; + m1.set_characteristic(5); + + auto orderedBoundaries = build_simple_boundary_matrix(); + orderedBoundaries.pop_back(); + orderedBoundaries.pop_back(); + + Matrix m2(orderedBoundaries, 5); + + test_chain_boundary_insertion(m1, m2); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_zp_barcode_access, Matrix, full_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns, 5); + test_chain_access(m); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_zp_barcode_row_access, Matrix, row_access_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns, 5); + test_non_base_row_access(m); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_zp_barcode_row_removal, Matrix, removable_rows_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns, 5); + test_chain_row_removal(m); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_zp_barcode_column_removal, Matrix, removable_columns_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns, 5); + test_chain_maximal_simplex_removal(m); +} + +#ifdef PM_TEST_MAX_DIM +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_zp_barcode_max_dimension, Matrix, full_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns, 5); + test_maximal_dimension(m); +} +#endif + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_zp_barcode_operation, Matrix, full_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns, 5); + test_chain_operation(m); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_zp_barcode_barcode, Matrix, full_matrices) { test_barcode(); } diff --git a/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_zp_chain_rep.cpp b/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_zp_chain_rep.cpp new file mode 100644 index 0000000000..d18177b0aa --- /dev/null +++ b/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_zp_chain_rep.cpp @@ -0,0 +1,96 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2023 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE "persistence_matrix" +#include + +#include "pm_matrix_tests.h" +#include "pm_matrix_tests_boost_type_lists.h" + +#ifdef PM_TEST_ID_IDX +#ifdef PM_TEST_MAX_DIM +using opts = boost::mp11::mp_list; +#else +using opts = boost::mp11::mp_list; +#endif +#else +#ifdef PM_TEST_MAX_DIM +using opts = boost::mp11::mp_list; +#else +using opts = boost::mp11::mp_list; +#endif +#endif + +using full_matrices = matrices_list >; +using row_access_matrices = matrices_list >; +using removable_rows_matrices = matrices_list >; +using removable_columns_matrices = matrices_list >; +using barcode_matrices = matrices_list >; + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_zp_rep_constructors, Matrix, full_matrices) { test_constructors(); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_zp_rep_insertion, Matrix, full_matrices) { + Matrix m1; + m1.set_characteristic(5); + + auto orderedBoundaries = build_simple_boundary_matrix(); + orderedBoundaries.pop_back(); + orderedBoundaries.pop_back(); + + Matrix m2(orderedBoundaries, 5); + + test_chain_boundary_insertion(m1, m2); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_zp_rep_access, Matrix, full_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns, 5); + test_chain_access(m); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_zp_rep_row_access, Matrix, row_access_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns, 5); + test_non_base_row_access(m); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_zp_rep_row_removal, Matrix, removable_rows_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns, 5); + test_chain_row_removal(m); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_zp_rep_column_removal, Matrix, removable_columns_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns, 5); + test_chain_maximal_simplex_removal(m); +} + +#ifdef PM_TEST_MAX_DIM +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_zp_rep_max_dimension, Matrix, full_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns, 5); + test_maximal_dimension(m); +} +#endif + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_zp_rep_operation, Matrix, full_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns, 5); + test_chain_operation(m); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_zp_rep_barcode, Matrix, barcode_matrices) { test_barcode(); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(Chain_matrix_zp_rep_representative_cycles, Matrix, full_matrices) { + auto columns = build_longer_boundary_matrix(); + Matrix m(columns, 5); + test_representative_cycles(m); +} diff --git a/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_zp_compression.cpp b/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_zp_compression.cpp new file mode 100644 index 0000000000..e5c92d0ead --- /dev/null +++ b/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_zp_compression.cpp @@ -0,0 +1,45 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2023 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE "persistence_matrix" +#include + +#include "pm_matrix_tests.h" +#include "pm_matrix_tests_boost_type_lists.h" + +using full_matrices = matrices_list; +using row_access_matrices = matrices_list; +using removable_rows_matrices = matrices_list; + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_column_compression_matrix_zp_constructors, Matrix, full_matrices) { + test_constructors(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_column_compression_matrix_zp_insertion, Matrix, full_matrices) { + test_general_insertion(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_column_compression_matrix_zp_access, Matrix, full_matrices) { + test_base_access(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_column_compression_matrix_zp_row_access, Matrix, row_access_matrices) { + test_base_z5_row_access(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_column_compression_matrix_zp_row_removal, Matrix, removable_rows_matrices) { + test_row_removal(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(Base_column_compression_matrix_zp_operation, Matrix, full_matrices) { + test_base_col_comp_operation(); + test_base_col_comp_cell_range_operation(); + test_base_col_comp_const_operation(); +} diff --git a/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_zp_ru_rep.cpp b/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_zp_ru_rep.cpp new file mode 100644 index 0000000000..e81bcaa4e7 --- /dev/null +++ b/src/Persistence_matrix/test/Persistence_matrix_matrix_tests_zp_ru_rep.cpp @@ -0,0 +1,106 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2023 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE "persistence_matrix" +#include + +#include "pm_matrix_tests.h" +#include "pm_matrix_tests_boost_type_lists.h" + +#ifdef PM_TEST_ID_IDX +#ifdef PM_TEST_BARCODE +#ifdef PM_TEST_MAX_DIM +using opts = boost::mp11::mp_list; +#else +using opts = boost::mp11::mp_list; +#endif +#else +#ifdef PM_TEST_MAX_DIM +using opts = boost::mp11::mp_list; +#else +using opts = boost::mp11::mp_list; +#endif +#endif +#else +#ifdef PM_TEST_BARCODE +#ifdef PM_TEST_MAX_DIM +using opts = boost::mp11::mp_list; +#else +using opts = boost::mp11::mp_list; +#endif +#else +#ifdef PM_TEST_MAX_DIM +using opts = boost::mp11::mp_list; +#else +using opts = boost::mp11::mp_list; +#endif +#endif +#endif + +using full_matrices = matrices_list >; +using row_access_matrices = matrices_list >; +using removable_rows_matrices = matrices_list >; +using removable_columns_matrices = matrices_list >; + +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_zp_rep_constructors, Matrix, full_matrices) { test_constructors(); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_zp_rep_insertion, Matrix, full_matrices) { test_boundary_insertion(); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_zp_rep_zeroing, Matrix, full_matrices) { test_zeroing(); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_zp_rep_row_removal, Matrix, removable_rows_matrices) { + test_row_removal(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_zp_rep_column_removal, Matrix, removable_columns_matrices) { + test_ru_maximal_simplex_removal(); +} + +#ifdef PM_TEST_MAX_DIM +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_zp_rep_max_dimension, Matrix, full_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns, 5); + test_maximal_dimension(m); +} +#endif + +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_zp_rep_operation, Matrix, full_matrices) { test_ru_operation(); } + +#ifdef PM_TEST_BARCODE +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_zp_rep_barcode, Matrix, full_matrices) { test_barcode(); } +#endif + +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_zp_rep_representative_cycles, Matrix, full_matrices) { + auto columns = build_longer_boundary_matrix(); + Matrix m(columns, 5); + test_representative_cycles(m); +} + +#ifdef PM_TEST_ID_IDX +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_zp_rep_access, Matrix, full_matrices) { test_boundary_access(); } + +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_zp_rep_row_access, Matrix, row_access_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns, 5); + test_non_base_row_access(m); +} +#else +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_zp_rep_access, Matrix, full_matrices) { + test_boundary_access(); + test_ru_u_access(); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE(RU_matrix_zp_rep_row_access, Matrix, row_access_matrices) { + auto columns = build_simple_boundary_matrix(); + Matrix m(columns, 5); + test_non_base_row_access(m); + test_ru_u_row_access(); +} +#endif diff --git a/src/Persistence_matrix/test/pm_column_tests.h b/src/Persistence_matrix/test/pm_column_tests.h new file mode 100644 index 0000000000..03f2be4f05 --- /dev/null +++ b/src/Persistence_matrix/test/pm_column_tests.h @@ -0,0 +1,1028 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2023 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +#ifndef PM_COLUMN_TESTS_H +#define PM_COLUMN_TESTS_H + +#include +#include + +#include + +#include "pm_test_utilities.h" +#include "pm_column_tests_mastermatrix.h" + +// assumes column was not modified since construction, ie no duplicated / erased values in heap or vector column +template +std::vector > get_ordered_column_contents(std::vector& matrix) { + std::vector(), + unsigned int, + std::pair + >::type> > ordCol(matrix.size()); + for (unsigned int i = 0; i < matrix.size(); ++i) { + Column& col = matrix[i]; + for (auto& cell : col) { + if constexpr (is_z2()) { + ordCol[i].insert(cell.get_row_index()); + } else { + ordCol[i].insert({cell.get_row_index(), cell.get_element()}); + } + } + } + return ordCol; +} + +// assumes column was not modified since construction, ie no duplicated / erased values in heap or vector column +template +std::vector > get_ordered_rows(std::vector& matrix) { + std::vector(), + unsigned int, + std::pair + >::type> > rows; + for (Column& col : matrix) { + for (auto& cell : col) { + if (cell.get_row_index() >= rows.size()) rows.resize(cell.get_row_index() + 1); + if constexpr (is_z2()) { + rows[cell.get_row_index()].insert(cell.get_column_index()); + } else { + rows[cell.get_row_index()].insert({cell.get_column_index(), cell.get_element()}); + } + } + } + return rows; +} + +template +std::vector build_column_matrix(typename Column::Column_settings& settings) { + std::vector matrix; + + if constexpr (is_z2()) { + using cont = std::vector; + + matrix.emplace_back(cont{0, 1, 3, 5}, 4, &settings); + matrix.emplace_back(cont{0, 1, 2, 5, 6}, 4, &settings); + matrix.emplace_back(cont{0, 1, 2, 5, 6}, 4, &settings); + matrix.emplace_back(cont{}, 4, &settings); + matrix.emplace_back(cont{0, 1, 3, 5}, 4, &settings); + matrix.emplace_back(matrix[1]); + } else { + using cont = std::vector >; + matrix.emplace_back(cont{{0, 1}, {1, 2}, {3, 3}, {5, 4}}, 4, &settings); + matrix.emplace_back(cont{{0, 4}, {1, 2}, {2, 1}, {5, 1}, {6, 1}}, 4, &settings); + matrix.emplace_back(cont{{0, 1}, {1, 3}, {2, 4}, {5, 4}, {6, 4}}, 4, &settings); + matrix.emplace_back(cont{}, 4, &settings); + matrix.emplace_back(cont{{0, 1}, {1, 2}, {3, 3}, {5, 4}}, 4, &settings); + matrix.emplace_back(matrix[1]); + } + + return matrix; +} + +template +std::vector build_column_matrix(Rows& rows, typename Column::Column_settings& settings) { + std::vector matrix; + + if constexpr (is_z2()) { + using cont = std::vector; + matrix.emplace_back(0, cont{0, 1, 3, 5}, 4, &rows, &settings); + matrix.emplace_back(1, cont{0, 1, 2, 5, 6}, 4, &rows, &settings); + matrix.emplace_back(2, cont{0, 1, 2, 5, 6}, 4, &rows, &settings); + matrix.emplace_back(3, cont{}, 4, &rows, &settings); + matrix.emplace_back(4, cont{0, 1, 3, 5}, 4, &rows, &settings); + matrix.emplace_back(matrix[1], 5, &rows); + } else { + using cont = std::vector >; + matrix.emplace_back(0, cont{{0, 1}, {1, 2}, {3, 3}, {5, 4}}, 4, &rows, &settings); + matrix.emplace_back(1, cont{{0, 4}, {1, 2}, {2, 1}, {5, 1}, {6, 1}}, 4, &rows, &settings); + matrix.emplace_back(2, cont{{0, 1}, {1, 3}, {2, 4}, {5, 4}, {6, 4}}, 4, &rows, &settings); + matrix.emplace_back(3, cont{}, 4, &rows, &settings); + matrix.emplace_back(4, cont{{0, 1}, {1, 2}, {3, 3}, {5, 4}}, 4, &rows, &settings); + matrix.emplace_back(matrix[1], 5, &rows); + } + + return matrix; +} + +template +std::vector build_base_boundary_column_matrix(Rows& rows, typename Column::Column_settings& settings) { + std::vector matrix; + + if constexpr (is_z2()) { + using cont = std::vector; + matrix.emplace_back(0, cont{0, 1, 3, 5}, &rows, &settings); + matrix.emplace_back(1, cont{0, 1, 2, 5, 6}, &rows, &settings); + matrix.emplace_back(2, cont{0, 1, 2, 5, 6}, &rows, &settings); + matrix.emplace_back(3, cont{}, &rows, &settings); + matrix.emplace_back(4, cont{0, 1, 3, 5}, &rows, &settings); + matrix.emplace_back(matrix[1], 5, &rows); + } else { + using cont = std::vector >; + matrix.emplace_back(0, cont{{0, 1}, {1, 2}, {3, 3}, {5, 4}}, &rows, &settings); + matrix.emplace_back(1, cont{{0, 4}, {1, 2}, {2, 1}, {5, 1}, {6, 1}}, &rows, &settings); + matrix.emplace_back(2, cont{{0, 1}, {1, 3}, {2, 4}, {5, 4}, {6, 4}}, &rows, &settings); + matrix.emplace_back(3, cont{}, &rows, &settings); + matrix.emplace_back(4, cont{{0, 1}, {1, 2}, {3, 3}, {5, 4}}, &rows, &settings); + matrix.emplace_back(matrix[1], 5, &rows); + } + + return matrix; +} + +template +std::vector build_base_boundary_column_matrix(typename Column::Column_settings& settings) { + std::vector matrix; + + if constexpr (is_z2()) { + using cont = std::vector; + matrix.emplace_back(cont{0, 1, 3, 5}, &settings); + matrix.emplace_back(cont{0, 1, 2, 5, 6}, &settings); + matrix.emplace_back(cont{0, 1, 2, 5, 6}, &settings); + matrix.emplace_back(cont{}, &settings); + matrix.emplace_back(cont{0, 1, 3, 5}, &settings); + matrix.emplace_back(matrix[1]); + } else { + using cont = std::vector >; + matrix.emplace_back(cont{{0, 1}, {1, 2}, {3, 3}, {5, 4}}, &settings); + matrix.emplace_back(cont{{0, 4}, {1, 2}, {2, 1}, {5, 1}, {6, 1}}, &settings); + matrix.emplace_back(cont{{0, 1}, {1, 3}, {2, 4}, {5, 4}, {6, 4}}, &settings); + matrix.emplace_back(cont{}, &settings); + matrix.emplace_back(cont{{0, 1}, {1, 2}, {3, 3}, {5, 4}}, &settings); + matrix.emplace_back(matrix[1]); + } + + return matrix; +} + +template +std::vector(), + unsigned int, + std::pair + >::type> > build_rows() { + using cell_type = typename std::conditional(), + unsigned int, + std::pair + >::type; + using container_type = std::vector; + std::vector rows(7); + + if constexpr (is_z2()) { + rows[0] = {0, 1, 2, 4, 5}; + rows[1] = {0, 1, 2, 4, 5}; + rows[2] = {1, 2, 5}; + rows[3] = {0, 4}; + rows[4] = {}; + rows[5] = {0, 1, 2, 4, 5}; + rows[6] = {1, 2, 5}; + } else { + rows[0] = {{0, 1}, {1, 4}, {2, 1}, {4, 1}, {5, 4}}; + rows[1] = {{0, 2}, {1, 2}, {2, 3}, {4, 2}, {5, 2}}; + rows[2] = {{1, 1}, {2, 4}, {5, 1}}; + rows[3] = {{0, 3}, {4, 3}}; + rows[4] = {}; + rows[5] = {{0, 4}, {1, 1}, {2, 4}, {4, 4}, {5, 1}}; + rows[6] = {{1, 1}, {2, 4}, {5, 1}}; + } + + return rows; +} + +template +std::vector(), + unsigned int, + std::pair + >::type> > build_column_values() { + using cell_type = typename std::conditional(), + unsigned int, + std::pair + >::type; + using container_type = std::vector; + std::vector columns(6); + + if constexpr (is_z2()) { + columns[0] = {0, 1, 3, 5}; + columns[1] = {0, 1, 2, 5, 6}; + columns[2] = {0, 1, 2, 5, 6}; + columns[3] = {}; + columns[4] = {0, 1, 3, 5}; + columns[5] = {0, 1, 2, 5, 6}; + } else { + columns[0] = {{0, 1}, {1, 2}, {3, 3}, {5, 4}}; + columns[1] = {{0, 4}, {1, 2}, {2, 1}, {5, 1}, {6, 1}}; + columns[2] = {{0, 1}, {1, 3}, {2, 4}, {5, 4}, {6, 4}}; + columns[3] = {}; + columns[4] = {{0, 1}, {1, 2}, {3, 3}, {5, 4}}; + columns[5] = {{0, 4}, {1, 2}, {2, 1}, {5, 1}, {6, 1}}; + } + + return columns; +} + +template +void column_test_common_constructors() { + using cell_type = typename std::conditional(), + unsigned int, + std::pair + >::type; + using container_type = std::vector; + + container_type cont1, cont2; + typename Column::Column_settings settings(5); + + if constexpr (is_z2()) { + cont1 = {0, 2, 4}; + cont2 = {0, 5, 6}; + } else { + cont1 = {{0, 1u}, {2, 2u}, {4, 3u}}; + cont2 = {{0, 1u}, {5, 2u}, {6, 3u}}; + } + + Column emptyCol(&settings); + BOOST_CHECK_EQUAL(emptyCol.size(), 0); + + Column col(cont1, 2, &settings); + BOOST_CHECK_EQUAL(col.size(), 3); + + std::vector rows; // type doesn't matter, as the row option is not enabled. + Column rowCol(2, cont2, 2, &rows, &settings); + BOOST_CHECK_EQUAL(rowCol.size(), 3); + + Column copyCol(col); + BOOST_CHECK_EQUAL(copyCol.size(), 3); + + Column rowCopyCol(rowCol, 4, &rows); + BOOST_CHECK_EQUAL(rowCopyCol.size(), 3); + + Column moveCol(std::move(col)); + BOOST_CHECK_EQUAL(moveCol.size(), 3); + BOOST_CHECK_EQUAL(col.size(), 0); + BOOST_CHECK(copyCol.get_content() == moveCol.get_content()); + + Column assignCol = copyCol; + BOOST_CHECK_EQUAL(assignCol.size(), 3); + + swap(copyCol, rowCol); + BOOST_CHECK_EQUAL(copyCol.size(), 3); + BOOST_CHECK_EQUAL(rowCol.size(), 3); + BOOST_CHECK(copyCol.get_content() == rowCopyCol.get_content()); + BOOST_CHECK(rowCol.get_content() == assignCol.get_content()); + + BOOST_CHECK_EQUAL(rows.size(), 0); +} + +template +void column_test_common_content_access(Column& col, + const std::set& setcont, + const std::vector& veccont) { + BOOST_CHECK(get_column_content_via_iterators(col) == setcont); + BOOST_CHECK(col.get_content(veccont.size()) == veccont); + if constexpr (Column::Master::Option_list::column_type != Column_types::HEAP) { + BOOST_CHECK_EQUAL(col.size(), setcont.size()); + } + + if (veccont.empty()) + BOOST_CHECK(col.is_empty()); + else + BOOST_CHECK(!col.is_empty()); + + for (unsigned int i = 0; i < veccont.size(); ++i) { + if (veccont[i] == 0u) + BOOST_CHECK(!col.is_non_zero(i)); + else + BOOST_CHECK(col.is_non_zero(i)); + } +} + +// assumes that matrix was build with build_column_matrix and was not modified since. +template +void column_test_common_z5_content_access(std::vector& matrix) { + std::set > setcont; + std::vector veccont; + + setcont = {{0, 1}, {1, 2}, {3, 3}, {5, 4}}; + veccont = {1, 2, 0, 3, 0, 4}; + column_test_common_content_access(matrix[0], setcont, veccont); + + setcont = {{0, 4}, {1, 2}, {2, 1}, {5, 1}, {6, 1}}; + veccont = {4, 2, 1, 0, 0, 1, 1}; + column_test_common_content_access(matrix[1], setcont, veccont); + + setcont = {{0, 1}, {1, 3}, {2, 4}, {5, 4}, {6, 4}}; + veccont = {1, 3, 4, 0, 0, 4, 4}; + column_test_common_content_access(matrix[2], setcont, veccont); + + setcont = {}; + veccont = {}; + column_test_common_content_access(matrix[3], setcont, veccont); + + setcont = {{0, 1}, {1, 2}, {3, 3}, {5, 4}}; + veccont = {1, 2, 0, 3, 0, 4}; + column_test_common_content_access(matrix[4], setcont, veccont); + + setcont = {{0, 4}, {1, 2}, {2, 1}, {5, 1}, {6, 1}}; + veccont = {4, 2, 1, 0, 0, 1, 1}; + column_test_common_content_access(matrix[5], setcont, veccont); +} + +// assumes that matrix was build with build_column_matrix and was not modified since. +template +void column_test_common_z2_content_access(std::vector& matrix) { + std::set setcont; + std::vector veccont; + + setcont = {0, 1, 3, 5}; + veccont = {1, 1, 0, 1, 0, 1}; + column_test_common_content_access(matrix[0], setcont, veccont); + + setcont = {0, 1, 2, 5, 6}; + veccont = {1, 1, 1, 0, 0, 1, 1}; + column_test_common_content_access(matrix[1], setcont, veccont); + + setcont = {0, 1, 2, 5, 6}; + veccont = {1, 1, 1, 0, 0, 1, 1}; + column_test_common_content_access(matrix[2], setcont, veccont); + + setcont = {}; + veccont = {}; + column_test_common_content_access(matrix[3], setcont, veccont); + + setcont = {0, 1, 3, 5}; + veccont = {1, 1, 0, 1, 0, 1}; + column_test_common_content_access(matrix[4], setcont, veccont); + + setcont = {0, 1, 2, 5, 6}; + veccont = {1, 1, 1, 0, 0, 1, 1}; + column_test_common_content_access(matrix[5], setcont, veccont); +} + +// assumes that matrix was build with build_column_matrix and was not modified since. +template +void column_test_common_z5_operators(std::vector& matrix) { + std::set > setcont; + std::vector veccont; + + matrix[0] += matrix[1]; + matrix[1] += matrix[2]; + + setcont = {{1, 4}, {2, 1}, {3, 3}, {6, 1}}; + veccont = {0, 4, 1, 3, 0, 0, 1}; + column_test_common_content_access(matrix[0], setcont, veccont); + BOOST_CHECK(matrix[1] == matrix[1]); + BOOST_CHECK(matrix[1] == matrix[3]); + BOOST_CHECK(matrix[1] < matrix[0]); + if constexpr (Column::Master::Option_list::column_type == Column_types::HEAP) { + BOOST_CHECK(matrix[0] < matrix[2]); // order different for heap columns + } else { + BOOST_CHECK(matrix[2] < matrix[0]); + } + BOOST_CHECK(!(matrix[0] < matrix[0])); + + matrix[0] *= 4; + matrix[1] *= 2; + matrix[2] *= 1; + + setcont = {{1, 1}, {2, 4}, {3, 2}, {6, 4}}; + veccont = {0, 1, 4, 2, 0, 0, 4}; + column_test_common_content_access(matrix[0], setcont, veccont); + setcont = {{0, 1}, {1, 3}, {2, 4}, {5, 4}, {6, 4}}; + veccont = {1, 3, 4, 0, 0, 4, 4}; + column_test_common_content_access(matrix[2], setcont, veccont); + BOOST_CHECK(matrix[1] == matrix[1]); + BOOST_CHECK(matrix[1] == matrix[3]); + BOOST_CHECK(matrix[1] < matrix[0]); + if constexpr (Column::Master::Option_list::column_type == Column_types::HEAP) { + BOOST_CHECK(matrix[0] < matrix[2]); // order different for heap columns + } else { + BOOST_CHECK(matrix[2] < matrix[0]); + } + BOOST_CHECK(!(matrix[0] < matrix[0])); + + // this = v * this + column + matrix[4].multiply_target_and_add(4, matrix[5]); + setcont = {{0, 3}, {2, 1}, {3, 2}, {5, 2}, {6, 1}}; + veccont = {3, 0, 1, 2, 0, 2, 1}; + column_test_common_content_access(matrix[4], setcont, veccont); + // this = this + column * v + matrix[5].multiply_source_and_add(matrix[3], 3); + setcont = {{0, 4}, {1, 2}, {2, 1}, {5, 1}, {6, 1}}; + veccont = {4, 2, 1, 0, 0, 1, 1}; + column_test_common_content_access(matrix[5], setcont, veccont); + // this = this + column * v + matrix[5].multiply_source_and_add(matrix[4], 3); + setcont = {{0, 3}, {1, 2}, {2, 4}, {3, 1}, {5, 2}, {6, 4}}; + veccont = {3, 2, 4, 1, 0, 2, 4}; + column_test_common_content_access(matrix[5], setcont, veccont); + // this = v * this + column + matrix[3].multiply_target_and_add(4, matrix[5]); + setcont = {{0, 3}, {1, 2}, {2, 4}, {3, 1}, {5, 2}, {6, 4}}; + veccont = {3, 2, 4, 1, 0, 2, 4}; + column_test_common_content_access(matrix[3], setcont, veccont); + BOOST_CHECK(matrix[3] == matrix[5]); + BOOST_CHECK(!(matrix[5] == matrix[4])); + BOOST_CHECK(!(matrix[5] < matrix[3])); + BOOST_CHECK(!(matrix[3] < matrix[5])); + if constexpr (Column::Master::Option_list::column_type == Column_types::HEAP) { + BOOST_CHECK(matrix[4] < matrix[5]); + BOOST_CHECK(matrix[4] < matrix[3]); + } else { + BOOST_CHECK(matrix[5] < matrix[4]); + BOOST_CHECK(matrix[3] < matrix[4]); + } +} + +// assumes that matrix was build with build_column_matrix and was not modified since. +template +void column_test_common_z2_operators(std::vector& matrix) { + std::set setcont; + std::vector veccont; + + matrix[0] += matrix[1]; + matrix[1] += matrix[2]; + + setcont = {2, 3, 6}; + veccont = {0, 0, 1, 1, 0, 0, 1}; + column_test_common_content_access(matrix[0], setcont, veccont); + BOOST_CHECK(matrix[1] == matrix[1]); + BOOST_CHECK(matrix[1] == matrix[3]); + BOOST_CHECK(matrix[1] < matrix[0]); + if constexpr (Column::Master::Option_list::column_type == Column_types::HEAP) { + BOOST_CHECK(matrix[0] < matrix[2]); // order different for heap columns + } else { + BOOST_CHECK(matrix[2] < matrix[0]); + } + BOOST_CHECK(!(matrix[0] < matrix[0])); + + matrix[0] *= 5; + matrix[2] *= 1; + + setcont = {2, 3, 6}; + veccont = {0, 0, 1, 1, 0, 0, 1}; + column_test_common_content_access(matrix[0], setcont, veccont); + setcont = {0, 1, 2, 5, 6}; + veccont = {1, 1, 1, 0, 0, 1, 1}; + column_test_common_content_access(matrix[2], setcont, veccont); + BOOST_CHECK(!(matrix[0] == matrix[2])); + BOOST_CHECK(matrix[2] == matrix[2]); + BOOST_CHECK(matrix[1] < matrix[0]); + if constexpr (Column::Master::Option_list::column_type == Column_types::HEAP) { + BOOST_CHECK(matrix[0] < matrix[2]); // order different for heap columns + } else { + BOOST_CHECK(matrix[2] < matrix[0]); + } + BOOST_CHECK(!(matrix[0] < matrix[0])); + + // this = v * this + column + matrix[4].multiply_target_and_add(3, matrix[5]); + setcont = {2, 3, 6}; + veccont = {0, 0, 1, 1, 0, 0, 1}; + column_test_common_content_access(matrix[4], setcont, veccont); + BOOST_CHECK(matrix[4] == matrix[0]); + // this = this + column * v + matrix[5].multiply_source_and_add(matrix[3], 3); + setcont = {0, 1, 2, 5, 6}; + veccont = {1, 1, 1, 0, 0, 1, 1}; + column_test_common_content_access(matrix[5], setcont, veccont); + BOOST_CHECK(matrix[5] == matrix[2]); + // this = this + column * v + matrix[5].multiply_source_and_add(matrix[4], 3); + setcont = {0, 1, 3, 5}; + veccont = {1, 1, 0, 1, 0, 1, 0}; + column_test_common_content_access(matrix[5], setcont, veccont); + // this = v * this + column + matrix[3].multiply_target_and_add(3, matrix[5]); + setcont = {0, 1, 3, 5}; + veccont = {1, 1, 0, 1, 0, 1, 0}; + column_test_common_content_access(matrix[3], setcont, veccont); + BOOST_CHECK(matrix[3] == matrix[5]); + BOOST_CHECK(!(matrix[5] < matrix[3])); + BOOST_CHECK(!(matrix[3] < matrix[5])); + BOOST_CHECK(matrix[5] < matrix[4]); +} + +// common: assumes that matrix was build with build_column_matrix(rows) and not modified since. +// base/boundary special: assumes that matrix was build with build_base_boundary_column_matrix(rows) and not modified +// since. +template +void column_test_row_access_constructors(std::vector& matrix, row_container_type& rows) { + test_matrix_equality(build_rows(), get_ordered_rows(matrix)); + test_matrix_equality(build_column_values(), get_ordered_column_contents(matrix)); + + Column col(matrix[0], 6, &rows); + for (auto& r : rows) { + if constexpr (Column::Master::Option_list::has_removable_rows) { + if (!r.second.empty()) { + auto& cell = *r.second.rbegin(); + if (cell.get_row_index() == 0 || cell.get_row_index() == 1 || cell.get_row_index() == 3 || + cell.get_row_index() == 5) { + BOOST_CHECK_EQUAL(cell.get_column_index(), 6); + } + } + } else { + if (!r.empty()) { + auto& cell = *r.rbegin(); + if (cell.get_row_index() == 0 || cell.get_row_index() == 1 || cell.get_row_index() == 3 || + cell.get_row_index() == 5) { + BOOST_CHECK_EQUAL(cell.get_column_index(), 6); + } + } + } + } + + Column moveCol(std::move(col)); + BOOST_CHECK_EQUAL(moveCol.size(), 4); + BOOST_CHECK_EQUAL(col.size(), 0); + BOOST_CHECK(matrix[0] == moveCol); + for (auto& r : rows) { + if constexpr (Column::Master::Option_list::has_removable_rows) { + if (!r.second.empty()) { + auto& cell = *r.second.rbegin(); + if (cell.get_row_index() == 0 || cell.get_row_index() == 1 || cell.get_row_index() == 3 || + cell.get_row_index() == 5) { + BOOST_CHECK_EQUAL(cell.get_column_index(), 6); + } + } + } else { + if (!r.empty()) { + auto& cell = *r.rbegin(); + if (cell.get_row_index() == 0 || cell.get_row_index() == 1 || cell.get_row_index() == 3 || + cell.get_row_index() == 5) { + BOOST_CHECK_EQUAL(cell.get_column_index(), 6); + } + } + } + } + + swap(col, moveCol); + BOOST_CHECK_EQUAL(moveCol.size(), 0); + BOOST_CHECK_EQUAL(col.size(), 4); + BOOST_CHECK(matrix[0] == col); + for (auto& r : rows) { + if constexpr (Column::Master::Option_list::has_removable_rows) { + if (!r.second.empty()) { + auto& cell = *r.second.rbegin(); + if (cell.get_row_index() == 0 || cell.get_row_index() == 1 || cell.get_row_index() == 3 || + cell.get_row_index() == 5) { + BOOST_CHECK_EQUAL(cell.get_column_index(), 6); + } + } + } else { + if (!r.empty()) { + auto& cell = *r.rbegin(); + if (cell.get_row_index() == 0 || cell.get_row_index() == 1 || cell.get_row_index() == 3 || + cell.get_row_index() == 5) { + BOOST_CHECK_EQUAL(cell.get_column_index(), 6); + } + } + } + } +} + +template +void column_test_base_boundary_constructors() { + using cell_type = typename std::conditional(), + unsigned int, + std::pair + >::type; + using container_type = std::vector; + + typename Column::Column_settings settings(5); + + container_type cont1, cont2; + + if constexpr (is_z2()) { + cont1 = {0, 2, 4}; + cont2 = {0, 5, 6}; + } else { + cont1 = {{0, 1u}, {2, 2u}, {4, 3u}}; + cont2 = {{0, 1u}, {5, 2u}, {6, 3u}}; + } + + Column col(cont1, &settings); + BOOST_CHECK_EQUAL(col.size(), 3); + + std::vector rows; // type doesn't matter, as the row option is not enabled. + Column rowCol(2, cont2, &rows, &settings); + BOOST_CHECK_EQUAL(rowCol.size(), 3); + + BOOST_CHECK(!(col == rowCol)); +} + +template +void column_test_base_boundary_z5_methods() { + std::vector veccont; + typename Column::Column_settings settings(5); + + Column col( + std::vector >{ + {0, 4}, {1, 2}, {2, 1}, {5, 1}, {6, 1}}, + &settings); + veccont = {4, 2, 1, 0, 0, 1, 1}; + BOOST_CHECK(col.get_content(veccont.size()) == veccont); + BOOST_CHECK_EQUAL(col.size(), 5); + + std::vector permutation{0, 5, 2, 3, 1, 4, 6}; + col.reorder(permutation); + veccont = {4, 0, 1, 0, 1, 2, 1}; + BOOST_CHECK(col.get_content(veccont.size()) == veccont); + BOOST_CHECK_EQUAL(col.size(), 5); + + col.clear(2); + col.clear(6); + veccont = {4, 0, 0, 0, 1, 2, 0}; + BOOST_CHECK(col.get_content(veccont.size()) == veccont); + BOOST_CHECK_EQUAL(col.size(), 3); + + col.clear(); + veccont = {}; + BOOST_CHECK(col.get_content(veccont.size()) == veccont); + BOOST_CHECK_EQUAL(col.size(), 0); +} + +template +void column_test_base_boundary_z2_methods() { + std::vector veccont; + typename Column::Column_settings settings; + + Column col(std::vector{0, 1, 2, 5, 6}, &settings); + veccont = {1, 1, 1, 0, 0, 1, 1}; + BOOST_CHECK(col.get_content(veccont.size()) == veccont); + BOOST_CHECK_EQUAL(col.size(), 5); + + std::vector permutation{0, 5, 2, 3, 1, 4, 6}; + col.reorder(permutation); + veccont = {1, 0, 1, 0, 1, 1, 1}; + BOOST_CHECK(col.get_content(veccont.size()) == veccont); + BOOST_CHECK_EQUAL(col.size(), 5); + + col.clear(2); + col.clear(6); + veccont = {1, 0, 0, 0, 1, 1, 0}; + BOOST_CHECK(col.get_content(veccont.size()) == veccont); + BOOST_CHECK_EQUAL(col.size(), 3); + + col.clear(); + veccont = {}; + BOOST_CHECK(col.get_content(veccont.size()) == veccont); + BOOST_CHECK_EQUAL(col.size(), 0); +} + +// assumes that matrix was build with build_column_matrix and was not modified since. +template +void column_test_base_z5_operators(std::vector& matrix) { + using Cell = typename Column::Cell; + std::set setcont; + std::vector veccont; + + Cell cell(0); + cell.set_element(4); + setcont.insert(cell); + cell = Cell(1); + cell.set_element(2); + setcont.insert(cell); + cell = Cell(2); + cell.set_element(1); + setcont.insert(cell); + cell = Cell(5); + cell.set_element(1); + setcont.insert(cell); + cell = Cell(6); + cell.set_element(1); + setcont.insert(cell); + matrix[0] += setcont; + + veccont = {0, 4, 1, 3, 0, 0, 1}; + BOOST_CHECK(matrix[0].get_content(veccont.size()) == veccont); + if constexpr (Column::Master::Option_list::column_type != Column_types::HEAP) { + BOOST_CHECK_EQUAL(matrix[0].size(), 4); + } + + setcont.clear(); + cell = Cell(0); + cell.set_element(1); + setcont.insert(cell); + cell = Cell(1); + cell.set_element(3); + setcont.insert(cell); + cell = Cell(2); + cell.set_element(4); + setcont.insert(cell); + cell = Cell(5); + cell.set_element(4); + setcont.insert(cell); + cell = Cell(6); + cell.set_element(4); + setcont.insert(cell); + matrix[1] += setcont; + + veccont = {}; + BOOST_CHECK(matrix[1].get_content(veccont.size()) == veccont); + if constexpr (Column::Master::Option_list::column_type != Column_types::HEAP) { + BOOST_CHECK_EQUAL(matrix[1].size(), 0); + } + + // this = v * this + column + matrix[4].multiply_target_and_add(4, setcont); + veccont = {0, 1, 4, 2, 0, 0, 4}; + BOOST_CHECK(matrix[4].get_content(veccont.size()) == veccont); + if constexpr (Column::Master::Option_list::column_type != Column_types::HEAP) { + BOOST_CHECK_EQUAL(matrix[4].size(), 4); + } + // this = this + column * v + setcont = {}; + matrix[5].multiply_source_and_add(setcont, 3); + veccont = {4, 2, 1, 0, 0, 1, 1}; + BOOST_CHECK(matrix[5].get_content(veccont.size()) == veccont); + if constexpr (Column::Master::Option_list::column_type != Column_types::HEAP) { + BOOST_CHECK_EQUAL(matrix[5].size(), 5); + } + // this = this + column * v + setcont.clear(); + cell = Cell(0); + cell.set_element(3); + setcont.insert(cell); + cell = Cell(2); + cell.set_element(1); + setcont.insert(cell); + cell = Cell(3); + cell.set_element(2); + setcont.insert(cell); + cell = Cell(5); + cell.set_element(2); + setcont.insert(cell); + cell = Cell(6); + cell.set_element(1); + setcont.insert(cell); + matrix[5].multiply_source_and_add(setcont, 3); + veccont = {3, 2, 4, 1, 0, 2, 4}; + BOOST_CHECK(matrix[5].get_content(veccont.size()) == veccont); + if constexpr (Column::Master::Option_list::column_type != Column_types::HEAP) { + BOOST_CHECK_EQUAL(matrix[5].size(), 6); + } + // this = v * this + column + setcont.clear(); + cell = Cell(0); + cell.set_element(3); + setcont.insert(cell); + cell = Cell(1); + cell.set_element(2); + setcont.insert(cell); + cell = Cell(2); + cell.set_element(4); + setcont.insert(cell); + cell = Cell(3); + cell.set_element(1); + setcont.insert(cell); + cell = Cell(5); + cell.set_element(2); + setcont.insert(cell); + cell = Cell(6); + cell.set_element(4); + setcont.insert(cell); + matrix[3].multiply_target_and_add(4, setcont); + veccont = {3, 2, 4, 1, 0, 2, 4}; + BOOST_CHECK(matrix[3].get_content(veccont.size()) == veccont); + if constexpr (Column::Master::Option_list::column_type != Column_types::HEAP) { + BOOST_CHECK_EQUAL(matrix[3].size(), 6); + } +} + +// assumes that matrix was build with build_column_matrix and was not modified since. +template +void column_test_base_z2_operators(std::vector& matrix) { + std::set setcont; + std::vector veccont; + + setcont = {0, 1, 2, 5, 6}; + matrix[0] += setcont; + + veccont = {0, 0, 1, 1, 0, 0, 1}; + BOOST_CHECK(matrix[0].get_content(veccont.size()) == veccont); + if constexpr (Column::Master::Option_list::column_type != Column_types::HEAP) { + BOOST_CHECK_EQUAL(matrix[0].size(), 3); + } + + setcont = {0, 1, 2, 5, 6}; + matrix[1] += setcont; + + veccont = {}; + BOOST_CHECK(matrix[1].get_content(veccont.size()) == veccont); + if constexpr (Column::Master::Option_list::column_type != Column_types::HEAP) { + BOOST_CHECK_EQUAL(matrix[1].size(), 0); + } + + // this = v * this + column + matrix[4].multiply_target_and_add(1, setcont); + veccont = {0, 0, 1, 1, 0, 0, 1}; + BOOST_CHECK(matrix[4].get_content(veccont.size()) == veccont); + if constexpr (Column::Master::Option_list::column_type != Column_types::HEAP) { + BOOST_CHECK_EQUAL(matrix[4].size(), 3); + } + // this = this + column * v + setcont = {}; + matrix[5].multiply_source_and_add(setcont, 1); + veccont = {1, 1, 1, 0, 0, 1, 1}; + BOOST_CHECK(matrix[5].get_content(veccont.size()) == veccont); + if constexpr (Column::Master::Option_list::column_type != Column_types::HEAP) { + BOOST_CHECK_EQUAL(matrix[5].size(), 5); + } + // this = this + column * v + setcont = {2, 3, 6}; + matrix[5].multiply_source_and_add(setcont, 1); + veccont = {1, 1, 0, 1, 0, 1, 0}; + BOOST_CHECK(matrix[5].get_content(veccont.size()) == veccont); + if constexpr (Column::Master::Option_list::column_type != Column_types::HEAP) { + BOOST_CHECK_EQUAL(matrix[5].size(), 4); + } + // this = v * this + column + setcont = {0, 1, 3, 5}; + matrix[3].multiply_target_and_add(0, setcont); + veccont = {1, 1, 0, 1, 0, 1, 0}; + BOOST_CHECK(matrix[3].get_content(veccont.size()) == veccont); + if constexpr (Column::Master::Option_list::column_type != Column_types::HEAP) { + BOOST_CHECK_EQUAL(matrix[3].size(), 4); + } +} + +template +void column_test_boundary_chain_methods(std::vector& matrix) { + BOOST_CHECK_EQUAL(matrix[0].get_pivot(), 5); + BOOST_CHECK_EQUAL(matrix[1].get_pivot(), 6); + BOOST_CHECK_EQUAL(matrix[2].get_pivot(), 6); + BOOST_CHECK_EQUAL(matrix[3].get_pivot(), -1); + BOOST_CHECK_EQUAL(matrix[4].get_pivot(), 5); + BOOST_CHECK_EQUAL(matrix[5].get_pivot(), 6); + + if constexpr (!is_z2()) { + BOOST_CHECK_EQUAL(matrix[0].get_pivot_value(), 4u); + BOOST_CHECK_EQUAL(matrix[1].get_pivot_value(), 1u); + BOOST_CHECK_EQUAL(matrix[2].get_pivot_value(), 4u); + BOOST_CHECK_EQUAL(matrix[3].get_pivot_value(), 0u); + BOOST_CHECK_EQUAL(matrix[4].get_pivot_value(), 4u); + BOOST_CHECK_EQUAL(matrix[5].get_pivot_value(), 1u); + } + + BOOST_CHECK_EQUAL(matrix[0].get_dimension(), 4); + BOOST_CHECK_EQUAL(matrix[1].get_dimension(), 4); + BOOST_CHECK_EQUAL(matrix[2].get_dimension(), 4); + BOOST_CHECK_EQUAL(matrix[3].get_dimension(), 4); + BOOST_CHECK_EQUAL(matrix[4].get_dimension(), 4); + BOOST_CHECK_EQUAL(matrix[5].get_dimension(), 4); +} + +template +void column_test_boundary_methods(std::vector& matrix) { + BOOST_CHECK_EQUAL(matrix[0].get_dimension(), 3); + BOOST_CHECK_EQUAL(matrix[1].get_dimension(), 4); + BOOST_CHECK_EQUAL(matrix[2].get_dimension(), 4); + BOOST_CHECK_EQUAL(matrix[3].get_dimension(), 0); + BOOST_CHECK_EQUAL(matrix[4].get_dimension(), 3); + BOOST_CHECK_EQUAL(matrix[5].get_dimension(), 4); +} + +// assumes that matrix was build with build_column_matrix and was not modified since. +template +void column_test_boundary_z5_operators(std::vector& matrix) { + using cont = std::vector >; + + std::vector veccont; + typename Column::Column_settings settings(5); + + const Column col0(cont{{0, 4}, {1, 2}, {2, 1}, {5, 1}, {6, 1}}, &settings); + matrix[0] += col0; + + veccont = {0, 4, 1, 3, 0, 0, 1}; + BOOST_CHECK(matrix[0].get_content(veccont.size()) == veccont); + if constexpr (Column::Master::Option_list::column_type != Column_types::HEAP) { + BOOST_CHECK_EQUAL(matrix[0].size(), 4); + } + + const Column col1(cont{{0, 1}, {1, 3}, {2, 4}, {5, 4}, {6, 4}}, &settings); + matrix[1] += col1; + + veccont = {}; + BOOST_CHECK(matrix[1].get_content(veccont.size()) == veccont); + if constexpr (Column::Master::Option_list::column_type != Column_types::HEAP) { + BOOST_CHECK_EQUAL(matrix[1].size(), 0); + } + + // this = v * this + column + matrix[4].multiply_target_and_add(4, col1); + veccont = {0, 1, 4, 2, 0, 0, 4}; + BOOST_CHECK(matrix[4].get_content(veccont.size()) == veccont); + if constexpr (Column::Master::Option_list::column_type != Column_types::HEAP) { + BOOST_CHECK_EQUAL(matrix[4].size(), 4); + } + // this = this + column * v + const Column col2(cont{}, &settings); + matrix[5].multiply_source_and_add(col2, 3); + veccont = {4, 2, 1, 0, 0, 1, 1}; + BOOST_CHECK(matrix[5].get_content(veccont.size()) == veccont); + BOOST_CHECK_EQUAL(matrix[5].size(), 5); + // this = this + column * v + const Column col3(cont{{0, 3}, {2, 1}, {3, 2}, {5, 2}, {6, 1}}, &settings); + matrix[5].multiply_source_and_add(col3, 3); + veccont = {3, 2, 4, 1, 0, 2, 4}; + BOOST_CHECK(matrix[5].get_content(veccont.size()) == veccont); + if constexpr (Column::Master::Option_list::column_type != Column_types::HEAP) { + BOOST_CHECK_EQUAL(matrix[5].size(), 6); + } + // this = v * this + column + const Column col4(cont{{0, 3}, {1, 2}, {2, 4}, {3, 1}, {5, 2}, {6, 4}}, &settings); + matrix[3].multiply_target_and_add(4, col4); + veccont = {3, 2, 4, 1, 0, 2, 4}; + BOOST_CHECK(matrix[3].get_content(veccont.size()) == veccont); + if constexpr (Column::Master::Option_list::column_type != Column_types::HEAP) { + BOOST_CHECK_EQUAL(matrix[3].size(), 6); + } +} + +// assumes that matrix was build with build_column_matrix and was not modified since. +template +void column_test_boundary_z2_operators(std::vector& matrix) { + using cont = std::vector; + + std::vector veccont; + typename Column::Column_settings settings; + + const Column col0(cont{0, 1, 2, 5, 6}, &settings); + matrix[0] += col0; + + veccont = {0, 0, 1, 1, 0, 0, 1}; + BOOST_CHECK(matrix[0].get_content(veccont.size()) == veccont); + if constexpr (Column::Master::Option_list::column_type != Column_types::HEAP) { + BOOST_CHECK_EQUAL(matrix[0].size(), 3); + } + + const Column col1(cont{0, 1, 2, 5, 6}, &settings); + matrix[1] += col1; + + veccont = {}; + BOOST_CHECK(matrix[1].get_content(veccont.size()) == veccont); + if constexpr (Column::Master::Option_list::column_type != Column_types::HEAP) { + BOOST_CHECK_EQUAL(matrix[1].size(), 0); + } + + // this = v * this + column + matrix[4].multiply_target_and_add(3, col1); + veccont = {0, 0, 1, 1, 0, 0, 1}; + BOOST_CHECK(matrix[4].get_content(veccont.size()) == veccont); + if constexpr (Column::Master::Option_list::column_type != Column_types::HEAP) { + BOOST_CHECK_EQUAL(matrix[4].size(), 3); + } + // this = this + column * v + const Column col2(cont{}, &settings); + matrix[5].multiply_source_and_add(col2, 3); + veccont = {1, 1, 1, 0, 0, 1, 1}; + BOOST_CHECK(matrix[5].get_content(veccont.size()) == veccont); + if constexpr (Column::Master::Option_list::column_type != Column_types::HEAP) { + BOOST_CHECK_EQUAL(matrix[5].size(), 5); + } + // this = this + column * v + const Column col3(cont{2, 3, 6}, &settings); + matrix[5].multiply_source_and_add(col3, 3); + veccont = {1, 1, 0, 1, 0, 1, 0}; + BOOST_CHECK(matrix[5].get_content(veccont.size()) == veccont); + if constexpr (Column::Master::Option_list::column_type != Column_types::HEAP) { + BOOST_CHECK_EQUAL(matrix[5].size(), 4); + } + // this = v * this + column + const Column col4(cont{0, 1, 3, 5}, &settings); + matrix[3].multiply_target_and_add(4, col4); + veccont = {1, 1, 0, 1, 0, 1, 0}; + BOOST_CHECK(matrix[3].get_content(veccont.size()) == veccont); + if constexpr (Column::Master::Option_list::column_type != Column_types::HEAP) { + BOOST_CHECK_EQUAL(matrix[3].size(), 4); + } +} + +template +void column_test_chain_methods() { + typename Column::Column_settings settings(5); + + Column col(&settings); + + BOOST_CHECK(!col.is_paired()); + BOOST_CHECK(col.get_paired_chain_index() == static_cast(-1)); + + col.unassign_paired_chain(); + BOOST_CHECK(!col.is_paired()); + BOOST_CHECK(col.get_paired_chain_index() == static_cast(-1)); + + col.assign_paired_chain(2); + BOOST_CHECK(col.is_paired()); + BOOST_CHECK(col.get_paired_chain_index() == 2); + + col.unassign_paired_chain(); + BOOST_CHECK(!col.is_paired()); + BOOST_CHECK(col.get_paired_chain_index() == static_cast(-1)); +} + +#endif // PM_COLUMN_TESTS_H diff --git a/src/Persistence_matrix/test/pm_column_tests_boost_type_lists.h b/src/Persistence_matrix/test/pm_column_tests_boost_type_lists.h new file mode 100644 index 0000000000..41e7188967 --- /dev/null +++ b/src/Persistence_matrix/test/pm_column_tests_boost_type_lists.h @@ -0,0 +1,127 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2023 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +#ifndef PM_COLUMN_TESTS_TYPE_LISTS_H +#define PM_COLUMN_TESTS_TYPE_LISTS_H + +#include + +#include "pm_column_tests_mastermatrix.h" +#include "pm_common_boost_type_lists.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using Gudhi::persistence_matrix::Column_types; +using Gudhi::persistence_matrix::Heap_column; +using Gudhi::persistence_matrix::Intrusive_list_column; +using Gudhi::persistence_matrix::Intrusive_set_column; +using Gudhi::persistence_matrix::List_column; +using Gudhi::persistence_matrix::Naive_vector_column; +using Gudhi::persistence_matrix::Set_column; +using Gudhi::persistence_matrix::Unordered_set_column; +using Gudhi::persistence_matrix::Vector_column; + +template +struct c_base_options { + using type = Base_col_options; +}; + +template +struct c_boundary_options { + using type = Boundary_col_options; +}; + +template +struct c_chain_options { + using type = Chain_col_options; +}; + +template +class column_non_validity { + private: + static constexpr bool is_non_valide() { + if constexpr (col_type::Master::Option_list::column_type == Column_types::INTRUSIVE_LIST) { + return !std::is_same_v >; + } else if constexpr (col_type::Master::Option_list::column_type == Column_types::INTRUSIVE_SET) { + return !std::is_same_v >; + } else if constexpr (col_type::Master::Option_list::column_type == Column_types::LIST) { + return !std::is_same_v >; + } else if constexpr (col_type::Master::Option_list::column_type == Column_types::SET) { + return !std::is_same_v >; + } else if constexpr (col_type::Master::Option_list::column_type == Column_types::UNORDERED_SET) { + return !std::is_same_v >; + } else if constexpr (col_type::Master::Option_list::column_type == Column_types::NAIVE_VECTOR) { + return !std::is_same_v >; + } else if constexpr (col_type::Master::Option_list::column_type == Column_types::VECTOR) { + return !std::is_same_v >; + } else if constexpr (col_type::Master::Option_list::column_type == Column_types::HEAP) { + return !std::is_same_v >; + } else { + return true; // we should not enter here, except if we want to ignore a column type. + } + } + + public: + static constexpr bool value = is_non_valide(); +}; + +// if a new column type is implemented, create a `ct_*` structure for it and add it to this list... +using col_type_list = boost::mp11::mp_list; +using row_col_type_list = boost::mp11::mp_list; +//...and add the column name here. +using column_list = mp_list_q; +using c_matrix_type_list = mp_list_q; + +template +using option_template = + boost::mp11::mp_transform >; + +template +using z2_no_ra_option_list = option_template; +template +using z2_only_ra_r_option_list = option_template; +template +using z2_only_ra_option_list = option_template; +template +using z5_no_ra_option_list = option_template; +template +using z5_only_ra_r_option_list = option_template; +template +using z5_only_ra_option_list = option_template; + +template +using c_matrices_list = boost::mp11::mp_product; + +// the remove_if here is quite unefficiant as it has to remove a lot. But I did not found another way to do it without +// having to define something for each column type individually. +template +using columns_list = boost::mp11::mp_remove_if< + boost::mp11::mp_product >, + column_non_validity>; + +#endif // PM_COLUMN_TESTS_TYPE_LISTS_H diff --git a/src/Persistence_matrix/test/pm_column_tests_mastermatrix.h b/src/Persistence_matrix/test/pm_column_tests_mastermatrix.h new file mode 100644 index 0000000000..b8751f8045 --- /dev/null +++ b/src/Persistence_matrix/test/pm_column_tests_mastermatrix.h @@ -0,0 +1,211 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2023 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +#ifndef PM_COLUMN_TESTS_MASTER_H +#define PM_COLUMN_TESTS_MASTER_H + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using Gudhi::persistence_matrix::Cell; +using Gudhi::persistence_matrix::Cell_column_index; +using Gudhi::persistence_matrix::Cell_field_element; +using Gudhi::persistence_matrix::Chain_column_extra_properties; +using Gudhi::persistence_matrix::Column_dimension_holder; +using Gudhi::persistence_matrix::Column_types; +using Gudhi::persistence_matrix::Dummy_cell_column_index_mixin; +using Gudhi::persistence_matrix::Dummy_cell_field_element_mixin; +using Gudhi::persistence_matrix::Dummy_chain_properties; +using Gudhi::persistence_matrix::Dummy_dimension_holder; +using Gudhi::persistence_matrix::Dummy_row_access; +using Gudhi::persistence_matrix::New_cell_constructor; +using Gudhi::persistence_matrix::Pool_cell_constructor; +using Gudhi::persistence_matrix::Row_access; + +using Zp = Gudhi::persistence_fields::Zp_field_operators<>; +using Z2 = Gudhi::persistence_fields::Z2_field_operators; + +template +struct Column_mini_matrix { + using Option_list = Options; + using Field_operators = typename Options::Field_coeff_operators; + using index = typename Options::index_type; + using id_index = typename Options::index_type; + using pos_index = typename Options::index_type; + using dimension_type = typename Options::dimension_type; + using element_type = typename std::conditional::type; + using characteristic_type = + typename std::conditional::type; + + struct matrix_row_tag; + struct matrix_column_tag; + + using base_hook_matrix_row = + boost::intrusive::list_base_hook, + boost::intrusive::link_mode >; + using base_hook_matrix_list_column = + boost::intrusive::list_base_hook, + boost::intrusive::link_mode >; + using base_hook_matrix_set_column = + boost::intrusive::set_base_hook, + boost::intrusive::link_mode >; + struct Dummy_row_hook {}; + struct Dummy_column_hook {}; + + using row_hook_type = typename std::conditional::type; + using column_hook_type = + typename std::conditional::type + >::type; + + using Cell_column_index_option = + typename std::conditional, Dummy_cell_column_index_mixin>::type; + using Cell_field_element_option = typename std::conditional + >::type; + using Cell_type = Cell >; + + inline static New_cell_constructor defaultCellConstructor; + using Cell_constructor = New_cell_constructor; + + struct Column_z2_settings { + Column_z2_settings() : cellConstructor() {} + Column_z2_settings([[maybe_unused]] characteristic_type characteristic) : cellConstructor() {} + + Cell_constructor cellConstructor; + }; + + struct Column_zp_settings { + Column_zp_settings() : operators(), cellConstructor() {} + Column_zp_settings(characteristic_type characteristic) : operators(characteristic), cellConstructor() {} + + Field_operators operators; + Cell_constructor cellConstructor; + }; + + using Column_settings = typename std::conditional::type; + + template + struct RowCellComp { + bool operator()(const Cell_type& c1, const Cell_type& c2) const { + return c1.get_column_index() < c2.get_column_index(); + } + }; + + using Row_type = + typename std::conditional, + boost::intrusive::base_hook >, + std::set > + >::type; + + using row_container_type = typename std::conditional, + std::vector + >::type; + + using Row_access_option = typename std::conditional >, + Dummy_row_access + >::type; + + static const bool isNonBasic = !Options::is_basic; + + using Column_dimension_option = + typename std::conditional >, + Dummy_dimension_holder + >::type; + + using Chain_column_option = typename std::conditional >, + Dummy_chain_properties + >::type; + + using boundary_type = typename std::conditional, + std::initializer_list > + >::type; +}; + +template +struct Base_col_options { + using Field_coeff_operators = typename std::conditional::type; + using index_type = unsigned int; + using dimension_type = int; // needs to be signed. + + static const bool is_basic = true; // exists just for the tests + static const bool is_of_boundary_type = true; + + static const bool is_z2 = is_z2_only; + static const Column_types column_type = col_type; + + static const bool has_row_access = has_row; + static const bool has_removable_rows = rem_row; // ignored if has_row_access == false + static const bool has_intrusive_rows = intr_row; // ignored if has_row_access == false +}; + +template +struct Boundary_col_options { + using Field_coeff_operators = typename std::conditional::type; + using index_type = unsigned int; + using dimension_type = int; // needs to be signed. + + static const bool is_basic = false; // exists just for the tests + static const bool is_of_boundary_type = true; + + static const bool is_z2 = is_z2_only; + static const Column_types column_type = col_type; + + static const bool has_row_access = has_row; + static const bool has_removable_rows = rem_row; // ignored if has_row_access == false + static const bool has_intrusive_rows = intr_row; // ignored if has_row_access == false +}; + +template +struct Chain_col_options { + using Field_coeff_operators = typename std::conditional::type; + using index_type = unsigned int; + using dimension_type = int; // needs to be signed. + + static const bool is_basic = false; // exists just for the tests + static const bool is_of_boundary_type = false; + + static const bool is_z2 = is_z2_only; + static const Column_types column_type = col_type; + + static const bool has_row_access = has_row; + static const bool has_removable_rows = rem_row; // ignored if has_row_access == false + static const bool has_intrusive_rows = intr_row; // ignored if has_row_access == false +}; + +#endif // PM_COLUMN_TESTS_MASTER_H diff --git a/src/Persistence_matrix/test/pm_common_boost_type_lists.h b/src/Persistence_matrix/test/pm_common_boost_type_lists.h new file mode 100644 index 0000000000..fe67522bd5 --- /dev/null +++ b/src/Persistence_matrix/test/pm_common_boost_type_lists.h @@ -0,0 +1,70 @@ +/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. + * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. + * Author(s): Hannah Schreiber + * + * Copyright (C) 2023 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +#ifndef PM_COMMON_BOOST_TYPE_LISTS_H +#define PM_COMMON_BOOST_TYPE_LISTS_H + +#include + +#include + +using Gudhi::persistence_matrix::Column_types; + +struct ct_intrusive_list { + static constexpr const Column_types t = Column_types::INTRUSIVE_LIST; +}; + +struct ct_intrusive_set { + static constexpr const Column_types t = Column_types::INTRUSIVE_SET; +}; + +struct ct_list { + static constexpr const Column_types t = Column_types::LIST; +}; + +struct ct_set { + static constexpr const Column_types t = Column_types::SET; +}; + +struct ct_heap { + static constexpr const Column_types t = Column_types::HEAP; +}; + +struct ct_unordered_set { + static constexpr const Column_types t = Column_types::UNORDERED_SET; +}; + +struct ct_vector { + static constexpr const Column_types t = Column_types::VECTOR; +}; + +struct ct_naive_vector { + static constexpr const Column_types t = Column_types::NAIVE_VECTOR; +}; + +struct true_value { + static constexpr const bool t = true; +}; + +struct false_value { + static constexpr const bool t = false; +}; + +template +using get_type = typename T::type; + +template